Introduction

Sparke turns an ordinary multi-page site into an instant SPA – with one script tag, no build step, and no framework.

Sparke turns a standard multi-page app into an instantly snappy SPA. It aggressively preloads content while the browser is idle, then atomically swaps pages in from memory when you click a link – no build step, no config, no framework.

It's pure progressive enhancement, so it fails safely: if JavaScript is unavailable, the browser just behaves as a normal MPA.

One script tag. No package, no bundler. Add it, and your existing site is instant.

The one-liner

Drop a single tag into your <head>. Use defer so it runs after the document parses.

Self-hosted (recommended)html
<script src="/js/sparke.min.js" defer></script>
Or via CDNhtml
<script
  src="https://cdn.jsdelivr.net/gh/benshawuk/sparke@1/sparke.min.js"
  defer
></script>

Sparke works with any server-rendered multi-page site - plain HTML, Astro, or HTML enhanced with Alpine, or HTMX etc.
Just use defer and put it in the <head>.

It's not for client-rendered SPA frameworks like React, Vue, Svelte or Solid - those already own rendering and routing, which is exactly the job Sparke does for server-rendered HTML. Astro is a perfect fit, though: every route it ships is a real HTML page.

Astro islands work automatically. Sparke auto-detects an Astro site per navigation - an <astro-island> in the current or incoming page, or the Astro generator meta tag - with no config, and on each swap runs the incoming page's not-yet-seen scripts so swapped-in <astro-island> elements hydrate. client:load, client:visible and client:only behave as on a fresh page load, including across back/forward (these are covered by the test suite); client:idle and client:media hydrate through the same mechanism. Scripts are deduped per session, so nothing runs twice, and the behaviour is completely inert on non-Astro sites. Pin it with data-astro="on" or data-astro="off" on the Sparke <script> tag if you ever need to override detection.

Sparke is a navigation enhancer, not Astro's <ClientRouter />: it does not emit Astro's astro:page-load / astro:after-swap lifecycle events and does not implement transition:persist. Reach for <ClientRouter /> if you depend on those.

Livewire works automatically too. Run your Livewire v3 app as a multi-page app (leave wire:navigate off, or keep it - see below) and Sparke handles navigation. Swapped-in Livewire components hydrate on their own - Alpine's observer boots them, so there is no extra config and no client runtime to wire up - and Sparke tears down the components a swap removes so nothing leaks. Detection is automatic (Livewire's server-rendered wire: markers); pin it with data-livewire="on" or data-livewire="off" if you need to.

In Livewire mode Sparke owns every navigation. It intercepts wire:navigate links too - its preloaded in-memory swap is faster than Livewire's server render, so a stray wire:navigate can't quietly reintroduce the slow path. Hand one link back to Livewire (or to a full reload) with data-sparke-ignore. Because each preload is a full server render, Sparke also softens its crawl on Livewire sites - it preloads pages as their links near the viewport rather than crawling the whole app up front (override with data-preload). This masks slow renders behind preloading; it doesn't make them cheaper. As with any MPA, a component's server state is not carried across a navigation (native input values are, via Sparke's form-state restore).

Why it's instant

HTMX, Turbo and Livewire only hit the network after you click, so every navigation is gated on a round trip. Sparke flips the order: it preloads reachable same-origin pages into memory while the browser is idle, before you click. So the click is an instant in-memory swap with no request on the critical path.

Network work happens A click feels like
HTMX / Turbo / Livewire After the click Delay = round trip
Sparke (true SPA) Before the click, while idle Instant swap from memory

Those libraries solve a different problem (server-driven partials, reactivity, no-reload forms) and run happily alongside Sparke. Every page Sparke serves is still a complete, server-rendered document – so you keep the SEO, the no-JS fallback, and the progressive enhancement of an MPA.

What Sparke handles

Sparke intercepts same-origin http/https <a href> links. It lets the browser handle everything else: a target other than _self, download, rel="external", hash-only links, mailto:/tel:/javascript:, and any route you exclude with data-ignore.

Next steps