Web Design and Engineering

Website architecture in 2026: SPA homepage, static for the rest

Most marketing sites overengineer every page. We treat the homepage like an app and ship the other 95% as static docs, splitting the build target by intent.

June 11, 20267 min read
watercolor wireframe sketches of website layouts

A SaaS marketing site is two products in one disguise. The homepage carries the load of conversion. The rest carries the load of trust. Most teams build them with the same architecture, then wonder why the homepage feels slow and the blog feels overengineered.

Our split: the homepage is built like a single-page app, with state, interaction, animation budget. Everything else (about, services, case studies, blog, contact, manifesto, stack pages) is shipped like a static document. Different build targets, different code paths, different performance budgets.

This is not a clever framework decision. It is a reading of where attention and intent actually sit on a SaaS site.

The two defaults most teams pick

The first default is "everything is a SPA". Pick Next.js, hydrate every page, give the blog the same client-side router as the dashboard. The homepage feels fine. The blog has a 400ms INP and an LCP that depends on how fast your CDN can serve a JS bundle. Crawlers wait for hydration. The team adds incremental complexity to fix what was a structural decision.

The second default is "everything is static". Pick Astro or Hugo, pre-render the homepage, the blog, the case studies, the contact page. The blog is fast. The homepage looks dead. The animations the founder asked for are now a fight with the framework, not a feature of it. The team adds incremental complexity to fix what was a structural decision.

Both defaults treat the homepage and the doc pages as the same product. They are not.

The homepage is an app. The rest is a doc.

The homepage has a specific job: take a visitor who arrived from a Google search, an X link, a podcast mention, an AI engine citation, and move them toward a contact. The decision happens in 8 to 15 seconds. The design has to do work in that window: animated hero, scroll-triggered reveals, an interactive demo, a state-aware CTA that knows whether the visitor has been here before.

None of that is theatrics. It is the conversion surface earning its budget.

The case study at /en/projects/kibank does a different job. It exists for the visitor who already saw the homepage, clicked into a case study, and now wants three pieces of information: what was the problem, what did we ship, what changed. That visitor wants the text to load instantly, the images to not jump, and the page to scroll without 60fps animations interrupting their reading. The case study is a document.

The blog post you are reading right now is also a document. It exists to be cited by ChatGPT, indexed by Google, and read by a product owner over coffee. Every gram of JavaScript on this page is a tax on that job.

How the split is built in Next.js 16

The implementation rides on three primitives that App Router gives you for free, if you set them up deliberately:

  1. Static by default, dynamic by opt-in. Doc pages export no dynamic-rendering signal. No cookies(), no headers(), no searchParams in the render path. They get pre-rendered at build time and served from a CDN. TTFB drops below 100ms on Vercel's edge.
  2. Suspense as the boundary. The homepage uses Partial Prerendering, stable in Next.js 16: a static shell (nav, hero copy, footer) renders instantly, then the dynamic parts (live testimonial counter, A/B-tested CTA variant, signed-in state) stream in behind Suspense. The shell is on the CDN. The dynamic parts are on the runtime.
  3. No client components on doc pages. Case studies and blog posts use zero "use client". The only JavaScript that reaches the browser is the global theme toggle and the language switcher. Both are isolated in the header. The body of the document carries no hydration cost.

The homepage is allowed to import GSAP, Lenis smooth scroll, and a hero animation that runs on every paint. The doc pages cannot. That constraint is enforced at the routing layer: anything under app/[lang]/(public)/(docs)/ goes through a layout that does not load the animation runtime.

Why this saves you specific numbers

The doc pages ship with an LCP of around 0.8s on a fast 4G connection, an INP under 80ms (because there is almost nothing to interact with), and a TTFB at the CDN floor. The 2026 Core Web Vitals thresholds (LCP under 2.5s, INP under 200ms, TTFB under 800ms) are not a stretch goal here, they are a floor we sit well below. We covered the broader Vitals work in Core Web Vitals on Next.js in 2026.

The homepage carries more. LCP target is 1.5s, INP target is 150ms, and the heavier animation budget is allowed because the shell renders before any of it loads. The page feels alive but the first useful paint does not wait on the GSAP timeline.

For SaaS marketing sites the competitive LCP benchmark has tightened from 2.5s to under 2.0s in 2026. Splitting the architecture is one of the few moves that gets you there without touching content.

Where the doc pages link back to the app

The trap with this split is treating the doc pages as second-class. They are not. They are the citation surface for AI engines, the search-index surface for Google, the reading surface for buyers who do not convert on the first visit. They earn long-tail traffic that the homepage cannot, and they feed the homepage with internal links.

Authority flows from the doc pages to the homepage if you link from inside the docs back to the conversion surface. Without that linking pattern, a doc that earns 400 backlinks per year contributes zero internal link equity to the main page. We use a footer block (rendered by the template, not by article HTML) that links from every doc back to a "tell us what you are building" surface.

The trade-offs we accept

This split is not free.

  • The design system has to be CSS-only. If your component library ships JavaScript-heavy primitives (a dropdown that requires React state to open, a date picker tied to a runtime), they cannot be used on doc pages without breaking the no-hydration rule. We removed Tailwind for the same reason: the tokens are CSS variables, the components are CSS classes, the JS budget is zero by default. See Why we removed Tailwind from production.
  • Two render paths means two bug surfaces. An animation that works on the homepage does not necessarily work on a doc page. The team has to remember which side of the line they are on.
  • Suspense boundaries leak. If a server component fetches data without a Suspense wrapper, the static shell will not render until the data arrives. Partial Prerendering only works if every dynamic fetch is bounded.
  • Search and filter UI on doc pages is hard. A blog index with live search needs a client component. We accept this exception: search is a client component, but it lives in its own island and does not hydrate the article body.

When not to do this split

If your site is a single-page marketing brochure with no blog, no case studies, no documentation, do not split it. The cost of maintaining two render paths is higher than the gain.

If your site is the SaaS app itself (everything behind login), do not split it. The whole site is an app. Static rendering for a logged-in dashboard is a different problem with a different solution, and the edge vs Node runtime call matters more there.

The split earns its keep when you have one or two high-stakes conversion pages (homepage, key landing pages, pricing) and a long tail of content pages that exist to inform, rank, and be cited. That shape is the modal SaaS marketing site in 2026.

The shorter version

One product, two render targets, one routing convention that enforces the line. The homepage gets to be an app because it earns it. The rest of the site stays a document because that is what the visitor actually needs, and that is what Google and the AI engines actually reward.

Sources

Photo by Hal Gatewood on Unsplash

Frequently asked questions

How does this split differ from a standard Next.js setup where every page goes through the App Router?
Standard Next.js routes everything through the same rendering pipeline. The split here is enforced at the layout level: a separate route group for doc pages strips client components, animation runtime, and any dynamic API access. The framework is the same, but the constraints inside each group are different. The line is visible in the directory tree, not in a build config.
How do you keep the design system consistent when half the site has no JavaScript?
The design system has to be CSS-only by design. Tokens are CSS variables, components are CSS classes, no JavaScript-required primitives. The same Button renders identically on the homepage (where the surrounding page is hydrated) and on a blog post (where it is not). If a primitive needs JavaScript to function (a dropdown, a date picker, a combobox), it lives in its own client island and is used only on the app side of the line.
Does this split work on Astro or Remix instead of Next.js?
Yes, with different mechanics. Astro is built around an islands model and gets you the doc side almost for free; the homepage becomes a single hydrated island. Remix can do the same with its loader pattern and selective client routes. The principle (one product, two render targets, a routing convention that enforces the line) is framework-agnostic. Only the syntax of the boundary changes.

Studio

Start a project.

One partner for the digital product you need to build. Faster delivery, modern tech, lower costs. One team, one invoice.