Skip to content

Faster first loads: static public pages, data with the page, Ask on demand - #1

Merged
Sky-walkerX merged 3 commits into
mainfrom
perf/first-load
Oct 2, 2026
Merged

Sky-walkerX merged 3 commits into
mainfrom
perf/first-load

Conversation

@Sky-walkerX

Copy link
Copy Markdown
Owner

Measured on production (functions now in Mumbai, next to the database):

  • Every page downloaded the Ask panel's markdown, KaTeX and highlight.js, even the sign-in form.
  • The root layout read the session, so no page could be served static. A visitor's first load waited about 1.3 s on a cold function.
  • Signed-in pages rendered empty, then made 5 or 6 API calls after hydrating.

Changes:

  • Ask loads when it first opens. The panel is a dynamic import.
  • Public pages are static. Routes are split into (public) (landing, sign-in, sign-up, share, consent) and (app) (the notebook). Only (app) reads the session. A rewrite in next.config.ts sends / to the contents when the session cookie is present, so the landing page needs no function either. Sign-in now ends in a full page load into the notebook.
  • Data comes with the page. The server runs the API routes' loaders while rendering and hands the results to React Query. The routes and the prefetch share the same lib functions and query keys. Today and due revisions use the browser's time zone from the lk-tz cookie.

Checked locally under next start:

  • tsc, lint, 377 tests and the build all pass. /, /login and /signup build as static.
  • Browser smoke test across every page, plus checks for the rewrite, sign-in with ?next=, sign-out and a stale cookie.
  • Every end-to-end script passes: pages, passage, Ask, OAuth, rate limit and the sweep.
  • Every signed-in page except contents makes zero API calls on first load. Contents makes one: the recent-notes refresh it does by design.

The panel imports the markdown renderer, KaTeX and highlight.js, and it sat in the providers every page loads, so even the sign-in form downloaded them. It now loads the first time Ask opens.
The root layout read the session, which made every page render per request: a visitor's first load waited on a function, cold start included. The routes now sit in two groups. (public) holds the landing page, sign-in, sign-up, shared pages and the consent screen, and reads no session, so the landing page, sign-in and sign-up are prerendered. (app) holds the notebook, and its layout reads the session for the spine.

/ is the contents when signed in and the landing page otherwise. A rewrite in next.config.ts decides on the session cookie before any page runs, so that costs no function either; a stale cookie reaches the contents page and is sent to sign-in. The chromeless route list is gone: the notebook's frame only exists in (app).

On the server, React Query now gets a new client per render instead of one shared module-level client.
Pages rendered empty, hydrated, then fetched their data: the spine's four lists and the page's own, each its own request and function after the JavaScript had run. The server now runs the same loaders while rendering and hands the results to React Query (prefetch and HydrationBoundary), so a page arrives with its data and the hooks find it already cached. The notebook layout sends the spine's lists once per load; each page sends its own.

The routes' data logic moved into lib functions that the routes and the prefetch both call, and the query keys they share live in lib/query-keys.ts, so the two can't drift. Today and due revisions are keyed by the user's local date: the browser keeps its time zone in the lk-tz cookie and the server works out the same day (lib/zone.ts). Without the cookie, the browser fetches those two as before.

Sign-in now ends in a full page load. The router on the sign-in page learned / while signed out, and / is a different page once the session cookie is set.
@vercel

vercel Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
lockin Ready Ready Preview Oct 2, 2026 7:37pm UTC
plan-wise Ready Ready Preview Oct 2, 2026 7:37pm UTC

@Sky-walkerX
Sky-walkerX merged commit b9d4337 into main Oct 2, 2026
3 checks passed
@Sky-walkerX
Sky-walkerX deleted the perf/first-load branch October 2, 2026 19:39

This branch was successfully deployed

1 active deployment
Preview — 40e0705d Deployed Oct 2, 2026 by vercel[bot]
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