Serve www.radix-ui.com from Cloudflare Workers static assets - #911
Open
moritz-petersen wants to merge 2 commits into
Open
moritz-petersen wants to merge 2 commits into
moritz-petersen wants to merge 2 commits into
Conversation
Vercel is no longer a deploy target for this site. The site is a Next.js static export (`output: "export"`) served by Cloudflare Workers static assets in the WorkOS Marketing Sites account (wrangler.jsonc, Worker `radix-website`). Everything except three pieces was already static (generateStaticParams everywhere, client-side MiniSearch, static og images, no next/image): - The /api/markdown route and proxy.ts converted rendered pages to Markdown at request time (rehype-remark + oxfmt, a native binary). The conversion moves to scripts/html-to-markdown.ts and scripts/build-markdown.ts writes a `<page>.md` twin next to every exported page after `next build`, so `/…/dialog.md` is a static file. worker/index.ts, scoped with `assets.run_worker_first` to the former proxy matcher, keeps answering `Accept: text/markdown` with the twin. - /colors/custom read cookies() and searchParams on the server. The saved and shared palette are now restored on the client after hydration; the appearance cookie that only informed SSR is gone. - next.config redirects()/rewrites() are ignored by an export: redirects move to public/_redirects (same status codes), headers to public/_headers (immutable /_next/static, nosniff on .md, noindex on workers.dev hosts). Build-time SITE_ENV (production only for the `main` branch on Workers Builds; never for `next dev` or NEXT_PUBLIC_SITE_ENV=preview) gates Google Analytics and adds robots noindex to previews, so a workers.dev preview can't report or be indexed as the live site. metadataBase is the canonical www host so og:image URLs resolve without a redirect. scripts/check-export.ts fails the build if the export is missing the files the hosting relies on. README documents build, Workers Builds settings, the apex/icons redirect rules and the cutover. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
@moritz-petersen is attempting to deploy a commit to the WorkOS Team on Vercel. A member of the Team first needs to authorize it. |
moritz-petersen
marked this pull request as draft
September 9, 2026 19:13
The former redirects() from next.config.js were ported to public/_redirects, but on the Cloudflare edge overlapping dynamic rules did not follow file order: /docs/colors/getting-started/usage took the broader /docs/colors/* rule and landed on /colors/docs/getting-started/usage instead of /colors/docs/overview/usage (same for the aspect-ratio and label utilities). wrangler dev honored the order, so this only showed on the deployed Worker. worker/redirects.ts now holds the rules with first-match-wins semantics and a matcher for splats and placeholders; worker/index.ts applies them before the Markdown negotiation, and run_worker_first covers every redirect source (REDIRECT_PATHS, asserted by the test). /docs/colors gets its own rule so it still reaches the installation page, as Vercel did via /colors/docs/. check-export now fails if a _redirects file reappears, since the platform would evaluate it ahead of the Worker. Also: the pre-hydration theme switcher on /colors/custom stays a controlled component (value "light"), removing Radix's controlled/uncontrolled warning on every page load. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
moritz-petersen
marked this pull request as ready for review
September 16, 2026 19:25
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.
Part of the Vercel Migration — UI-1950 (and UI-1919 for
icons.radix-ui.com, which becomes a zone redirect rule, see below).Summary
radix-website, already created and serving this branch atradix-website.workos-sites.workers.dev).next.config.jssetsoutput: "export";wrangler.jsoncrunspnpm buildviabuild.command,not_found_handling: "404-page",html_handling: "drop-trailing-slash"(NexttrailingSlash: false, as on Vercel),observability,workers_dev,preview_urls, no routes (the custom domain is attached at cutover).generateStaticParams+dynamicParams = false; docs search is client-side (MiniSearch overpublic/search-index.json); og images are static files; there is nonext/image, ISR,getServerSidePropsor server actions. Three server-side pieces had to move:/api/markdown/[...path]route +proxy.ts(Vercel served/<page>.mdandAccept: text/markdownby converting the rendered HTML at request time with rehype-remark + oxfmt). The conversion moved verbatim toscripts/html-to-markdown.ts;scripts/build-markdown.tsruns it afternext buildand writes a<page>.mdtwin next to every exported page (150 files)./…/dialog.mdis a plain static asset; the output is byte-identical to production's (checked for three pages).Accept: text/markdownnegotiation is kept byworker/index.ts: for the docs and blog paths it serves the.mdtwin when asked for Markdown (addingVary: Accept) and otherwise passes through toenv.ASSETS, so_headers,html_handlingandnot_found_handlingstill apply./colors/customreadcookies()andsearchParamsin a server component. Restore now happens on the client right after hydration; the URL sync waits for that restore so shared params aren't wiped first. The appearance cookie (only used by the SSR render) is removed. Degradation: returning visitors and shared links see the default palette for one paint before their palette applies. Verified on the deployed Worker:?accent-light=…&accent-dark=…&gray-…params apply after hydration.worker/redirects.ts, notpublic/_redirects. The first version of this PR used_redirects; on the deployed Worker the platform did not honor file order for overlapping dynamic rules, so/docs/colors/getting-started/usagetook the broader/docs/colors/*rule and landed on/colors/docs/getting-started/usageinstead of/colors/docs/overview/usage(same for the aspect-ratio and label utility paths).wrangler devhonored the order, so this only showed up on the edge. The rules are now Worker code with first-match-wins semantics,run_worker_firstcovers every redirect source (REDIRECT_PATHS, asserted by the test), andcheck-exportfails the build if a_redirectsfile reappears, since the platform would evaluate it ahead of the Worker. 308/307 match what Next emitted; query strings are preserved. Headers →public/_headers: immutable/_next/static/*,nosniffon/*.md,X-Robots-Tag: noindexon*.workos-sites.workers.dev.pnpm test(Node's test runner viatsx) pins every legacy redirect to production's answer, query-string preservation, trailing-slash tolerance, that no real page is caught, and thatwrangler.jsonccovers every redirect source.SITE_ENVis inlined bynext.config.js— production only when notnext dev,NEXT_PUBLIC_SITE_ENV !== "preview", andWORKERS_CI_BRANCHis unset ormain. GA (utils/analytics.ts) and arobots: noindex, nofollowmeta (utils/metadata.ts) hang off it; fails closed.metadataBase→https://www.radix-ui.com; the blog index og image becomes relative so it resolves the same way./colors/customstays a controlled component, removing Radix's controlled/uncontrolled console warning on every load.wranglerdev dependency;pnpm preview/pnpm deploy/pnpm test;next startremoved (doesn't apply to an export);.wrangler/ignored. README has a Hosting section (build steps, redirects, Workers Builds settings, redirect rules, cutover checklist).Decisions that deviate from the shared conventions (and why)
image-loader.ts/NEXT_PUBLIC_IMAGE_TRANSFORMS: zeronext/imageusages, so no Image Transformations needed on the zone..nvmrc,engines), pnpm as the repo already uses; lockfile clean..mdcache policy: platform default (max-age=0, must-revalidate+ ETag) instead of the old 7-daymax-age, so docs updates show up right after a deploy..htmlURLs are answered by the platform with a 307 (Vercel: 308 / 404);/case-studiesand/docs/colors/getting-startedland directly on their targets instead of via a…/hop; an unknown docs path withAccept: text/markdowngets the HTML 404 page (Vercel: JSON).Verified on the deployed Worker (
radix-website.workos-sites.workers.dev, production-mode build)A 48-case probe (every legacy redirect incl. the overlapping ones, trailing slashes,
.htmlURLs, 404s,.mdtwins,Accept: text/markdown/text/x-markdown/HEAD, search index, colors/custom with params) run against production and the Worker differs only in the items listed above. Also:_next/static/*immutable,.mdserved astext/markdown+nosniff,Vary: Accepton negotiated responses,og:imageabsolute, gtag present,X-Robots-Tag: noindexpresent on workers.dev.pnpm test(28 tests),pnpm typecheck,pnpm lint,pnpm format:checkclean.Test plan
pnpm install && pnpm buildsucceeds;out/contains404.html,_headers,.mdtwins (1919 files, 115 MB, no file over 25 MB).pnpm test;wrangler devand the deployed Worker match production on redirects, trailing slashes, 404s, Markdown negotiation./colors/custom?accent-light=…&accent-dark=…restores the palette after hydration.npx wrangler deploy, non-productionnpx wrangler versions upload, no build variables; Node from.nvmrc) and letmaindeploy.www.radix-ui.com, the two Redirect Rules below with placeholder records, delete Vercel projectsradix-websiteandradix-icons.Redirect Rules to create in the
radix-ui.comzone(http.host eq "radix-ui.com")concat("https://www.radix-ui.com", http.request.uri.path), preserve query string, 308(http.host eq "icons.radix-ui.com")https://www.radix-ui.com/icons, preserve query string, 308Both need a proxied placeholder DNS record (e.g.
AAAA 100::). Today Vercel answers exactly these two redirects (308, query preserved;icons.radix-ui.com/*→radix-ui.com/icons).🤖 Generated with Claude Code