Educational Products: Month 1

Michael Lynch

教育產品:第 1 個月

一句話總結

我要回來重新經營我的部落格課程了。

亮點

  • 我正在重啟 2020 年的部落格課程。
  • htmx 還不錯,但並非完全符合我的期待。
  • 我正在為我以前的生酮網站 Is It Keto 尋找買家

重啟《Hit the Front Page》

2020 年,我製作了一門關於部落格寫作的影片課程,名為「Hit the Front Page of Hacker News」。我對課程內容感到自豪,也收到學生的正面回饋,但我覺得自己從未給予它應有的關注。

課程發布時,TinyPilot 正快速成長,我沒有時間行銷課程或迭代內容。

我的上一篇部落格文章中,我調查了讀者希望我接下來做什麼。在表示有興趣看我教學的人當中,結果如下:

關於接下來想看我教什麼的興趣堆疊長條圖。「幫助開發者提升寫作能力」與「將刻意練習技巧應用於軟體開發」並列第一,各有 32 人感興趣。「為開發者族群寫部落格」排名第三,有 31 人感興趣。

我上一篇部落格文章中讀者問卷的回覆結果

解讀這些數據的方式有很多,但我的結論是,大家特別希望我教授寫作技巧。讓我驚訝的是,刻意練習也名列前茅,雖然熱情稍微低一些。

我決定,既然現有課程已經幾乎完成,而且不論怎麼算都排在第二或第三名,我應該把這些教材拿出來整理一下,並在 2024 年重新發布更新版。

尋找試點計畫的學員

Rob Fitzpatrick(羅伯·費茲派崔克)的著作 Write Useful Books(《寫出有用的書》)深深影響了我對教育產品的做法。他主張,在發布書籍或影片課程之前,應該先以現場教學的方式教授主題,以便根據真實學員的回饋進行迭代。

我和太太也在八月底迎來我們的第一個孩子,到時候我打算暫別幾個月,專心陪伴家人。當我考慮這件事時,距離預產期大約還有 10 週,而課程為期六週,因此緩衝時間不多。

我寫了一篇關於課程的簡介,並寄電子郵件給部落格訂閱者,請有興趣的人填寫一份簡短的申請表。當有人填寫申請表後,我會針對他們在表單中的回覆給予個人化回應,並傳送付款連結以保留名額。

結果如下:

  • 1,944 位訂閱者收到了關於課程的電子郵件。
  • 11 人填寫問卷表達興趣。
  • 7 人購買了課程。
    • 在填寫問卷後未報名的 4 人中,有 3 人是因為時段衝突。

尋找視訊平台

令我驚訝的是,視訊通話方面其實沒有真正可行的 Zoom 替代方案。自從他們被揭露在安全性上說謊後,我就一直避免使用 Zoom,但對於現場課程來說,他們似乎是唯一的選擇。

過去幾年我都用 Jitsi Meet 開工作會議,整體體驗大多不錯,但我注意到參加 Zoom 會議時的品質更好。我原本希望找到 Jitsi Meet 的付費方案,但他們所有的付費選項都標示為「Contact Us」,這很可能意味著每月至少要 1,000 美元。

所以,Jitsi 還算可用,雖然有點笨重。它不支援錄影,因此我的變通方法是:

  1. 在我的筆記型電腦上主持課程視訊通話。
  2. 從我的桌上型電腦加入通話。
  3. 在桌上型電腦上使用一般的螢幕錄影軟體錄製通話。
  4. 將影片上傳到PicoShare
  5. 將 PicoShare 連結用電子郵件寄給全班。

這比我希望的要複雜一些,但也有一個額外的好處:即使我簡報的投影片佔滿了整個螢幕,我還是可以看著桌上型電腦的螢幕看到每個人的臉。

如果你知道比 Jitsi Meet 更好的替代方案,請告訴我

我該放棄 Hacker News 這個主軸嗎?

大約有一半報名課程的學員表示,他們對 Hacker News 並沒有特別感興趣。他們只是喜歡我的寫作,想更了解我的寫作過程,因此儘管課程聚焦於 Hacker News,他們還是報名了。

回頭看問卷數據,大家對寫作本身的興趣似乎高於對部落格寫作的興趣。而且沒有人特別要求要學 Hacker News。

所以,我在想是否應該放棄 Hacker News,轉向更為通用的主題。

我擔心放棄 Hacker News 會讓我失去優勢。教部落格或寫作的人多如過江之鯽,我覺得自己很難在其中脫穎而出。如果我專門教 Hacker News,那我在這個領域就是全世界最厲害的老師,因為沒有其他人在做這件事。

放棄 Hacker News 的另一個問題是,我大部分部落格成功的證據都與 Hacker News 有關。在個人部落客中,我在 Hacker News 上取得了非比尋常的成功。如果我教更通用的寫作課程,我的資歷就沒那麼亮眼。我沒有靠部落格賺錢,也沒有龐大的訂閱數可以炫耀。

同時,我認為大多數購買我課程的人,都是先認識我,然後才找到我的課程。他們大概不是搜尋「全世界最懂 Hacker News 的專家」才找到我的。

也許結論是,我應該少擔心與眾多其他人競爭,因為無論如何,通往我課程的路徑很可能都是透過我本人或口耳相傳。

這門課程以原本聚焦 Hacker News 的形式已經完成了 95%,所以我打算先發布這個版本。完成之後,我會再調整教材,以適用於更通用的課程。

學習 htmx

過去兩年來,htmx 越來越頻繁地出現在我的關注範圍內。我的朋友 Cory Zue(柯瑞·祖伊)使用 htmx,這引起了我的興趣。

很長一段時間以來,最大的障礙只是我不懂 htmx 的意義何在。

htmx 官方首頁的第一句話之一是:「為什麼只有 <a><form> 能發送 HTTP 請求?」當我讀到這句話時,我的感覺是:「嗯,這樣是不錯,但 JavaScript 可以讓任何 HTML 元素發送任何類型的請求。我需要為了省下幾行 JavaScript 就採用一套全新的方法嗎?」

真正讓我理解 htmx 的是這本書——Hypermedia Systems(《超媒體系統》)。它由 htmx 的同一批作者撰寫,解釋了 htmx 背後的動機,並詳細說明了幾個可以使用它的情境。

如果要我向四年前的自己推銷 htmx,我會這樣說。

寫給過去的自己的 htmx 推介詞

還記得你剛開始學習寫網站時,會這樣寫 HTML 嗎?

<form action="/users" method="POST">
  <input name="first-name" placeholder="First name" />
  <input name="last-name" placeholder="Last name" />
  <input type="submit" value="Add user" />
</form>

現在你不會這樣寫了,因為你想避免使用者提交表單時重新載入整個頁面。整頁重新載入速度慢又讓人困惑,尤其當你只是想顯示「User added successfully」這樣的訊息時。而且如果伺服器拒絕輸入,你也不想清空使用者所有的輸入內容。

所以,你會轉而使用 JavaScript,寫出像這樣的程式碼:

document.addEventListener("DOMContentLoaded", () => {
  document.querySelector("form").addEventListener("submit", (evt) => {
    evt.preventDefault(); // Block default submit.
    fetch(`/users`, {
      method: "POST",
      credentials: "include",
      headers: {
        Accept: "application/json",
      },
      body: JSON.stringify({
        firstName: document.querySelector("[name='first-name']"),
        lastName: document.querySelector("[name='last-name']"),
      }),
    })
      .then((response) => {
        if (response.ok) {
          return response.json();
        }
        // TODO: Handle errors too.
      })
      .then((result) => {
        // TODO: Handle success.
      });
  });
});

這不算大量的 JavaScript,但每次需要表單時都得重寫一次。你可以重構重複的程式碼,但那會讓 UI 邏輯分散到多個檔案中。對於每次客戶端與伺服器的互動來說,這都是額外的阻礙。

或者,你可能會轉向像 React 或 Vue 這樣厚重的框架,而現在你的程式碼與瀏覽器中呈現的內容之間,就隔著無數層 JavaScript 抽象層。

htmx 的承諾是回歸到你剛開始學做網頁時的簡潔。與其寫 HTML 然後把所有邏輯都丟給 React 或手寫的事件處理器,你可以將 htmx 整合到應用程式中,然後這樣寫表單:

<form hx-post="/users" hx-target="this">
  <input name="first-name" placeholder="First name" />
  <input name="last-name" placeholder="Last name" />
  <input type="submit" value="Add user" />
</form>

然後 htmx 會讓一切直接運作,而你不需要撰寫任何自訂的 JavaScript。

我目前使用 htmx 的體驗

為了測試 htmx,我一直在用 htmx 重寫 ScreenJournal 的部分功能。ScreenJournal 是我的開源電影評論應用程式,就像是電影版的 Goodreads。或者說,它像是 letterboxd,但開源且比較陽春。

ScreenJournal 是我用來撰寫電影評論的興趣專案網頁應用程式。

我重寫的一個好例子是用 htmx 重新實作通知偏好設定頁面。我有一個頁面讓使用者指定想接收哪些電子郵件:

ScrenJournal 上用來控制通知的頁面

通知頁面需要大量自訂 JavaScript 來完成以下功能:

  • 透過 fetch 提交表單內容,而非重新載入頁面。
  • 在請求進行中時停用表單輸入。
  • 在請求進行中時顯示狀態轉圈圖示。
  • 若請求失敗則顯示錯誤訊息。

以下是從原生 JavaScript 轉換到 htmx 的對比:

整體程式碼行數更少,尤其是 JavaScript 的行數大幅減少。它將邏輯從前端移到後端,這是我偏好的做法,因為我覺得後端程式碼比 UI 程式碼更容易測試。

以下是目前使用 htmx 的一些心得:

htmx 增加了一層抽象,但很直觀

  • 使用 Vue 和 Angular 時,我完全不知道它是如何將我的程式碼轉換成網頁應用程式的。
  • 使用 htmx 時,它的行為夠直觀,我大概可以根據所見的運作方式自行重新實作 htmx。

htmx 的錯誤處理不太理想

  • htmx 的模型假設你向伺服器發送請求,然後將伺服器的回應放置在頁面上的一個元素中。
  • 問題在於,成功時我想替換 HTML 表單,但失敗時我想保留表單不動,並在表單下方顯示錯誤訊息。
  • htmx 的解法是讓伺服器重新渲染包含使用者所有輸入的 HTML 表單,但我不喜歡這樣,因為這等於從頭重新渲染原本就已存在的東西,而且會擴大 XSS(跨網站指令碼) 弱點的攻擊面。
  • 我可以透過撰寫自己的事件處理器來繞過這個問題,但到那個地步,就有點像是在跟框架對抗了。

htmx 會削弱 Content Security Policy(內容安全政策)(CSP)

  • 我喜歡把 CSP 當作防止跨網站指令碼(XSS)的最後一道防線。
  • htmx 大致上與 CSP 相容,但由於 htmx 功能太多,攻擊者只要能寫入自訂的 HTML,就等同於能寫入任意的 JavaScript。
  • 範例
    • <form action=/delete-account" method="post" onload="this.submit()">
      • CSP 會阻止這段程式碼執行。
    • <form hx-post="/delete-account" hx-trigger="load">
      • CSP 則會允許這段等效的程式碼執行。
  • 你仍然可以用 htmx 寫出安全的應用程式,但它確實會讓 CSP 無法成為防禦 XSS 的可靠最後防線。

出售 Is It Keto

我這週注意到,Amazon 在某個時間點改變了聯盟行銷連結的做法,導致 Is It Keto 上 90% 的聯盟連結失效。這個網站仍透過 Google AdSense 賺取收益,但我已經沒有時間維護它了。

我正在尋找願意收購這個網站的人,如果收到來自其他獨立創業者或有志創業者的不錯提案,我願意以低於市價的價格出售。

總結

完成了什麼?

  • 我開始向一小群學員教授我的部落格課程。
  • 我透過將 ScreenJournal 的許多部分移植到 htmx 來學習 htmx。
  • 我錄製了課程的第一段額外內容。

經驗教訓

  • 我並沒有像自己想的那樣受限於課程中的 Hacker News 主軸。
    • 如果有人要上我的課程,他們很可能是因為喜歡我的寫作,而不是因為這些文章在 Hacker News 上的表現如何,所以我應該多想想這樣的客群是什麼樣子。

下個月的目標

  • 錄製課程中四堂課可發布的版本。
  • 開始銷售新版課程。

尋求協助

視訊通話平台

如果你對符合以下條件的視訊通話平台有建議,請告訴我:

  • 必要:支援最多 10 位參與者、通話時間 90 分鐘。
  • 必要:允許參與者無需建立新帳號或在裝置上安裝軟體即可加入通話。
  • 必要:費用為每月 80 美元或以下。
  • 加分:允許我錄製視訊通話。

課程平台的使用經驗

如果你曾以講師或學員的身分使用過像 Maven 或 Udacity 這類課程平台,請告訴我你的經驗。

原文由 Michael Lynch 發布

本文章由 muse-spark-1.2-contributor 進行翻譯