Patch Protocol
- Navigations update the live page instead of loading a new document
- Client state, focus and handlers survive, and the server sends only what changed
- Enabled per application with the
patchesoption - Experimental, coming soon
A server-rendered page usually starts over on every navigation: the browser discards the document, and with it every <let>, focused field and scroll position, then parses and resumes a new one. Persisted pages, built on the patch protocol, keep the document. When a link or form targets another route of the same application, the server renders that route as a patch, a stream of updates the live page applies to what it already shows. Structure that is new to the page is created from shells the response carries, values that changed are written in place, and everything else keeps the state it had.
No HTML is compared on either side. The compiler knows every spot where a server value can end up, so a patch writes values straight to those spots, and elements unrelated to server data are left alone.
The patch protocol is experimental and not available to try yet. The option, the response format and the client runtime may change in any release, including patch releases. See Coming Soon in the August newsletter and the September update for progress so far.
Enabling
Pass patches to the Vite plugin:
import { defineConfig } from "vite";
import marko from "@marko/run/vite";
export default defineConfig({
plugins: [marko({ patches: true })],
});The option compiles every template to answer navigations with patches and installs the client router on each page. Each build answers only its own pages, so a page loaded from an earlier deploy navigates with full document loads until it reloads.
Client State
State belongs to the page, as it does in client rendering. A component rendered in the same place before and after a navigation keeps its <let> values, the text typed into its fields and the elements a script attached to it. Consider a search page:
<form method="get">
<input name="q" value=$global.search.q>
<button>Search</button>
</form>
<let/expanded=false>
<button onClick() { expanded = !expanded }>Filters</button>
<if=expanded>
<filter-panel/>
</if>form method="get"
input name="q" value=$global.search.q
button -- Search
let/expanded=false
button onClick() { expanded = !expanded } -- Filters
if=expanded
filter-panelSubmitting the form patches the results in, while the open filter panel stays open and the search field keeps its text. The field's value= is the value it renders with. Without a valueChange= handler, what the user typed stays, exactly as when the value changes in the browser.
Scripts and Effects
Effects follow the same rules as in client rendering. A <script> runs when the content that contains it is created, runs again when a value it reads changes, and its $signal aborts before each rerun and when its content is removed. Content a navigation creates runs its effects once the patch applies.
Markup inserted by a navigation is created by the client, so an inline <html-script> in it is added to the page without running.
Content Security Policy
The client evaluates each part of a patch as JavaScript. A page served with a Content Security Policy needs 'unsafe-eval' in its script-src for its navigations to patch. Without it, each navigation loads the document.
Supported Pages
Patches apply to the pages Marko Run renders from route files. Templates rendered some other way, such as a template embedded inside another document, answer every navigation with a document.
Templates must use the Tags API.
Page Size
Every page includes the client router and the part of the patch runtime its templates use, including pages that otherwise ship no JavaScript. Its HTML also carries the markers and values a later patch updates, so a document is larger than the same page built without patches.
A patch is always smaller than the document it replaces: values the page already renders from its own state are left out, and shells the page has already received are not sent again.
Contributors
Helpful? You can thank these awesome people! You can also edit this doc if you see any issues or want to improve it.