CategoryCode and architecture
JavaScript framework or static site: what the page does
A framework isn’t a decision about development speed. It is a decision about how much state the page has to carry in the browser. A page that only presents content pays the framework price and never uses what the framework delivers. Decide by the state the page needs, not by the tool you prefer.
What the page has to do beyond presenting content
Presenting content is HTML and CSS. The browser paints the text and the images, and the job ends there. Nothing on the page has to stick around after a click.
State is everything else. A filter that keeps the selection, a cart that remembers an item, a form that validates while you type, a list that updates without a reload. State means the page knows something, remembers it and changes what it shows without a new document.
One simple question separates the two paths. When the visitor clicks, does the page have to decide something on the device, or does it only have to load another document?
When the answer is another document, the browser already knows how to do that. It has done it since before any framework existed.
What the visitor downloads on each path
On a static site, the visitor downloads the document, the stylesheet and the fonts. The JavaScript that ships is the part that genuinely needs the device, such as a search box or a theme switch.
On a framework page, the download includes the framework runtime and the component code for the whole route. Google’s Rendering on the Web page describes the reason. For the client scripts to take over where the server stopped, most server-rendered solutions serialize the data the interface was built from as script tags.
The document then carries the HTML, the source data and a complete copy of the interface. Google’s own wording for that is one app for the price of two.
Google’s page on Core Web Vitals defines where this weight lands. Largest Contentful Paint measures loading and should occur within 2.5 seconds. Interaction to Next Paint measures interactivity and should stay at 200 milliseconds or less. Cumulative Layout Shift measures visual stability and should stay at 0.1 or less. Google reads all three at the 75th percentile of page loads, split between mobile and desktop.
The same page records that Interaction to Next Paint replaced First Input Delay as a stable Core Web Vital in 2024. The metric set moves, so the number you optimize today has a documented retirement path.
Where the response-time limits disagree with the metric
Nielsen Norman Group sets three response-time limits. A delay of 0.1 second reads as instant, 1 second keeps the flow of thought, and 10 seconds keeps attention on the task.
A second NN/g article repeats the same three limits and adds the outcome. A 10-second delay often makes users leave the site immediately.
A third article turns the limits into interface rules. Any action over about 1 second needs a progress indicator, looped between 2 and 10 seconds and percent-done above 10 seconds.
Those limits describe perceived delay per interaction. The Core Web Vitals describe field data per page load, at the 75th percentile.
The two references disagree on the scale. Interaction to Next Paint at 200 milliseconds is twice the NN/g limit for instant feedback, and half of its 1-second flow limit (Core Web Vitals, NN/g).
Largest Contentful Paint at 2.5 seconds sits above the 1-second flow limit and below the 10-second attention limit. A page can pass the metric and still feel slow (Core Web Vitals, NN/g).
They also disagree on what a pass means. Google reads one number across all loads at the 75th percentile, so a quarter of visits can be worse. NN/g describes the single delay the user notices.
The third gap is feedback. NN/g tolerates a wait when the interface shows progress. No Core Web Vital scores that signal, and Cumulative Layout Shift has no NN/g counterpart, because it measures stability and not time.
Look at both. The metric says how the page behaves in the field. The limits say what the visitor tolerates while waiting.
What the server does on each path
A static site does its work once, at build time. Google describes static rendering as producing a separate HTML file for each URL ahead of time. The host then returns a file, and a CDN can serve it from an edge cache.
The same page lists the outcome. Static rendering offers a fast First Contentful Paint and a lower Total Blocking Time and Interaction to Next Paint, as long as the page limits client-side JavaScript. It also gets a consistently fast Time to First Byte, because the HTML exists before the request arrives.
A framework page with server rendering does that work on every navigation. The server renders the interface to HTML, embeds the JavaScript and the data, and the client renders it again. Google notes that many server-side rendering solutions delay Time to First Byte or send the data twice, once in the markup and once in the script.
That is the price of personalization. A page that assembles itself per visitor needs a server that assembles it.
What happens on the next navigation
On a static site, the next click is a new document. The browser parses the HTML and paints. Google notes that the visitor is less likely to wait for CPU-bound JavaScript before using the site.
The cost is reloading the page. In-page state resets, and an animation or an open panel is gone unless the site stores it.
On a single-page app, the router intercepts the click, fetches the data and renders in place. State survives, and after the first load the transition can feel immediate.
The first load is what paid for it. It also has a failure mode that shows up in the metrics. Google states that a server-rendered page can look loaded and interactive while it can’t respond to input, until the scripts execute and the handlers attach. The same page says that on mobile this can take minutes.
A visitor who clicks inside that window gets nothing. The page looked finished.
When the framework pays for itself
The framework is the right call when the page carries state that the device owns. Four cases make that clearly true.
- An authenticated dashboard, where the screen composes data from several endpoints and the user filters, sorts and edits without leaving.
- Real-time editing or collaboration, where a remote change has to appear without a reload.
- A flow with many chained interactions, where each step depends on the previous answer and validation happens as you type.
- A product where one state drives many views, such as a builder or an editor with a preview pane.
Client-side routing also pays when the app has dozens of screens and moving between them is the product. Each navigation then costs a data fetch instead of a document.
These cases are the exception, though, and not the default. A page that only presents content has nothing to gain from the framework.
The middle path: static site with interaction islands
Most comparisons stop at two options. The third one ships static HTML and hydrates only the parts that need it.
Astro’s documentation defines an island as an enhanced UI component on an otherwise static page of HTML. The same page states that most JavaScript-based frameworks hydrate and render the whole site as one large application.
Astro inverts the default. The docs state that the framework hydrates exactly what is marked and leaves the rest as static HTML. A component becomes interactive through a client directive, and the directive also sets the timing.
- client:load hydrates the component as soon as the page loads.
- client:idle waits until the browser is idle.
- client:visible waits until the component enters the viewport, and a component the visitor never sees never loads.
For a content site, that covers the real cases. A search box, a pricing toggle, a theme switch and a newsletter form each ship as a small bundle, and the article around them stays HTML.
There is a real cost here. You now make a per-component decision about where the static page ends and the interactive part begins. Get it wrong in one direction and you ship JavaScript nobody needs. Get it wrong in the other and a control stays dead until the bundle arrives.
Astro’s own documentation claims a site can load 40 percent faster with 90 percent less JavaScript than the same site built with the most popular React web framework. The same page markets the default as zero JS. Both numbers come from the vendor, on the vendor page, and neither is a neutral measurement.
How to decide, by the state the page carries
Answer these before you pick a tool.
- List every piece of state that must survive a click. Zero items points to a static site.
- Count the interactions that chain into each other. One step is a widget, five dependent steps are an app.
- Ask who owns the data at each step, the server or the device. Server ownership favors documents.
- Count the URLs. A handful of pages with deep content favors static, and dozens of screens with shared state favors the framework.
- Ask what the visitor does on a second visit. Reading again is a document, and returning to a working session is state.
- Check the team. A framework you can’t operate is a worse trade than HTML you can.
What decides is the state, not the tool. The same team can pick different answers for the marketing site and for the product, and both answers can be right.
Test before you settle it
- Measure the JavaScript bytes per route, not the bundle size in the repository.
- Read Interaction to Next Paint at the 75th percentile, on a throttled mobile profile.
- Load the page with JavaScript disabled. Google offers this as the test between static rendering and prerendering, because a prerendered page looks inert without scripts.
- Click through the next navigation on a slow connection, and note whether the page answers or only looks ready.
- Count the routes that render on the client, and check that each one has a reason.
- Recheck after a quarter. The framework version and the requirement both move.
The answer comes down to the state the page carries, the bytes the visitor pays for it, and the point where a document would have been enough.


