Deployment helpers
getDeploymentId, isHardReloadResponse, handleHardReload and initDeploymentId: detect that a tab runs an older build than the server, and reload it.
import {
getDeploymentId,
handleHardReload,
initDeploymentId,
isHardReloadResponse,
} from '@gio.js/react';The client router already uses them: every navigation, prefetch, refresh and <GioForm> post sends the tab's deployment id, and a tab on an old build loads the new one in full. You need them only for your own fetch() calls.
Reference
How skew detection works
- Every server-rendered page carries an inline script setting
window.__GIO_DEPLOYMENT_ID__to the server's deployment id - a hash of the build, orGIO_DEPLOYMENT_IDwhen you pin one. - A request from the tab sends it back as the
x-deployment-idheader. - When the id differs from the server's, the server answers
409withx-gio-action: hard-reloadbefore any handler runs - for pages and route handlers alike. A request without the header is never refused.
[server] skew_protection = false makes the server ignore the header (no 409s); it logs a startup warning.
getDeploymentId
getDeploymentId(): string | undefinedgetDeploymentId() returns the id of the build that served this document, read from window.__GIO_DEPLOYMENT_ID__ on first use. Soft navigations never run the fetched page's scripts, so it keeps naming the build whose code runs in the tab. It is undefined on the server and on a page without the script (a static export).
isHardReloadResponse
isHardReloadResponse(response: Response): booleanisHardReloadResponse() is true when response.status is 409 and its x-gio-action header is hard-reload. A 409 your own handler returns does not match.
handleHardReload
handleHardReload(): voidhandleHardReload() reloads the page (window.location.reload()), so the tab fetches the new build's HTML and scripts. On the server, where there is no window, it does nothing, like the other helpers.
initDeploymentId
initDeploymentId(): voidinitDeploymentId() re-reads window.__GIO_DEPLOYMENT_ID__ into the value the getter above returns. Nothing needs to call it, since the getter reads the value on first use; it is kept for compatibility.
Examples
A fetch wrapper for your API
import { getDeploymentId, handleHardReload, isHardReloadResponse } from '@gio.js/react';
/** fetch() for this app's API that reloads the tab when a new build is live. */
export async function apiFetch(input: string, init: RequestInit = {}): Promise<Response> {
const headers = new Headers(init.headers);
const id = getDeploymentId();
if (id !== undefined) headers.set('x-deployment-id', id);
const res = await fetch(input, { ...init, headers });
if (isHardReloadResponse(res)) handleHardReload();
return res;
}After a deploy, the next apiFetch from a tab on the old build gets the 409 before your handler runs - so a POST changed nothing - and the tab reloads onto the new build.
Good to know
- Opt-in for your requests. Plain
fetch()calls do not sendx-deployment-id, so an old tab keeps talking to the new server - fine for an API that stays compatible across deploys. - Reloading loses unsaved state on the page (form input, scroll in nested views). For a request the user cannot afford to lose, save a draft before calling
handleHardReload(). - Pin the id across instances. Instances that run the same build compute the same id. If yours could differ (different build hosts), set the same
GIO_DEPLOYMENT_IDon all of them and change it with every deploy; otherwise a load balancer alternating between them causes reloads.
Related
- [server] -
skew_protection - Headers -
x-deployment-idandx-gio-action - Environment variables -
GIO_DEPLOYMENT_ID - navigate
Version history
| Version | Changes |
|---|---|
v0.1.0-beta.8 | The router sends the id on navigations, prefetches, refreshes and <GioForm> posts, and getDeploymentId() reads it on first use - initDeploymentId() is no longer needed. [server] skew_protection can turn the 409 off. handleHardReload() does nothing on the server instead of throwing. |
v0.1.0-beta.1 | Introduced. |