Data fetching
Overview
createRoute() is the recommended way to set up routes that are connected to the CMS. Give it a collection, and it loads the doc for each URL and renders the route's component with the doc as props.doc.
It also handles the details: draft content for ?preview=true URLs (which the CMS adds to its preview links), the locale for each request, translations, 404s for missing docs, and Cache-Control headers.
// @/routes/[...slug].tsx
import {createRoute} from '@blinkk/root-cms';
import {PagesDoc} from '@/root-cms.js';
export default function Page(props: {doc: PagesDoc}) {
return <h1>{props.doc.fields?.meta?.title}</h1>;
}
// Renders `Pages/<slug>`, e.g. `/about` renders `Pages/about` and `/`
// renders `Pages/index`.
export const {handle} = createRoute({
collection: 'Pages',
slugParam: 'slug',
});
For a site built as static HTML (SSG), export getStaticProps and getStaticPaths instead, which build a page for every published doc in the collection:
export const {getStaticProps, getStaticPaths} = createRoute({
collection: 'BlogPosts',
ssg: true,
});
Route options
Most custom logic maps to an option of createRoute(), so a route rarely needs its own handler:
-
slug: the doc for a single-page route, e.g.{slug: 'index'}inroutes/index.tsx. UseslugFormatorgetSlug(params)for slugs built from several URL params. -
batchRequestandpreRenderHook: load more content for the page and pass it as props (see Batch requests). -
translations: load more translations, such as strings shared by every page (see Translations). -
resolveLocale: choose the locale yourself, e.g. from the user's country. -
resolveDoc: serve a different doc on the same URL, e.g. a regional variant. -
getMode: choose between draft and published content yourself. -
notFoundHook: render your own page when the doc doesn't exist. -
preRenderHook: redirect withprops.$redirect, or set a status code withprops.$statusCode. -
cacheandcacheControl: cache published reads in memory for 60 seconds, and set theCache-Controlheader for published pages.
See the @blinkk/root-cms reference for every option.
Batch requests
Every read from the CMS is a round trip to Firestore. A batch request combines the reads a page needs: its docs, queries and data sources are fetched in parallel. With the v2 translations manager, the translations for every doc in the batch are then fetched together, for the page's locale only.
In createRoute(), add reads with the batchRequest option. They're fetched in the same batch as the route's doc. The response is available as ctx.batchResponse, e.g. in preRenderHook, which passes it to the page as props:
export const {handle} = createRoute({
collection: 'Pages',
slugParam: 'slug',
batchRequest: (req) => {
req.addDoc('Global/header');
req.addQuery('posts', 'BlogPosts', {limit: 3});
req.addDataSource('pricing');
},
preRenderHook: (props, ctx) => ({
...props,
header: ctx.batchResponse!.getDoc('Global/header'),
posts: ctx.batchResponse!.getQuery('posts'),
pricing: ctx.batchResponse!.getDataSource('pricing'),
}),
});
Read the results with the response's getters, using the ids you added: getDoc(docId) and getDataSource(id) return null if the doc or data source doesn't exist, and getQuery(queryId) returns an empty array if the query has no results.
Batch requests outside a route
In a custom handler, getStaticProps() or a plugin, create a batch request with cmsClient.createBatchRequest(). With translate: true and the v2 translations manager, it also loads the translations for the docs it returns, for the locales you pass:
import {RootCMSClient} from '@blinkk/root-cms';
const cmsClient = new RootCMSClient(rootConfig);
const req = cmsClient.createBatchRequest({
mode: 'published',
translate: true,
locales: [locale],
});
req.addDoc('Pages/index');
req.addTranslations('common');
const res = await req.fetch();
const doc = res.getDoc('Pages/index');
const translations = res.getTranslations(locale);
If the locale depends on the doc, fetch the content first and the translations once the locale is known:
const res = await req.fetchContent();
const locale = pickLocale(res.getDoc('Pages/index'));
await req.fetchTranslations(res, {locales: [locale]});
Listing docs
To list the docs in a collection, add a query to the batch request with addQuery(). It takes the same options as cmsClient.listDocs(): limit, offset, orderBy, orderByDirection, and query for Firestore filters.
// @/routes/blog/index.tsx, which renders `Pages/blog`.
export const {handle} = createRoute({
collection: 'Pages',
slug: 'blog',
batchRequest: (req, ctx) => {
req.addQuery('posts', 'BlogPosts', {
orderBy: ctx.mode === 'draft' ? 'sys.createdAt' : 'sys.firstPublishedAt',
orderByDirection: 'desc',
});
},
preRenderHook: (props, ctx) => ({
...props,
posts: ctx.batchResponse!.getQuery('posts'),
}),
});
Custom order
Collections with customSorting: true let editors drag docs into order in the CMS. The order is stored at sys.sortKey:
req.addQuery('guides', 'Guides', {orderBy: 'sys.sortKey'});
Ordering by sys.sortKey leaves out docs that don't have a position yet, e.g. ones created by a script.
Translations
createRoute() loads the translations for the page's locale: the doc's own strings and the strings tagged common. With the v2 translations manager, it loads the translations for docs the page references and docs in its batch request instead of common. In your components, translate strings with useTranslations():
import {useTranslations} from '@blinkk/root';
export default function Page(props: {doc: PagesDoc}) {
const t = useTranslations();
return <h1>{t(props.doc.fields?.meta?.title || '')}</h1>;
}
To load more translations, such as strings shared by every page, return their ids from the translations option:
createRoute({
collection: 'Pages',
translations: () => ({tags: ['common']}),
});
Outside a route, use cmsClient.loadTranslationsForLocale(locale, {tags: ['common']}). With the v2 translations manager, use a batch request with translate: true and res.getTranslations(locale) instead (see Batch requests outside a route), since loadTranslations() and loadTranslationsForLocale() only read the v1 translations.
Reading docs directly
For a single read, e.g. in a script, use RootCMSClient. Every read takes a mode: published for live content, or draft for the latest edits.
import {RootCMSClient} from '@blinkk/root-cms';
const cmsClient = new RootCMSClient(rootConfig);
const doc = await cmsClient.getDoc('Pages', 'index', {mode: 'published'});
const res = await cmsClient.listDocs('BlogPosts', {mode: 'published'});