Skip to content

fix: make Notie SSR-safe (renderToString works in Node)#54

Closed
branyang02 wants to merge 1 commit into
mainfrom
ssr-clean
Closed

fix: make Notie SSR-safe (renderToString works in Node)#54
branyang02 wants to merge 1 commit into
mainfrom
ssr-clean

Conversation

@branyang02

Copy link
Copy Markdown
Owner

Summary

  • Replace render-time `navigator.platform.includes("Mac")` in `CodeBlock` with a `useState` + `useEffect` pattern. The unconditional access threw `ReferenceError: navigator is not defined` whenever a consumer rendered `` (with an `execute-*` code block) through `react-dom/server.renderToString` under Node.
  • Add a vitest-based SSR regression suite that renders sample markdown — headings, inline + display math, fenced code blocks, blockquotes, and an `execute-python` block — through `renderToString` and asserts the output is non-empty and contains the expected content.

Why

Consumers that want to pre-render notie pages at build time (static-site generation, vite-ssr, Next.js, Astro, etc.) can't currently do so because of the `navigator` access. With this change, `renderToString()` succeeds in pure Node — confirmed by the new test.

I verified the regression: reverting just the `CodeBlock` change while keeping the new test causes test #7 ("renders a markdown containing an execute-* block without throwing") to fail with the exact `navigator.platform` stack trace at `CodeBlock.tsx:187`. With the fix applied all 8 tests pass.

The audit also covered every other `document`/`window`/`navigator` reference in `src/` — they were all already inside `useEffect` callbacks or event handlers, which don't run during SSR. `StaticCodeBlock`'s async shiki upgrade is also safe: its `useState` initial value (escaped `

`) matches between server and client, so hydration doesn't error; the highlighted swap only happens after mount.

Changes

  • `src/components/CodeBlock.tsx`: `navigator.platform` moved into `useEffect`-driven state.
  • `src/tests/ssr.test.tsx`: 8 SSR tests via `renderToString`.
  • `package.json`: add `vitest` devDep and `"test": "vitest run"` script.

Test plan

  • `npm test` — 8/8 pass on this branch
  • Reverted CodeBlock change locally — test fails with the targeted error, proving the regression test catches the issue
  • `npm run build` — succeeds
  • `npm run lint` — clean
  • `npx prettier --check .` — clean (matches CI)

🤖 Generated with Claude Code

The execute-code-block render path called navigator.platform unconditionally
during render, which throws when consumers run renderToString in Node. Move
the platform detection into a state + useEffect pattern so SSR returns the
default (Ctrl) icon and the client switches to (Cmd) after mount.

Adds a vitest-based SSR test that renders sample markdown (math, code,
headings, blockquotes) plus an `execute-python` block through
renderToString, guarding against future regressions of this kind.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Apr 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
notie Ready Ready Preview, Comment Apr 27, 2026 11:43pm

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant