Skip to content

Content

Remotes

remotes(site) mints nav, meta, page, and search as Kit remotes: prerendered by default, live when you ask. The doc body bakes into a region ticket. Thus the corpus stays server-side.

On this page

The site value (docs here) is server-only: it holds the corpus. The page needs data. remotes() mints the crossing: the sidebar tree, the shell bundle, the view of one page, and search, as Kit remotes in a .remote.ts. The browser imports only these. The collection never follows.

When to use

Always, for a site() site. The remotes are the one sanctioned path from the server-side site to a page. To render the nav or a body without them means to import the collection into a client module: the exact leak that the server-module rule exists to prevent.

Mint them

import { remotes } from 'ogygia/content/server';
import { docs } from './site.server';

export const { nav, meta, page, search } = remotes(docs, { base: '/docs' });

Four remotes. Each is a plain async function that the page awaits:

  • nav(slug?): the sidebar tree, hrefs baked for base. On a dimensioned site, pass the current slug (or a bare coordinate prefix like 'v1/'), so that the tree reflects that coordinate.
  • meta(slug?): the full shell bundle { nav, switcher, data } in one prerendered call. Feed it directly to <DocsShell {meta}>. Then the layout never imports the corpus.
  • page(slug) returns the view of one page: data, meta, headings, prev/next, and the baked body.
  • search(q): ranked hits from search.

Prefer `meta` over `nav` for shells

nav exists for custom chrome that wants only the tree. A shell wants the tree and the switcher and the site facts. meta is those three in one prerendered payload: one await, one cache entry.

The body crosses as a ticket

A body is a live region: a same-pass SSR render. It cannot serialize as-is. page awaits it. That bakes its SSR HTML into the region ticket: HTML only, no source, no corpus. The page renders that with <Region> (through <Doc>). Each island that the source imported wakes from the baked markup:

<script>
	import { Doc } from 'ogygia/content';
	import * as docs from '$lib/site.remote';

	const view = (await docs.page(slug))!;   // view.entry.body is a baked ticket
</script>

<Doc {view} />

That is why the docs corpus can be megabytes, while a page ships only its own baked HTML.

Prerender or live

By default, nav, meta, and page prerender: static payloads, computed at build. Some sites want them dynamic (an authenticated preview, per-request content). The modes option picks per remote:

export const { nav, meta, page, search } = remotes(docs, { base: '/docs' });
// nav/meta/page are baked at build and served static; search runs per request
export const { nav, meta, page, search } = remotes(docs, {
	base: '/docs',
	modes: { page: 'query', nav: 'query' }   // per-request; the site's `context` gates what renders
});
  • 'prerender' (default for nav / meta / page): baked at build, served static. Required when the awaiting pages prerender.
  • 'query': runs on the server per request. It pairs with site({ context }) for previews and roles.

search is always a query: a search is dynamic by nature. (For a fully static search box, ship the index in its place, and query it in a client worker. Then you do not mint search at all.)

Prerender entries

The route still must know which slugs to bake. docs.entries() is each leaf in the outline, declared old addresses included. Thus redirect stubs bake too:

import { docs } from '$lib/site.server';

export const prerender = true;
export const entries = docs.entries;
export const load = docs.load;   // the 404 guard + alias redirects + per-page checks

docs.load is the piece that people forget. It maps an unknown slug to a real 404 (not an empty page). It serves a baked 308 for each address that an entry declares in redirect_from. And it runs the per-page checks in dev.

Do / don’t

  • Do mint remotes in a .remote.ts, and import only those from pages. That is the full corpus safety.
  • Do let page bake the body. The await is what turns a live region into something that can cross the wire.
  • Don’t return a raw entry.body from a remote or a server load. It is a same-pass render. ogygia stops you, with an error that says exactly that.
  • Don’t use query mode by default. Prerendered pages are faster and cheaper. Switch a remote to query only when the content truly varies per request.