Pods
A pod is a small Root.js site that a plugin merges into yours: a folder of routes, custom elements, bundles and CMS collections, laid out like a project's own. Pods are how a plugin ships pages and content models, e.g. a tool that opens in the CMS sidebar, or a set of collections that several sites share, without copying files into each project.
Defining a pod
A plugin registers a pod with its pod field. The pod's name must be unique across the site, and each directory is an absolute path, so resolve them from the plugin's own file. That way they still work once the plugin is installed from node_modules:
// @/plugins/launches-pod.ts
import path from 'node:path';
import {fileURLToPath} from 'node:url';
import type {Plugin} from '@blinkk/root';
const POD_DIR = path.dirname(fileURLToPath(import.meta.url));
export function launchesPod(): Plugin {
return {
name: 'launches-pod',
pod: {
name: 'launches-pod',
mount: '/internal/launches',
routesDir: path.join(POD_DIR, 'routes'),
elementsDirs: [path.join(POD_DIR, 'elements')],
collectionsDir: path.join(POD_DIR, 'collections'),
},
};
}
// @/root.config.ts
import {defineConfig} from '@blinkk/root';
import {launchesPod} from './plugins/launches-pod';
export default defineConfig({
plugins: [launchesPod()],
});
pod can also be a list of pods, or a function that receives {rootConfig} and returns one (or a promise of one), for a pod that depends on the site's config. Wrap a standalone pod in definePod() to type-check it. Every directory is optional, and the fields are listed under Pod in the API reference.
The dev server watches the pod's directories, so adding or deleting a file reloads the page the same as it does in your own project.
Routes
Files in routesDir follow the same rules as routes/: index.tsx serves its directory, [slug].tsx and catch-alls take params, getStaticPaths() lists the pages to prerender, and files starting with _ are skipped. They're served under the pod's mount, which defaults to /. With the pod above, routes/index.tsx serves /internal/launches and routes/[id].tsx serves /internal/launches/<id>.
The site's base and localized URL format apply to pod routes too, and root build prerenders them along with yours. Keep pod URLs clear of your own routes, since which one serves an overlapping path isn't something to rely on. Change the mount or exclude the route instead, as shown below.
Routes are keyed by file path, so two pods can't share one routesDir. If a plugin registers the same pages more than once, e.g. at two mounts, give each pod its own directory and re-export the page from a shared module.
Elements and bundles
Each directory in elementsDirs is scanned like elements/, so a page that uses one of the pod's custom elements loads it automatically, whether the page comes from the pod or from your site. Tag names share one namespace with your own elements, so prefix them with something specific to the pod.
Scripts at the top level of bundlesDir are built with your bundles/.
CMS collections
Every *.schema.ts file in collectionsDir becomes a collection in the CMS, named after its file the same as your own schemas, and is included in the generated root-cms.d.ts types. If the site has a collection with the same id, the site's own schema is used and the pod's is ignored. That makes it possible to override a pod's schema by copying it into collections/ and editing it.
Two pods that define the same collection id fail with an error. Rename one of them with collections.rename, which changes the id the CMS uses, so do it before anyone saves docs to the collection.
Opening a pod in the CMS
A pod's pages can be shown inside the CMS by pointing a sidebar tool at its mount. This is how this site's own Templates tool works:
// @/root.config.ts
export default defineConfig({
plugins: [
templatesPod(), // Mounted at /cms-tools/templates.
cmsPlugin({
// ...
sidebar: {
tools: {
templates: {
label: 'Templates',
iframeUrl: '/cms-tools/templates/',
},
},
},
}),
],
});
Configuring a pod
pods in root.config.ts overrides a pod's settings for one site, keyed by the pod's name. It can turn a pod off, move it, and drop or rename some of what it adds:
// @/root.config.ts
export default defineConfig({
plugins: [launchesPod(), blogPod()],
pods: {
'launches-pod': {
mount: '/tools/launches',
routes: {
exclude: ['archive/', /\.preview\.tsx$/],
},
},
'blog-pod': {
collections: {
exclude: ['Authors'],
rename: {Posts: 'BlogPosts'},
},
},
'legacy-pod': {
enabled: false,
},
},
});
A string in routes.exclude skips every route file whose path, relative to the pod's routesDir, contains it, and a regex is tested against that path. collections.exclude and the keys in collections.rename are the pod's own collection ids. For the full list, see PodConfig in the API reference.