Gatsby has long used <Link> prefetching to make navigation feel quick. Modern Chromium browsers now offer two platform features that go further: the Speculation Rules API, which can prerender the next page fully, and cross-document view transitions, which animate between full page loads using CSS only. Both work on a static site with no client-side router changes. This tutorial shows how to add them to a Gatsby 5 site safely.
Browser support is uneven (both are Chromium-first; others ignore them), so treat both as progressive enhancement.
1. Inject a speculation rules script
Speculation rules are JSON in a <script type="speculationrules"> tag. In gatsby-ssr.js, add it through onRenderBody so it lands in the static HTML:
// gatsby-ssr.js
const React = require("react")
const rules = {
prerender: [
{
where: {
and: [
{ href_matches: "/*" },
{ not: { href_matches: "/api/*" } },
{ not: { href_matches: "/account/*" } },
{ not: { selector_matches: "[rel~=nofollow]" } },
],
},
eagerness: "moderate",
},
],
}
exports.onRenderBody = ({ setHeadComponents }) => {
setHeadComponents([
<script
key="speculation-rules"
type="speculationrules"
dangerouslySetInnerHTML={{ __html: JSON.stringify(rules) }}
/>,
])
}
eagerness: "moderate" prerenders when a user hovers a link for a short time (or on pointer down), which keeps wasted work low. Use "conservative" if bandwidth or server cost matters, and avoid "eager" on large sites. If you only want to download the document rather than run it, use a prefetch rule instead of prerender.
2. Exclude pages that should not be prerendered
A prerendered page executes its JavaScript before the user visits it. Exclude anything with side effects: logout routes, client-only authenticated routes, pages that fire a one-time event on load, and anything that mutates state. Keep the exclusion list next to the rules, and review it whenever you add routes.
3. Fix analytics and third-party scripts
The most common problem is double or premature counting. A prerendered page loads your analytics script before the visit. Defer pageview logic until the page is actually activated:
// gatsby-browser.js
function sendPageview() {
window.analytics && window.analytics.page()
}
exports.onClientEntry = () => {
if (document.prerendering) {
document.addEventListener("prerenderingchange", sendPageview, { once: true })
} else {
sendPageview()
}
}
Apply the same document.prerendering check to anything else that should wait for a real visit: A/B test assignment, chat widgets, consent banners, and ad impressions.
4. Add cross-document view transitions
For full-page navigations between same-origin pages, opt in with CSS:
@view-transition {
navigation: auto;
}
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 180ms;
}
@media (prefers-reduced-motion: reduce) {
@view-transition {
navigation: none;
}
}
Note the caveat for Gatsby: Gatsby's <Link> performs client-side navigation using the History API, which is a same-document transition, not a cross-document one. The @view-transition rule applies to real page loads, such as the first hit on a prerendered page, plain <a> links, and navigations from outside the SPA. If you want transitions on <Link> navigations, call document.startViewTransition() from onPreRouteUpdate in gatsby-browser.js, and test it carefully. Keep the animation short and never block navigation on it.
To keep a header stable across pages, give it a name:
.site-header { view-transition-name: site-header; }
Names must be unique per page, so do not assign one to repeated list items without generating distinct values.
5. Verify it works
- In Chrome DevTools, open Application → Background services → Speculative loads to see which rules matched and whether a prerender succeeded or failed (and why).
- Navigate and check
performance.getEntriesByType("navigation")[0].activationStart. A value above zero means the page was activated from a prerender. - Compare field LCP for pages reached through navigation before and after, using your real-user monitoring rather than lab runs alone.
6. Rollout checklist
- Start with
prefetch, then move toprerenderfor your top navigation paths. - Confirm that cookie banners, consent state, and analytics behave correctly under prerendering.
- Serve the HTML with sensible cache headers so prerendered documents are not stale after deploys.
- Respect
prefers-reduced-motion. - Keep a one-line kill switch (an environment flag around the script) so you can remove the rules without a code change to routes.
These two features cost little to add and degrade gracefully, which suits a static site. If you have a Gatsby site that needs performance work or a careful review of third-party scripts, get in touch.