昨天有人請我在 Slack 上分享會議紀錄。

我貼上了 Markdown。Slack 把表格吃掉了。程式碼區塊失去了縮排。待辦清單變成了字面上的 [ ] 字元。我花了五分鐘重新排版一個在我的編輯器裡本來就看起來完美的東西。

所以我改成直接送出一個連結。

不是 Notion——對方得先註冊。不是 Google 文件——同樣有問題,而且我也不想讓這些內容永遠留在別人的雲端硬碟裡。不是 pastebin——那只是沒有結構、沒有頁面、也沒辦法明天回來修正錯字的一坨純文字。

我想要的是:一個連結、多個頁面、真的能正確渲染的 Markdown、免註冊,而且之後我還能修改內容、URL 卻不會變。

我以前做過分享工具。FreeShare 是給檔案用的。NotePage 是給單頁文字用的。SharePad 就是我在需要介於 pastebin 和 wiki 之間的東西時,希望它早就存在的產品——但又不想有註冊牆。

線上體驗:https://sharepad.in

原始碼:https://github.com/Varshithvhegde/sharepad

https://sharepad.in


想法

大多數「分享筆記」產品都走同一套流程。建立帳號、驗證電子郵件、建立工作區、邀請成員、設定權限。等你全部弄完,會議早就結束了,也沒人再在乎那些筆記。

SharePad 直接跳過這一切。

你寫 Markdown,系統給你兩個連結:

  • 檢視連結/n/kitchen-reno — 可以自由分享
  • 編輯連結/e/{secret-token} — 這就是你的擁有權憑證,請不要分享

不用帳號。不用建立密碼(除非你想要把檢視連結鎖起來)。也沒有「升級後才能分享給更多人」這種東西。編輯 token 本身就是那本筆記本的身份。

這個決定——以 token 作為擁有權,而不是使用者帳號——讓其他一切都簡化了。沒有驗證流程、沒有 session 管理、應用本身也不需要「忘記密碼」。建立時只產生一組 32 位元組的 secret,以 SHA-256 雜湊後存入 Postgres,並且只顯示給你一次。

<!-- 📸 IMAGE 1 — COVER + hero (★★★ required)
WHAT: Split editor in edit mode — markdown textarea on the left, live
preview on the right. Show the red margin rule, toolbar, and real
content (headings, a table, a code block).
WHERE: /e/{your-edit-token} → switch to split view (⌘/)
CAPTURE: Full browser width, ~1440px. Crop to editor area if needed.
ALT TEXT: Split editor with markdown on the left and live preview on the right
-->

Split editor with markdown on the left, live preview on the right, red margin rule visible


你實際可以拿它做什麼

在看架構圖之前,先看看實際使用時它長什麼樣。

十秒內貼上並分享。/quick,貼上你的 Markdown,拿到連結。標題來自第一個標題層級。slug 自動產生。編輯連結會直接進入你的剪貼簿。完成。

<!-- 📸 IMAGE 2 — Quick paste flow (★★★ required)
WHAT: /quick page right after creating a note — textarea with markdown,
the success state showing the view link (and ideally “copied” toast).
WHERE: sharepad.in/quick → paste sample markdown → click share
CAPTURE: Include the “Paste and share” heading and the generated link box.
ALT TEXT: Quick paste page with a generated share link on the clipboard
-->

Quick paste page — markdown in the box, share link ready to copy

單一 URL 底下可放多頁筆記本。 這是它和 pastebin 最大的差別。一個筆記本可以有好幾十頁——會議紀錄、附錄、待辦清單、參考文件——全部都能透過側邊欄索引瀏覽。讀者只看到一個連結,你管理的是一個筆記本。

<!-- 📸 IMAGE 3 — Multi-page sidebar (★★ strongly recommended)
WHAT: Editor with sidebar open showing 3–5 named pages (Meeting notes,
Todo, Appendix, etc.). Active page highlighted.
WHERE: Any notebook with multiple pages in edit mode
CAPTURE: Left sidebar + part of the main content pane
ALT TEXT: Notebook sidebar with multiple pages behind one share link
-->

Sidebar listing multiple pages in a single notebook

自己選地址。/new 你可以一邊輸入一邊認領 sharepad.in/n/kitchen-reno。名字被占用會立刻提示。保留的 slug(像 /new/quick/api 等)會被擋下來,避免你不小心破壞路由。

<!-- 📸 IMAGE 9 — Custom slug + create form (★ nice to have)
WHAT: /new creation form — title field, slug field with green “available”
check, paper texture chips (ruled/grid/dot/plain), expiry picker.
WHERE: sharepad.in/new
CAPTURE: Middle of the form, slug checker visible
ALT TEXT: New notebook form with custom slug and paper texture options
-->
New notebook form with custom slug check and paper texture picker

兩種權限層級。 把檢視連結丟到 Slack。把編輯連結存在密碼管理器裡。或者開啟開放編輯,讓任何拿到檢視連結的人都能寫內容——適合共享購物清單或回顧板,不想逼大家註冊帳號的場景。

重要細節:開放編輯只允許訪客修改內容。設定、到期時間、密碼和刪除,一律還是需要編輯 token。訪客不可能把你自己的筆記本鎖在門外。

<!-- 📸 IMAGE 4 — Share panel (★★ strongly recommended)
WHAT: Share modal open — view URL, edit URL (masked or partial), QR code,
copy buttons. Ideally both links visible.
WHERE: Edit mode → Share icon (top right)
CAPTURE: The full share panel card with tape decoration
ALT TEXT: Share panel showing view link, secret edit link, and QR code
-->

Share panel with view link, edit link, and QR code

需要時可以把它鎖起來。 檢視連結可加密碼保護(bcrypt 雜湊)。可切換成唯讀模式。可設定看一次即焚,讓連結只有效一次。也能選擇可見性:公開(可被索引)、未列出(只能透過連結)、或私人(檢視連結直接封鎖)。

<!-- 📸 IMAGE 8 — Settings panel (★ nice to have)
WHAT: Settings modal — expiry chips, visibility radios, password field,
open editing toggle, delete button.
WHERE: Edit mode → Settings gear icon
CAPTURE: Scroll to show expiry + visibility + toggles
ALT TEXT: Notebook settings — expiry, visibility, password, open editing
-->

Settings panel with expiry, visibility, and access toggles

可以直接把截圖貼進編輯器。 這部分比我預期花了更久才做好。你貼上一張圖片,瀏覽器先壓縮,然後上傳到 Cloudflare R2,最後插入 Markdown 連結。下面會再細講。

<!-- 📸 IMAGE 7 — Image paste (★ nice to have)
WHAT: Either (a) editor with a pasted screenshot rendered in preview, or
(b) the “⏳ uploading image 1…” placeholder mid-upload.
WHERE: Edit mode → paste a screenshot from clipboard
CAPTURE: Split view showing image in the preview pane
ALT TEXT: Pasted screenshot rendering inline in the markdown preview
-->

Screenshot pasted into the editor and rendered in the live preview

能把整份內容列印成真正的文件。 /n/slug/print 會移除手寫感風格,並以 Source Serif 呈現每一頁——封面、目錄、分頁。直接從瀏覽器另存為 PDF。手寫風格消失,最後看起來像你會交給客戶的文件,而不是塗在便利貼上的草稿。

<!-- 📸 IMAGE 6 — Print / PDF view (★★ strongly recommended)
WHAT: /n/{slug}/print — title page or table of contents in Source Serif,
clean white background, no sticky-note UI. Show “Print” if visible.
WHERE: sharepad.in/n/your-slug/print
CAPTURE: Title sheet + start of TOC. Use browser print preview if cleaner.
ALT TEXT: Print view — professional serif layout with table of contents
-->

Print view with serif typography, title sheet and table of contents

還會自動幫你整理後續收尾。 筆記本預設 10 天後到期。可以延長到一年,或直接關閉到期機制。當時間一到,檢視連結會立刻失效。三天後由 cron 工作實際刪除資料列。

<!-- 📸 IMAGE 5 — Home page (★★ strongly recommended)
WHAT: Landing page hero — “Write it down. Send one link.”, pinned
“Free forever · No signup” tag, sticky-note feature cards below.
WHERE: sharepad.in/
CAPTURE: Above-the-fold hero + one row of feature cards
ALT TEXT: SharePad home page with paper aesthetic and free-no-signup tag
-->

Home page hero — Write it down, Send one link, Free forever no signup


技術堆疊

Next.js 16 搭配 App Router 和 Turbopack。初始筆記本載入使用伺服器元件,編輯器使用用戶端元件。分割檢視、自動儲存、捲動同步與工具列都在同一個 NotebookEditor 元件中,負責處理檢視模式(/n/slug)和編輯模式(/e/token)。

Supabase Postgres 負責所有持久化資料:筆記本、頁面、版本歷史、留言、圖片中繼資料、速率限制計數器。每張資料表都啟用 row-level security,但沒有公開寫入政策——伺服器是透過 service role key 與 Postgres 溝通。anon key 只是因為 Supabase 需要它,但應用程式不會直接暴露用戶端寫入。

Tailwind CSS 4 負責樣式,但視覺辨識刻意不是那種通用 SaaS 儀表板。手寫字體用 Kalam 和 Architects Daughter。每一頁都有紅色邊界線。首頁有貼膠帶的便利貼卡片。我想讓它看起來像紙張,而不是又一個紫色漸層主視覺的 AI 產品。

react-markdown 搭配 remark-gfm 處理 GitHub 風格 Markdown、rehype-highlight 處理程式碼區塊、rehype-raw 處理內嵌 HTML,並在任何內容進入 DOM 之前先經過 rehype-sanitize。最後這一步不是可有可無的——等一下會說原因。

Cloudflare R2 負責圖片。我選 R2 而不是 Supabase Storage,是因為它的 egress 在任何用量下都免費。Supabase 的免費方案會讓資料庫和儲存空間共用 egress——一個突然爆紅、又塞滿截圖的筆記本就可能把整個 app 拖垮。R2 對 egress 永遠不收費。

PostHog 負責分析,而且是選用、且限制很多的那種。關掉 autocapture。筆記本路由關掉 session recording。任何事件 payload 都不包含筆記內容、標題、slug 或編輯 token。編輯 token 是寫入憑證——把它放進分析工具等於把筆記本控制權交給第三方。

GitHub Actions 跑兩個 cron 工作:每三天 ping 一次 Supabase,避免免費專案暫停;每週清理 R2 裡的孤兒圖片。


擁有權如何運作(沒有登入)

沒有 users 資料表。沒有註冊表單。完整的認證模型如下:

建立筆記本
  → server 產生 edit_token(32 個隨機位元組)
  → 將 SHA-256(edit_token) 存入 notebooks.edit_token_hash
  → 只回傳一次 edit_token 給瀏覽器
  → 瀏覽器把它存進 localStorage,供首頁的「已儲存筆記本」使用

檢視:

GET /n/{slug}           → 任何人都可看(除非有密碼/已過期/私人)

編輯:

GET /e/{edit_token}     → 驗證 token 雜湊 → 完整編輯器

寫入:

POST/PATCH/DELETE /api/* with X-Edit-Token header

受密碼保護的筆記本會多一步:

POST /api/notebooks/unlock/{slug}  { password }
  → bcrypt compare
  → 設定 httpOnly cookie sp_unlock_{slug} 24 小時

編輯 token 不會出現在檢視路由的 URL 裡。檢視路由在未開放編輯時也絕不接受寫入。設定永遠需要 token。

我做過完整帳號系統的產品。對這個使用情境來說,帳號只會造成純粹摩擦。編輯連結本身就是帳號。


編輯器

編輯器就是一個 textarea 加上一個預覽窗格。故意做得很樸素。

分割檢視與捲動同步。 左邊寫、右邊看。捲動其中一邊,另一邊會跟著動。手機上則會收合成只寫模式,因為在手機上做分割檢視根本不可用。

工具列操作會參與復原。 這段時間有一個很微妙的 bug。打字時 Ctrl+Z 可以正常用,但點「粗體」或「插入程式碼區塊」不行——因為我直接改了 textarea.value,繞過了瀏覽器的復原堆疊。修正方式:document.execCommand("insertText")。對,它已經過時了。但若要在普通 textarea 裡讓程式化編輯也能復原,而不是自己打造一整套 ProseMirror,我目前還是得靠它。我可不會為了 side project 去做 ProseMirror。

Tab 真的會縮排。 預設瀏覽器行為是把焦點移到下一個按鈕。SharePad 會攔截 Tab,插入兩個空白(或縮排整個選取區塊)。Shift+Tab 會取消縮排。如果你真的需要跳到下一個 UI 元件,Esc+Tab 是逃生路徑。

版本歷史。 每一頁都保留最近十個草稿。打開歷史記錄,挑一個舊版本,還原即可。快照是在內容變動時由 server 端產生,而不是每個按鍵都記錄——否則自動儲存的 debounce 會產生一堆沒用的版本。

頁面範本。 會議紀錄、專案簡報、週計畫、日記。點一下就能新增一個預先填好的頁面,標題結構也很合理。


圖片(以及只會在真實照片上爆炸的 bug)

貼上一張截圖,理論上應該直接可用。

流程是這樣:

  1. 瀏覽器先壓縮圖片——最大邊長 1600px,重新編碼成 82% 品質的 WebP。手機截圖通常在碰到網路之前就先縮小 10 倍。
  2. 伺服器 sniff magic bytes(不是看宣告的 Content-Type,因為上傳者可以隨便填,證明不了任何事)。
  3. 物件存到 R2,路徑為 {notebook_id}/{random-hex}.{ext}
  4. 將資料列插入 images 資料表。
  5. 插入 Markdown 連結:![alt](https://pub-....r2.dev/...)

限制:壓縮後 5 MB、每本筆記本 50 張圖片、每個 IP 每 10 分鐘 60 次上傳。速率限制用單一 Postgres 陳述式完成,這樣兩個同時上傳的請求就不會一起讀到舊的計數而鑽漏洞。

浪費我一個下午的 bug 是:小圖片可以貼,大圖片卻失敗,錯誤訊息是「The image store did not accept that file.」

Node 的 fetch 在 body 超過某個大小後會切換成 chunked transfer encoding。Cloudflare R2 對沒有明確 Content-Length 的 PUT 會回 411 Missing ContentLength。小圖片剛好塞進單一 chunk,所以能用;真正的照片就不行。

修正只要加一個 header:

headers: {
  "Content-Type": contentType,
  "Content-Length": String(body.byteLength),
  "Cache-Control": "public, max-age=31536000, immutable",
}

我也學到,把動畫 GIF 丟進 canvas 會只保留第一幀,動畫會悄悄壞掉,所以 GIF 不壓縮、直接通過。還有一個 ⏳ uploading image 1… 的 placeholder,明顯比插入一個空的 ![]( ) 好,因為空的 src 會讓瀏覽器試圖重新載入整個頁面。

<!-- 📸 IMAGE 12 — GIF demo (★ nice to have — great closer, like FlowChat post)
WHAT: Short loop: paste screenshot → “uploading…” text → image appears in
preview with markdown link in editor. 5–8 seconds.
HOW: QuickTime screen record → ezgif.com → upload to dev.to
ALT TEXT: GIF of pasting a screenshot into SharePad and seeing it render
-->
GIF — paste a screenshot and watch it upload and render in the preview


Markdown 裡的 HTML(以及為什麼消毒不是可選項)

SharePad 會在 Markdown 裡渲染原始 HTML——<details><kbd><mark><abbr>、表格對齊屬性。這對可收合區塊與鍵盤快捷鍵很有用。

但它也會在渲染前,依照 GitHub 的 schema 把所有內容做消毒。script、style、iframe、object、form、事件處理器、javascript: URL——全部都會被移除。

這件事為什麼重要:開放編輯 代表任何拿到檢視連結的人都能寫進筆記本。如果沒有消毒,訪客就能插入 script。當擁有者打開編輯連結時,這段 script 會在擁有者的瀏覽器中執行——而編輯 token 正好就在 URL 裡,所有已儲存的 token 也都在 localStorage 裡。只要一個惡意筆記本,就能把這個瀏覽器曾建立過的所有筆記本全交出去。

允許的標籤集合放在 lib/markdown-schema.ts。我在 GitHub 預設值之上多加了一些呈現性標籤,但沒有放寬任何和 script 有關的限制。


到期機制(以及那段沒人要求、但每個人都需要的寬限期)

選擇「10 天」會儲存一個絕對時間戳,而不是滾動式倒數。

時間線如下:

  1. 到期時刻 —— 檢視與列印連結回傳 404,讀者立即被鎖在外面。
  2. 三天後 —— pg_cron 真正刪除筆記本資料列。頁面、版本、留言全部連帶消失。
  3. 看一次即焚 —— 單次檢視後一天刪除。

「讀者先被鎖住、資料列稍後才刪除」這三天的間隔是刻意設計的。在這段期間內,擁有者仍可開啟編輯連結,把到期日往後延。若到期就立刻硬刪,只要漏掉一次行事曆提醒,筆記就會完全消失,沒有任何復原路徑。

select cron.schedule(
  'purge-expired-notebooks', '15 3 * * *',
  $$select public.purge_expired_notebooks()$$
);

透過 app 刪除筆記本時,會立即清掉它在 R2 裡的圖片。過期筆記本則依賴每週一次的掃描:列出 bucket,檢查哪些 notebook ID 在 Postgres 裡已不存在,然後刪除那些孤兒檔案。


一些誠實的痛點

日期的 hydration 不一致。 toLocaleDateString() 在 server 上渲染成 11/08/2026,在 client 上卻變成 8/11/2026。老問題了。後來用一個共用工具明確指定 en-GB 格式,讓 server 和 client 保持一致。

紅色邊界線飄到錯的位置。 在寬螢幕上,邊界線錨定在全寬容器,但文字卻在置中的欄位裡。結果紅線和文字之間差了一大段距離。修正:把邊界線移到文字欄位包裝器內,讓它不論視窗寬度都緊貼內容。

CSS 權重對上 Tailwind。 全域 CSS 裡的 .sk paper-card 類別有 position: relative。Tailwind 的 absolute 在下拉選單上輸掉了層疊規則戰。選單被裁成一條一像素寬的細縫。修正:把選單包在一般的 div 裡,用 inline positioning,並把 .sk 移到 @layer components 裡,讓 utilities 能勝出。

<!-- 📸 IMAGE 10 — Custom 404 (★ nice to have — shows personality)
WHAT: The torn-paper 404 page (“This page got lost”) after opening an
expired or deleted notebook slug.
WHERE: sharepad.in/n/something-that-does-not-exist
CAPTURE: Full 404 card with the handwritten reasons list
ALT TEXT: SharePad 404 page with torn paper notebook aesthetic
-->

Custom 404 page — torn paper notebook not found

刪除筆記本後沒有重新導向。 API 回傳 200,筆記本也確實刪掉了,但使用者還停留在一個已死的 /e/{token} 頁面上,盯著凍結的設定對話框。router.push("/") 是一種 soft navigation,沒有徹底拆掉編輯器。修正:window.location.replace("/?deleted=1") —— 直接離開,而且不能用返回鍵回到幽靈 URL。

圖片速率限制太嚴。 每 10 分鐘 20 次上傳在試算表裡看起來合理,但實際上要寫一份文件,五分鐘內就可能貼十幾張截圖。後來調到 60。每本筆記本 50 張的上限才是真正的濫用限制;IP 限制只是防止有人把這裡當成跨好幾百本筆記本的免費圖床。

Supabase 免費方案閒置七天後會暫停。 我用 GitHub Action 每三天 ping 一次 /api/ping。基礎設施聽起來很無聊,但資料庫一旦暫停,整個 app 就死了。


SEO(因為沒有人會搜尋「SharePad」)

他們會搜尋「share markdown without signup」、「online notepad no login」、「send notes without account」。

所以我為每個字詞都做了著陸頁——/share-markdown-without-signup/online-notepad-no-login/markdown-to-pdf——而且內容是真的有東西,不是關鍵字堆砌。再加上給現在會讀這些檔案的爬蟲用的 llms.txtllms-full.txt。首頁也放了 sitemap、robots.txt、FAQ schema。品牌名稱對發現性沒那麼重要,著陸頁才重要。


自己跑起來

git clone https://github.com/Varshithvhegde/sharepad.git
cd sharepad
npm install
cp .env.example .env.local
# 填入 Supabase 金鑰
npm run dev

依照順序套用 supabase/migrations/ 裡的 SQL migration——共六個檔案,從初始 schema 到圖片與速率限制。Supabase CLI:supabase db push。或者也可以把每個檔案手動貼到 SQL 編輯器裡。

最少需要的環境變數:

NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=...
SUPABASE_SERVICE_ROLE_KEY=...
NEXT_PUBLIC_SITE_URL=http://localhost:3000

在五個 R2_* 變數都設定好之前,圖片上傳功能會關閉。應用本身在沒有它們的情況下也能正常執行——只是不能貼截圖。

部署到任何能跑 Next.js 的地方都可以。Vercel 是最省事的路徑。正式環境記得把 NEXT_PUBLIC_SITE_URL 設成你的真實網域。

完整設定文件:github.com/Varshithvhegde/sharepad


試試看

線上版: sharepad.in

快速路徑: sharepad.in/quick — 貼上 Markdown,拿到連結

完整建立: sharepad.in/new — 自訂 slug、紙張紋理、字型、到期日、密碼

找回遺失的編輯連結: sharepad.in/recover

{% github Varshithvhegde/sharepad

如果有東西壞掉了,請 開 issue。我會看的。


我一直反覆想到的是,我們其實默默接受了多少摩擦。註冊、驗證電子郵件、建立工作區、設定分享。這一切只是為了筆記。大多數時候,你只是想把一個連結丟給別人,然後繼續過你自己的日子。

SharePad 是我試著讓那個連結值得被打開的嘗試——真正能用的 Markdown、多頁面、不會讓你尷尬的列印檢視、可直接貼上的截圖,以及一個到期日,讓舊筆記不會永遠纏著你。

如果你在這之上做出什麼東西,或是 fork 之後把它帶去我沒想到的地方,我很想看看。歡迎留言或到 dev.to 找我。

如果你曾經因為 Slack 把表格格式弄壞而弄丟會議紀錄——我也是。這就是這個東西存在的原因。


原文出處:https://dev.to/varshithvhegde/i-built-a-notebook-for-sharing-notes-that-doesnt-ask-you-to-sign-up-first-2ldd


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝8   💬4   ❤️2
239
🥈
我愛JS
💬1  
4
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登