🍡 mochi

SSR framework for Svelte 5 + Bun with islands-based selective hydration

On this page

Speculation Rules

Since: 0.10.0 (not released yet): The speculationRules option is new in 0.10.0.

The Speculation Rules API lets the browser prefetch or prerender same-site URLs ahead of a navigation, so the next page loads instantly. Pass a typed speculationRules object to Mochi.serve() and Mochi injects it as a <script type="speculationrules"> tag in every page’s <head>.

import { Mochi } from 'mochi-framework';
import type { SpeculationRules } from 'mochi-framework';

const speculationRules: SpeculationRules = {
  prefetch: [
    {
      where: {
        and: [{ href_matches: '/*' }, { not: { href_matches: '/api/*' } }, { not: { selector_matches: '[rel~=nofollow]' } }],
      },
      eagerness: 'moderate',
    },
  ],
  prerender: [{ where: { href_matches: '/' }, eagerness: 'moderate' }],
};

await Mochi.serve({ speculationRules, routes });

Both prefetch and prerender are optional. An omitted option — or an object whose arrays are both empty — injects nothing.

Rules are either document rules (where matches links on the page) or list rules (urls names explicit targets):

const listRules: SpeculationRules = {
  prefetch: [{ urls: ['/pricing', '/docs/'] }],
};

href_matches speaks the same URL Pattern syntax as the router, so a route pattern can be used verbatim: /blog/:slug matches one segment, while * crosses them. Match the URLs your server actually serves — with trailingSlash: 'always', that means /about/, not /about.

An important note about prerendering

Prerendering fully loads a page in a hidden tab — it is far more expensive than prefetching, so keep it to a few high-likelihood destinations and prefer eagerness: 'moderate'. Exclude URLs with side effects (sign-out, add-to-cart, one-time-password flows), and add not: { selector_matches: '[rel~=nofollow]' } and [target=_blank] guards so flagged links are left alone.

That hidden tab also runs the page’s scripts, so an analytics snippet reports a pageview for a visit that may never happen. Gate the initialisation on activation — wherever the snippet lives, usually your HTML shell:

if (document.prerendering) {
  document.addEventListener('prerenderingchange', initAnalytics, { once: true });
} else {
  initAnalytics();
}

Google Analytics already defers itself this way; most other vendors do not. For code that runs after load, performance.getEntriesByType('navigation')[0]?.activationStart > 0 tells you the page was prerendered rather than visited directly.