Web performance
Shipping fast static apps on a CDN without breaking SEO
A single page app served straight from a CDN is the cheapest, fastest thing you can deploy — right up until a crawler asks it for HTML and gets an empty div.
/7 min read
Render on the server, hydrate in the browser
The default fix people reach for is prerendering a handful of routes. It works until content becomes dynamic, and then it silently stops working for exactly the pages that matter most.
We render on the server and hydrate on the client. Crawlers get complete HTML with the real text, headings and links in it on first response; users get a full client-side application after hydration. Nothing about the experience depends on JavaScript executing in a crawler.
The test is simple and worth running on every deploy: fetch the page with JavaScript disabled and check the heading, body copy and internal links are present in the response.
Cache headers are the performance work
Most of the speed of a CDN-hosted app comes from getting two cache policies right. Hashed build assets are immutable and cached for a year. HTML is not, and needs a short cache with revalidation so a deploy is visible immediately.
Getting these backwards is the classic failure: either every visitor re-downloads a megabyte of unchanged JavaScript, or a stale HTML document keeps pointing at asset filenames that no longer exist and the app fails to boot for anyone with a warm cache.
Crawl layer basics that are easy to forget
A robots.txt that allows crawling and names the sitemap. A sitemap generated from the same route data the application uses, so it can never drift. One canonical URL per page, self-referencing. Redirects consolidated onto a single host and scheme rather than serving the same content on four addresses.
None of this is clever, and all of it is the difference between a site being indexed properly and being indexed partially. Generating the sitemap from route data rather than maintaining it by hand is the single highest-leverage item on that list.
Keeping the payload honest
The fastest asset is one that is never requested. We use system and self-hosted fonts sparingly, avoid client libraries that duplicate what the platform already does, load images at the size they render, and keep analytics to a single deferred script.
Route-level code splitting matters more than bundle size in aggregate. A visitor landing on one page should not pay for the JavaScript of every other page, and framework-level splitting gives that almost for free if you avoid importing page modules across route boundaries.