Release: serve /f from a route handler (13 KB, one request) - #80
Merged
Merged
Conversation
* Compile the hosted form's stylesheet into an inlinable module A route handler cannot import a stylesheet — nothing processes it — so the same forms.css entry the App Router used is compiled at build time and checked in. The entry is unchanged, not a scoped subset: Tailwind v4 does not tree-shake per entry, so narrowing the source globs would fork the utility surface for a customer-facing page, and a class used only in the form would render unstyled. 64 KB minified, 11.5 KB over the wire, and no fork. Checked-in build output drifts, so a test recompiles and compares. Its control asserts the stylesheet is non-empty, because otherwise "matches" and "both empty" are the same green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Add the hosted form's document shell <html>, <head> and the inlined stylesheet for a surface with no layout to provide them. noindex is stated as a meta tag here and as a header in the route, because the metadata API is no longer there to emit either. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Serve /f from a route handler instead of an App Router page Closes #56. The logic is unchanged — loadForm, the Hindsight variant, the flash cookie, the embed context, prefill, steps — and only the last step differs, where the tree is rendered to a string rather than handed to the framework. The published schema is still the only source, so the hosted form and the MCP tool keep deriving from one FormSchemaDocument. The reason is arithmetic. An App Router route ships react-dom plus the router runtime whether or not the page uses them, and this page uses neither: no Client Component beneath it, no useState, no event handler. That runtime was 134 KB of the 150 KB the form transferred across 9 requests, on traffic the customer paid for, to render what is often four inputs. Measured after: 13 KB gzipped, one request, zero external scripts, one 275-byte inline theme script. Verified that it still submits with no JavaScript by posting with curl, which cannot execute a script even if one existed — the row stored, stamped unverified. react-dom/server is imported at call time because Next refuses a static import of it under app/; the check keeps it out of client bundles and does not distinguish a route handler, which has no client bundle at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Correct the forms layout comment: it no longer wraps the form Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two commits. No migration required — this release adds no
drizzle/*.sql, so it can merge directly.What ships
/f/{formId}(#56) moves from an App Router page to a route handler.<script src>The hosted form has no Client Component beneath it and submits with scripting switched off, but an App Router route ships react-dom plus the router runtime regardless, with no per-route opt-out. That was 134 KB of the 150 KB — on traffic the customer paid for, to render what is often four inputs.
The logic is unchanged and
src/lib/render/*never moved, so the hosted form and the MCP tool still derive from oneFormSchemaDocument.The stylesheet is compiled at build time by
scripts/build-form-css.mtsfrom the sameforms.cssentry — not a scoped subset, so no class can go silently unstyled — and inlined, because a form on someone else's page is judged on first paint. A test recompiles and compares so the checked-in output cannot drift.Verified before merge
npm run verifyexits 0; CI green on #78 and #79. Against a real server on seeded data: both themes screenshotted, the 404 / no-schema /?ef_embed=inlinebranches, thenoindexandno-referrerheaders, and a submission posted withcurl— which cannot execute a script even if one existed — which stored and stampedunverified.Risk
Lower than the last release: no schema change, no new secrets, one route's rendering path. The rollback is reverting #78.
thanks/page.tsxis untouched and still carries the runtime; it is seen once, after the lead is captured.🤖 Generated with Claude Code