GioJSdocs
On this page

revalidate

Cache a page's rendered HTML in the Rust server for a number of seconds, or until the next deploy or purge.

app/blog/page.tsx
export const revalidate = 60;   // fresh for 60 seconds, then refreshed in the background

A page without revalidate renders on every request. With it, the first request renders the page in the Node worker and the Rust server stores the HTML; later requests for the same URL are answered from memory (or the disk tier) without running any JavaScript.

Reference

OptionTypeDefaultDescription
revalidatenumber | false(not set)Seconds the cached page stays fresh, or false to keep it until a purge or a deploy. Not set: the page is rendered per request and never stored.
ValueEffect
not exportedRendered per request and streamed. Cache-Control: private, no-cache, X-Gio-Cache: bypass.
N (a whole number above 0)Cached and fresh for N seconds, then served stale while one background render refreshes it.
falseCached for one year (31536000 seconds): in practice until a purge, or a deploy that changes the deployment ID.
0Not cached - the same as leaving it out.
anything elseAn error naming the file: at startup when written as a literal, at render otherwise (see below).

Behavior

  • Freshness and stale-while-revalidate. For N seconds a request is a hit (X-Gio-Cache: hit; ttl=...). After that, until the page is [cache] swr_multiplier times N old (10 by default), the stale copy is served at once (stale; age=...; revalidating) while one background render replaces it. Past that window the next request renders synchronously. swr_multiplier = 0 never serves stale.
  • The cache key is the method (GET or HEAD), the path and the query string, so /blog?page=2 is its own entry. With i18n, each locale is its own entry. Concurrent misses for the same key share one render.
  • HTTP caching. A cached page is sent with Cache-Control: public, max-age=0, s-maxage=<seconds left>, stale-while-revalidate=<rest of the window> and a weak ETag; a matching If-None-Match gets a 304. A Cache-Control set by the app or a [[headers]] rule wins. See Browser and CDN caching.
  • Purging. revalidateTag(), revalidatePath() and POST /_gio/revalidate remove entries at once; the next request renders fresh. A new deployment ID (any change to the app's code) drops every entry the previous build stored.

What is never cached

Even with revalidate set, these renders are answered but not stored:

  • A render whose getServerSideProps or generateMetadata read the visitor's cookies, IP, host or scheme, or a credential header - see personalized renders. A warning names the route.
  • A page whose getServerSideProps returned response headers, or any response that sets a cookie.
  • Redirects, 404s and error pages; a render that recovered from an error inside a Suspense boundary.
  • Requests other than GET and HEAD, including a page action's re-render.
  • A render an onRequest plugin rewrote (path, query or locale) after reading credentials.
  • Everything, with [cache] enabled = false.

Examples

A page that changes every few minutes

app/page.tsx
import type { GetServerSideProps, InferPageProps } from '@gio.js/core';
import { db, type Post } from '../lib/db.server.ts';

export const revalidate = 300;

export const getServerSideProps: GetServerSideProps<{ posts: Post[] }> = async () => {
  return { props: { posts: await db.posts.latest(10) } };
};

export default function Home({ posts }: InferPageProps<typeof getServerSideProps>) {
  return <ul>{posts.map((p) => <li key={p.id}>{p.title}</li>)}</ul>;
}

Cache until the content changes

Keep the page until you purge it, and purge it where the data changes:

app/docs/[slug]/page.tsx
import type { PageProps } from '@gio.js/core';

export const revalidate = false;
export const tags = ['docs'];

export default function Doc({ params }: PageProps<'/docs/:slug'>) {
  return <h1>{params.slug}</h1>;
}
app/api/cms-webhook/route.ts
import { revalidateTag, type GioRequest } from '@gio.js/core';
import { verifySignature } from '../../../lib/cms.server.ts';

export async function POST(req: GioRequest) {
  await verifySignature(req);
  const result = await revalidateTag('docs');   // { ok, purged }
  return { purged: result.purged };
}

Check what the cache did

bash
curl -sI http://localhost:3000/blog | grep -i -e x-gio-cache -e cache-control
# x-gio-cache: miss; stored
# cache-control: public, max-age=0, s-maxage=60, stale-while-revalidate=540

Good to know

  • Use a whole number of seconds or false. A negative or fractional number, NaN, or a string such as '60' is an error naming the file. Written as a literal (export const revalidate = 1.5), it is read from the source when the routes are discovered and stops the worker at boot (see worker boot errors) and gio build standalone. Any other expression (60 * 60, an imported constant) is checked when the page is requested, since page modules load on first use: development shows it in the error overlay, production logs it and answers 500 with a digest.
    text
    app/blog/page.tsx: export const revalidate must be a whole number of seconds (0 or more) or false - got 1.5
  • revalidate is read from page.tsx only. In a layout.tsx or a route.ts it has no effect: route handler responses are never cached, so set Cache-Control on the Response for browsers and CDNs.
  • app/sitemap.ts, app/robots.ts and app/manifest.ts also read revalidate. There it defaults to 3600, a fraction is rounded down, and 0 or less turns caching off.
  • The cache belongs to one server instance. Behind a load balancer, purge each instance (POST /_gio/revalidate on its own address).
  • Data your page reads at runtime (a database, files, .env values) is not part of the deployment ID: after changing it, purge.
  • gio export ignores revalidate: every exported page is static HTML.
  • Next.js's dynamic = 'force-static' has no effect in GioJS; gio migrate turns it into revalidate = false on pages.

Version history

VersionChanges
v0.1.0-beta.8An invalid value (negative, fractional, a string, NaN) stops the worker at boot when written as a literal, and is a render error naming the file otherwise; it used to answer a bare 500 because the server could not parse the response. Cached pages send Cache-Control and a weak ETag; renders that read credentials, set cookies or return headers are no longer stored; purges with revalidateTag(), revalidatePath() and POST /_gio/revalidate.
v0.1.0-beta.1Introduced, with stale-while-revalidate.