Getting started
Overview
Root.js is a web platform for building content-driven websites. It pairs a TypeScript web framework with a built-in CMS, so developers, writers and translators can work on the same site.
-
The framework renders TSX on the server, either on demand (SSR) or ahead of time as static HTML (SSG). It's built on Vite, and it ships no client-side JavaScript unless you add it.
-
The CMS adds visual editing with live preview, localization, releases and publishing workflows. Content is stored in your own Firebase project, and content models are defined in code.
Requirements
-
Node.js 24 (the current LTS) or later.
-
A package manager. We recommend pnpm, which you can enable with Corepack.
-
For the CMS: the gcloud CLI and a Google Cloud billing account. The CMS needs Secret Manager and a storage bucket, which require billing, but low-traffic sites usually stay in the free tier.
node --version # v24 or later
corepack enable
Set up with an AI agent
The quickest way to a working site is to let a coding agent (such as Claude Code) run the steps on this page for you. Start it in the folder where you keep your projects and paste this prompt:
Set up a new Root.js site. Follow the instructions at
https://rootjs.dev/skills/root-cms-setup.md
The agent asks a few questions up front: the folder name, whether to use a new or existing Google Cloud project, and a site id. It then creates the project, runs root-cms setup (after showing you a dry run), starts the dev server and installs the Root.js skills, so later sessions know how to work with your content.
A few steps need you, because they happen in a browser or pick who pays: signing in with gcloud, choosing a billing account for a new project, and turning on Google sign-in in the Firebase console. The agent tells you when.
If you'd rather do it yourself, the rest of this page covers the same steps.
Create a project
Scaffold a new project from the starter template:
pnpm create @blinkk/root my-site
It offers to install the dependencies and set up Google Cloud for the CMS right away. Say yes to run the setup wizard described below, or say no and run it later. Either way, start the dev server from the new folder:
cd my-site
pnpm install
pnpm dev
The site runs at http://localhost:4007, and pages reload as you edit them. To use a different port, set the PORT environment variable.
The starter comes with the CMS, a Pages collection, and a route that builds each page from a list of templates:
my-site/
├── collections/
│ └── Pages.schema.ts # The Pages collection
├── components/
│ └── PageModules/ # Renders each module with its template
├── layouts/
│ └── BaseLayout.tsx # <html>, <head>, header and footer
├── routes/
│ ├── [[...slug]].tsx # Renders Pages docs by slug
│ └── 404.tsx
├── templates/
│ └── TemplateHero/ # TemplateHero.schema.ts, .tsx, .module.scss
├── root-cms.d.ts # Generated types for your schemas
└── root.config.tsUntil the CMS is connected to Google Cloud, the home page shows the setup steps.
Just the framework? The minimal template is a plain Root.js project with a single "Hello, Root.js" page at routes/index.tsx, and no CMS. It's a good fit for static sites. To add the CMS to it later, install @blinkk/root-cms and firebase-admin, and add cmsPlugin() as described in CMS configuration:
pnpm create @blinkk/root --template=minimal my-site
Connect the CMS to Google Cloud
The CMS runs inside your site at /cms/ and stores content in Firestore, in a Google Cloud project you own. The root-cms setup wizard sets up that project. If you said yes in create-root, it has already run; otherwise run it from your project:
pnpm exec root-cms setup
It asks for a new or existing project and a site id, then:
-
Signs you in with
gcloud, if needed, and links a billing account. -
Adds Firebase, creates the Firestore database and storage bucket, and applies the CMS's security rules.
-
Makes you an ADMIN of the CMS.
-
Creates a service account for the site to run as in production.
-
Stores a session secret in Secret Manager, and creates a
.root.secrets.jsonmanifest to commit (see Share environment variables). -
Writes the site id and Firebase config into
root.config.ts.
One step is manual, because Google has no API for it: the wizard asks you to turn on Google sign-in in the Firebase console, links you to the page, and checks that it worked.
The site id keeps this site's content, uploads and secrets apart, so several sites can share one Google Cloud project. The wizard is safe to run again: it skips anything that's already set up, so if a step fails, fix it and re-run. Pass --dry-run to see what it would change first.
Create your home page
Restart the dev server and open http://localhost:4007/cms/. In the Pages collection, create a doc with the slug index, add a TemplateHero module, and publish it. It replaces the setup page at /. Other slugs map to their own URL, e.g. about renders at /about.
To set up Firebase without the wizard, or to see every option the CMS supports, see CMS configuration.
Build pages from templates
Each page in the starter is a list of modules. The Pages collection offers every template schema in the templates/ folder with schema.glob():
// @/collections/Pages.schema.ts
schema.array({
id: 'modules',
label: 'Modules',
of: schema.oneOf({
// Every `templates/<Name>/<Name>.schema.ts` file is available here.
types: schema.glob('/templates/*/*.schema.ts'),
}),
}),
The route at routes/[[...slug]].tsx loads the doc for the URL with RootCMSClient, and PageModules renders each module with the component that matches its template name. Editors can preview drafts by adding ?preview=true to the URL.
To add a template, create a folder in templates/ with a schema and a component of the same name. For example, here's the starter's TemplateHero template:
// @/templates/TemplateHero/TemplateHero.schema.ts
import {schema} from '@blinkk/root-cms';
export default schema.define({
name: 'TemplateHero',
description: 'Large page heading with an optional image.',
fields: [
schema.string({
id: 'title',
label: 'Title',
translate: true,
}),
schema.string({
id: 'body',
label: 'Body',
translate: true,
variant: 'textarea',
}),
schema.image({
id: 'image',
label: 'Image',
}),
],
});
// @/templates/TemplateHero/TemplateHero.tsx
import {useTranslations} from '@blinkk/root';
import {TemplateHeroFields} from '@/root-cms.js';
import styles from './TemplateHero.module.scss';
export function TemplateHero(props: TemplateHeroFields) {
const t = useTranslations();
return (
<section className={styles.hero}>
{props.title && <h1 className={styles.title}>{t(props.title)}</h1>}
{props.body && <p className={styles.body}>{t(props.body)}</p>}
{props.image?.src && (
<img
className={styles.image}
src={props.image.src}
width={props.image.width}
height={props.image.height}
alt={props.image.alt || ''}
/>
)}
</section>
);
}
Then regenerate the TypeScript types for your schemas. The new template shows up in the CMS the next time you open it:
pnpm exec root-cms generate-types
Learn more in Schemas and Data fetching.
Share environment variables
API keys and other secrets belong in a .env file, which shouldn't be committed. To share them with your team, root secrets stores them in Google Cloud Secret Manager and keeps each developer's .env in sync. root-cms setup creates the .root.secrets.json manifest for you; for a project without the CMS, create it with root secrets init:
# Create a manifest (.root.secrets.json) and commit it.
pnpm exec root secrets init --gcp-project=my-project --gsm-key=my-site-env
# Upload the values in your .env file.
pnpm exec root secrets push
# Teammates download them into their own .env.
pnpm exec root secrets sync
Once a project has a manifest, root dev syncs the secrets when it starts. See the CLI reference for every command.
Work with AI agents
Root.js includes skills that teach AI coding agents, such as Claude Code, how to set up the CMS and how to read and edit your content from the command line. If an agent set up your project, they're already installed. Otherwise, install them into your project and commit them:
pnpm exec root-cms skill.install
With the skills installed, you can ask an agent to:
-
Look up or update content, e.g. "list the pages that don't have a meta description".
-
Propose content changes as a YAML file that your team reviews in a pull request, then applies with
root-cms proposal.apply. -
Connect a teammate's checkout, or a new environment, to Google Cloud with
root-cms setup.
The skills install to an existing agent skills folder, such as .claude/skills, or to .agent/skills if there isn't one. Re-run the command with --force after upgrading @blinkk/root-cms to pick up new versions.
Next steps
-
Project structure: where routes, components and content models live.
-
Routes: file-based routing, data fetching and SSR.
-
Deployment: ship your site as static HTML or with a server.
-
Schemas: model your content.