EmpireUI
Get Pro
← Blog7 min read#speculation-rules#prerender#performance

Speculation Rules API: Instant Page Loads with Prerender

The Speculation Rules API lets browsers prerender pages before users click. Here's how to wire it up in React apps for near-instant navigation without a build step.

High-speed light trails on a dark road at night, representing instant page navigation

What the Speculation Rules API Actually Does

Honestly, most performance advice is just rearranging deck chairs. Compress your images, lazy-load that carousel, shave a couple hundred milliseconds. But the Speculation Rules API is a different category of thing. It tells the browser to fully prerender a page — run the JavaScript, resolve the layout, paint everything — before the user ever clicks a link.

The result is navigations that feel instant. Not faster. Instant. Chrome 108+ ships with full prerender support, and as of Chrome 121 the implementation is stable enough to ship in production without worrying about edge cases eating your users alive.

Prefetch and prerender are not the same thing. Prefetch fetches the document into cache. Prerender creates a full hidden browsing context: scripts run, network requests fire, the page is rendered and sitting in memory waiting. When the user clicks, the browser just swaps the context in. The difference in perceived speed is dramatic.

The JSON Syntax You Actually Need to Know

You inject speculation rules via a <script type="speculationrules"> tag containing JSON. That's it. No npm package. No build step. Just a script tag with a JSON payload. Chrome parses it and acts accordingly.

Here's a real example you can drop into any HTML document or React app:

<script type="speculationrules">
{
  "prerender": [
    {
      "where": {
        "href_matches": "/blog/*"
      },
      "eagerness": "moderate"
    }
  ],
  "prefetch": [
    {
      "where": {
        "href_matches": "/docs/*"
      },
      "eagerness": "conservative"
    }
  ]
}
</script>

The eagerness field controls when the browser acts. immediate fires as soon as the rules are parsed. eager triggers on mouse-over. moderate waits for the cursor to hover for ~200ms. conservative only fires on pointer-down. For most sites, moderate is the right default — it avoids wasting bandwidth on pages users glance past while scanning a list.

Injecting Speculation Rules Dynamically in React

Static HTML works fine, but React apps often need dynamic rules — maybe you want to prerender the next article in a series, or prerender the checkout page only after a user adds something to their cart. You can inject speculation rules at runtime by creating a script element and appending it to the document head.

import { useEffect } from 'react';

function useSpeculationRules(rules: object) {
  useEffect(() => {
    if (!HTMLScriptElement.supports?.('speculationrules')) return;

    const script = document.createElement('script');
    script.type = 'speculationrules';
    script.textContent = JSON.stringify(rules);
    document.head.appendChild(script);

    return () => {
      document.head.removeChild(script);
    };
  }, [JSON.stringify(rules)]);
}

// Usage — prerender likely next pages dynamically
export function ArticlePage({ nextSlug }: { nextSlug: string }) {
  useSpeculationRules({
    prerender: [
      {
        urls: [`/blog/${nextSlug}`],
        eagerness: 'moderate',
      },
    ],
  });

  return <article>{/* ... */}</article>;
}

The HTMLScriptElement.supports('speculationrules') check is important. Browsers that don't support the API just ignore the script type, but the check lets you avoid appending useless DOM nodes everywhere. That feature-detect pattern is the same one you'd use before reaching for any newer web platform API.

Eagerness Levels and When to Use Each One

Choosing the wrong eagerness level is the most common mistake teams make. immediate sounds appealing — prerender everything right now! — but on a page with 20 links, that's 20 full page renders running in the background, burning through bandwidth and CPU. Mobile users will notice.

moderate is almost always the right call for content pages. The ~200ms hover delay matches real reading behavior well. Users who are actually about to click tend to pause on a link for at least that long. Users scanning content move fast enough that the timer never fires. It's a surprisingly good signal.

For e-commerce product pages or SaaS onboarding flows where you have a strong prediction about the next step, eager makes sense. You're betting that hovering means intent, and in high-intent flows that bet pays off. For anything behind authentication or personalization (dashboard pages, account settings), stay on conservative or skip prerender entirely — you don't want to burn server resources rendering pages before confirming the user even navigates there.

What about pages that change frequently? If your blog listing updates every few minutes and you prerender it aggressively, users might see stale content for a few seconds after clicking. Not catastrophic, but worth thinking about. The browser does validate the prerendered page against cache headers before activating it.

Caveats, Restrictions, and Things That'll Trip You Up

Prerendering isn't free. The hidden browsing context runs with some restrictions in Chrome. Headers like Sec-Purpose: prefetch;prerender are sent with the initial request, which lets your server detect speculative loads and avoid logging them as real pageviews, skip analytics hits, or return a lighter payload. You should check for this header server-side.

Pages that require user activation can't fully prerender. If your page auto-plays video or fires getUserMedia, that'll be blocked until the page activates (i.e., when the user actually clicks). Same for notifications, certain Web Storage writes, and window.open. These aren't errors — the browser just queues those operations until activation. But it means you need to test your pages in a prerendered context, not just a normal load.

Analytics is the one that trips up almost every team. If Google Analytics or your custom event tracking fires on page load, you'll double-count pageviews — once for the speculative prerender and once for the activation. Chrome fires a prerenderingchange event on document when the page activates. Wire your analytics initialization to that event instead of DOMContentLoaded. If you're using something like WebGL background effects or heavy canvas work on page load, those also run during prerender — which is fine for warming up the render, but be aware of the CPU cost.

Same-origin only. Speculation rules work for navigations within your own origin. Cross-origin prerenders are restricted to prefetch only (as of Chrome 121), and even that requires the destination to opt in via Vary: Sec-Purpose or the No-Vary-Search header. Don't try to prerender third-party pages — it won't work.

Measuring the Impact with Chrome DevTools and CrUX

You can't feel a performance improvement in production until you measure it. Chrome DevTools has a dedicated panel for this. Open DevTools, go to Application > Speculation Rules, and you'll see every rule the browser has parsed, which pages it's decided to prerender, and the status of each prerender. It also shows you why a prerender was abandoned if something went wrong.

For real user data, look at your Core Web Vitals through CrUX (Chrome UX Report) or a RUM tool. The metric you care about is Interaction to Next Paint (INP) on navigation — though LCP on the destination page is often what you'll see drop most visibly. A fully prerendered page can post LCP times of 0-50ms because the paint already happened. That's not a typo.

If you're also working on visual complexity — say, adding parallax scrolling effects or canvas animations to landing pages — speculative prerender becomes even more valuable. Those animations have real startup cost. Prerender pays that cost before the user clicks, so they see the animation already running when the page activates.

Combining Speculation Rules with Next.js and React Router

Next.js App Router already does prefetching via <Link> components, but it's prefetch not prerender — the full page render still happens client-side on navigation. Speculation Rules gives you something different: the browser's native prerender pipeline, which runs before React's hydration logic even matters.

The cleanest integration in Next.js is to add a <Script> component in your root layout with a static speculation rules block targeting your most commonly-visited routes. For dynamic rules based on user behavior, use the useSpeculationRules hook pattern from earlier in this article and drop it into specific page components.

// app/layout.tsx
import Script from 'next/script';

const speculationRules = {
  prerender: [
    {
      where: { href_matches: '/blog/*' },
      eagerness: 'moderate',
    },
  ],
  prefetch: [
    {
      where: { href_matches: '/docs/*' },
      eagerness: 'conservative',
    },
  ],
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Script
          id="speculation-rules"
          type="speculationrules"
          dangerouslySetInnerHTML={{
            __html: JSON.stringify(speculationRules),
          }}
        />
        {children}
      </body>
    </html>
  );
}

The dangerouslySetInnerHTML here isn't actually dangerous — the content is developer-controlled JSON, not user input. It's just how you render a non-standard script type in React without Next.js trying to re-serialize it. React Router v7's new preloading APIs are heading in a similar direction, but for now the script injection approach works across any framework.

Should You Use This Today?

Chrome's market share makes this worth doing for most web apps right now. Non-supporting browsers just ignore the script tag — there's no downside, no polyfill needed, no fallback to wire up. The risk/reward ratio is unusually favorable for a newer web platform feature.

Is it the right tool for every page? No. Pages with heavy server-side personalization, pages behind authentication, and pages that fire analytics in ways you can't easily modify are all candidates for skipping prerender and sticking with conservative prefetch. But for marketing pages, blog posts, documentation, and product listing pages? Ship it.

If you're already investing in visual quality — glassmorphism UI patterns, theme toggle animations, elaborate micro-interactions — speculation rules is the performance foundation that makes all of that feel polished rather than heavy. The visual work doesn't matter if the page takes 3 seconds to appear. Get the navigation speed right first, then layer in the visual complexity.

FAQ

Does the Speculation Rules API work in Firefox and Safari?

Not yet. As of late 2026, full prerender support is Chrome/Edge only (Chromium 108+). Firefox and Safari both ignore the script tag without error, so adding speculation rules is safe to do unconditionally — non-supporting browsers just get the normal navigation experience.

Will my Google Analytics pageviews get double-counted when using prerender?

Yes, if you initialize analytics on DOMContentLoaded without checking for prerendering. Fix it by listening for the prerenderingchange event: document.addEventListener('prerenderingchange', initAnalytics). Also initialize immediately if document.prerendering is already false at script execution time.

How many pages can the browser prerender simultaneously?

Chrome limits concurrent prerenders to avoid resource abuse. In practice it's typically 1-2 active prerenders at once, with queuing for additional candidates. If your rules match 20 pages with immediate eagerness, Chrome will manage the queue — you won't get 20 simultaneous background renders. That said, don't write rules that match hundreds of URLs unnecessarily.

Can I prerender pages that require the user to be logged in?

Technically yes if the prerendered request includes session cookies (it will, since it's same-origin). The risk is server cost — you're rendering authenticated pages speculatively. More importantly, if the session expires between prerender and activation, the activated page may show stale authenticated content. For most apps, conservative prefetch is safer for authenticated routes.

How do I detect in my server code that a request is a speculative load?

Chrome sends Sec-Purpose: prefetch;prerender in the request headers for prerender loads, and Sec-Purpose: prefetch for prefetch loads. Check for this header server-side to skip analytics logging, return lighter payloads, or avoid writing to databases on speculative loads.

Does speculation rules work with client-side routing in SPAs?

Speculation rules apply to full page navigations, not client-side route changes within a SPA. If your React app uses client-side routing (React Router, Next.js App Router) for most navigation, speculation rules only help for the initial load or hard navigations between origins. For in-SPA route transitions, framework-level data prefetching is still your main tool.

Free components in 40 styles
React & Tailwind, copy-paste ready.
Browse →

Read next

Core Web Vitals in 2026: LCP, INP, CLS with Real Next.js FixesPaint Holding: Eliminating Flash of Blank Before NavigationView Transitions API: Page Animations Without a FrameworkNext.js Edge Runtime: When to Use It and Its Limitations