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.
<script src="/js/sparke.min.js" defer></script>
<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.