Choosing Between Static and Server Rendering Per Route
Rendering-mode arguments usually happen at the project level and stay unresolved, because the honest answer differs per route. The procedure below resolves it per route in about twenty minutes, produces a written decision, and keeps islands out of the argument entirely β activation is orthogonal, as rendering modes for islands sets out. What follows is the classification work that makes the choice mechanical.
Prerequisites
Diagnostic Steps
Step 1 β Inventory what the route displays
Open the route and write down every value a visitor can see, including values that appear only inside island props. The props matter as much as the visible markup, because a serialised prop is bytes in the document and is cached with it. A typical product route produces a list of fifteen to twenty entries; a listing route produces fewer than you expect, because most of the page is one repeated shape.
Step 2 β Classify each value into one of three buckets
# route-decision.md β kept next to the route, not in a wiki
route: /product/[id]
build-time title, description, images, spec table, breadcrumb
shared-varying price (max staleness: 5 min), stock band (5 min), review count (1 h)
per-visitor greeting, saved flag, loyalty price, recently viewed
decision: incremental, revalidate = 300s
personalised: /_fragment/personal-offer (private, no-store)
reason: price is the tightest shared constraint at 5 minutes;
everything per-visitor is already below the line.
Disagreements surface at this step and they are productive. βIs the stock band per visitor?β is a real question β if warehouse allocation differs by region, the answer is yes and the value moves right, which changes the whole decision.
Step 3 β Draw the personalisation line and route what is below it
Every per-visitor value becomes either a separate fragment request or a fetch performed by an island after activation. Prefer the fragment when the value must be visible without JavaScript, and the island fetch when it is genuinely enhancement. Both keep the document shared, which is the entire objective. The mechanics of the fragment are covered in using Astro server islands for personalised content.
Step 4 β Take the tightest freshness constraint as the window
If price may be five minutes stale and review count may be an hour stale, the window is five minutes. If any shared value tolerates zero staleness, it is not shared-varying β it is per-request, and it belongs below the line with the personalised values.
Step 5 β Write the decision into the route file
A three-line comment above the routeβs mode export, naming the window and the constraint that produced it. This is the artefact that survives; the ticket does not.
Verification
Three checks confirm the decision was implemented rather than merely written down.
The two-session diff. Request the route with two different session cookies and diff the documents. Any difference must be a fragment you deliberately placed below the line. This is the check that catches a leak before a visitor does.
The header check. Request the route and read the cache headers that actually arrive. Framework defaults, host defaults and CDN rules interact, and the header you set is not reliably the header that ships. Confirm the document is public with the window you chose, and that the personalised fragment is private and no-store.
The freshness check. Change a shared value at the source, then poll the route until it updates. The observed lag should approximate the window. If it never updates, revalidation is not wired to anything β a common outcome when the framework option was set but the platform serves from a different cache layer.
Troubleshooting
What if one value on the route needs to be fresh every request?
Move that value below the personalisation line rather than making the whole route dynamic. A live stock indicator, a rate or a queue position can be rendered by a small per-request fragment or fetched by its island after activation, leaving the document itself cacheable. Making an entire route dynamic for one number is the most common and most expensive over-correction in this area.
How do I decide the revalidation window?
Ask what happens if a visitor acts on a value that is N minutes old. If the answer is nothing, the window can be long. If the answer involves a promise you cannot keep β an item that is actually sold out, a price you will not honour β the value does not belong in a cached document at all. The window is a statement about acceptable wrongness, and writing it down that way makes it a product decision rather than a guess.
Should the decision ever be revisited?
Yes, whenever the route gains a value it did not have. Adding a greeting to a cached page is a one-line change that quietly makes the page uncacheable or, worse, leaks one visitor's data to another. A comment in the route file naming the mode and the reason gives the next author the context to notice, which no amount of documentation elsewhere achieves.
Related
- Revalidating Cached Pages Without Rehydrating Islands β what happens to activated islands when the document behind them refreshes.
- Edge Caching and Delivery for Islands β implementing the policy this decision produces.
- Rendering Modes for Islands β the conceptual background to the three buckets.
β Back to Rendering Modes for Islands