Skip to content
Strife Docs

Tutorials

Use Strife as your Astro CMS

Use Strife as the CMS for your Astro site: start from the Strife starter or add Strife to your own project, define your content with defineType, deploy it with the CLI, render pages from the content index, and turn on live preview.

Strife gives your Astro site a CMS. Editors write and publish in Strife Studio, and your Astro pages read the content on each request and render it with your own components. @strifeapp/astro connects your site to your team's database, and live preview shows editors their changes on your site as they type.

There are two ways to start:

You need Node.js 22.12 or later and Astro 5, 6 or 7. If you are new to Strife, How Strife works explains the parts.

Start from the Strife starter

Run this where you want the new project:

Terminal
npm create @strife

The CLI signs you in through your browser, and lets you pick a team or create a new one. It then sets up the starter, writes the connection secret to .env, deploys the starter's schema and adds a first page. Start the site:

Terminal
npm run dev

Open the address Astro prints, usually http://localhost:4321. The page shows its heading and body from Strife. Open the page in Studio, where the CLI printed a link, and edit it to watch your site change as you type.

The starter is a working example of everything below:

  • the schema is in schemas/home.ts
  • the query is in src/pages/index.astro
  • live preview is in src/components/HomeContent.svelte

Add Strife to your own Astro project

1. Install the packages

Terminal
npx astro add @strifeapp/astro
npm install @strifeapp/strife
npm install --save-dev @strifeapp/cli
  • astro add installs the integration and adds it to astro.config.mjs.
  • @strifeapp/strife has live preview and the schema tools.
  • @strifeapp/cli gives you the strife command.

2. Connect the project to your team

From your project's folder, run:

Terminal
npx strife .

The CLI signs you in, links the folder to a team (or creates one), and writes STRIFE_SECRET to .env. It also adds .env to .gitignore. The secret contains your certificate's private key, so keep it out of version control and out of the browser. See How your site connects.

3. Configure Astro

JavaScript
// astro.config.mjs
import { defineConfig } from 'astro/config';
import strife from '@strifeapp/astro';

export default defineConfig({
  // Live preview needs pages rendered on request.
  output: 'server',
  // The address Studio previews a new team's site on.
  server: { port: 4321 },
  integrations: [strife()],
});

astro add adds the integration but not output: 'server', so add it yourself. Live preview reads a preview token on each request, and that only happens for pages rendered on request.

A team the CLI creates previews http://localhost:4321, so keep the dev server on that port. An existing team previews the site address in its settings.

Then declare the types that @strifeapp/astro doesn't ship yet, in src/env.d.ts:

TypeScript
/// <reference types="astro/client" />

declare module 'strife:store' {
  import type { DocumentStore } from 'ravendb';
  export const store: DocumentStore;
}

declare namespace App {
  interface Locals {
    editMode?: boolean;
  }
}

4. Define your content

Write a schema for each kind of page. Create schemas/article.ts:

TypeScript
import { defineType, fields } from '@strifeapp/strife/schema';

export const article = defineType({
  name: 'article',
  title: 'Article',
  type: 'document',
  fields: {
    heading: fields.text({ label: 'Heading', localizable: true }),
    body: fields.html({ label: 'Body', localizable: true }),
  },
});

Point the CLI at your schemas in strife.config.ts:

TypeScript
import { defineConfig, glob } from '@strifeapp/strife/schema';

export default defineConfig({
  schema: glob('./schemas/**/*.ts'),
});

Deploy them, and generate types for them:

Terminal
npx strife push
npx strife typegen generate
  • strife push creates the Article template in your team and deploys Content/ByUrl, the content index your pages query. Run it again whenever you change a schema.
  • strife typegen generate writes an interface for each template to .strife/types.ts, such as Article extends Content.

Schemas and the Field Reference list everything you can define.

5. Write a page in Studio

Open Studio, create an Article, give it an address and publish it. See Your first page in the user guide.

6. Render pages

Create src/pages/[...slug].astro. It finds the page for the address being visited and renders it:

Astro
---
import { store } from 'strife:store';
import LivePreview from '@strifeapp/astro/LivePreview.astro';
import type { Article } from '../../.strife/types';

const session = store.openSession();
const find = (status: string) =>
  session
    .query<Article>({ indexName: 'Content/ByUrl' })
    .whereEquals('url', Astro.url.pathname)
    .whereEquals('locale', 'en')
    .whereEquals('status', status)
    .selectFields(['displayName', 'heading', 'body'])
    .noCaching()
    .firstOrNull();

// Visitors see the published version. In Strife's live preview, show the
// pending edits first, then a page that was never published.
const page = Astro.locals.editMode
  ? ((await find('draft')) ?? (await find('unpublished')) ?? (await find('published')))
  : await find('published');

if (!page) {
  return new Response(null, { status: 404 });
}
---

<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>{page.displayName}</title>
    <LivePreview />
  </head>
  <body>
    <h1>{page.heading}</h1>
    <article set:html={page.body} />
  </body>
</html>
  • Content/ByUrl has a row for each page and language. A page's pending edits and scheduled publish are rows too, at the same address, so the query always filters on status.
    • Visitors get the published row.
    • In live preview, Astro.locals.editMode is true. The page then shows the pending edits (draft), or a page that was never published (unpublished).
    • Index URL describes every kind of row.
  • selectFields returns each field in the row's language. Without it, the query returns the whole document, where a translated field is an object such as { en: '…' }.
  • .noCaching() stops the RavenDB client from caching a response for every address someone tries, which matters in a catch-all route.
  • set:html renders the rich text body's HTML.

Start the dev server with npm run dev and open the page's address.

7. Turn on live preview

<LivePreview /> loads Strife's live preview script when your page is open in Studio, and does nothing for visitors. Place it once, in the <head> of your layout.

To update the page while editors type, read the live document in a client component with subscribe from @strifeapp/strife. It calls your function with the document on every change. A translated field can arrive as text or as an object keyed by language. Here is a Svelte component. It needs Astro's Svelte integration (npx astro add svelte), listed before strife() in integrations as in the starter:

Svelte
<!-- src/components/ArticleContent.svelte -->
<script lang="ts">
  import { onMount, untrack } from 'svelte';
  import { subscribe } from '@strifeapp/strife';

  type Localized = string | Record<string, unknown> | null | undefined;
  let { heading: initialHeading, body: initialBody, locale = 'en' } = $props();

  function text(value: Localized): string {
    if (value == null) return '';
    if (typeof value === 'string') return value;
    const v = value[locale] ?? Object.values(value)[0];
    return typeof v === 'string' ? v : '';
  }

  let heading = $state(untrack(() => text(initialHeading)));
  let body = $state(untrack(() => text(initialBody)));

  onMount(() =>
    subscribe((data) => {
      const doc = data as Record<string, Localized>;
      if ('heading' in doc) heading = text(doc.heading);
      if ('body' in doc) body = text(doc.body);
    }),
  );
</script>

<h1>{heading}</h1>
<article>{@html body}</article>

Render it from your page with <ArticleContent client:load heading={page.heading} body={page.body} />. The same pattern works in React or Vue: call subscribe when the component mounts, and return its result to stop listening. See Live preview for the details.

Deploy

astro dev works without more setup. For production builds, add the Astro adapter for your host:

Deploy targetAdapterInstall
Self-hosted Node.js
@astrojs/node
npx astro add node
Vercel
@astrojs/vercel
npx astro add vercel
Netlify
@astrojs/netlify
npx astro add netlify
Cloudflare Workers
@astrojs/cloudflare
npx astro add cloudflare

Then set STRIFE_SECRET as a server-side secret in your host's environment settings. @strifeapp/astro reads it on the server when a page is requested, and never builds it into your site's files. The certificate makes the value long, and some hosts limit how long a variable can be: the @strifeapp/astro README lists the limits.

More for your site

  • Snapshots: let editors share a link to a frozen copy of a page for review. See Snapshots.
  • Open in Strife: a toolbar that takes editors from a page on your site to the same page in Studio. See Open in Strife.
  • Insights: <Insights /> from @strifeapp/astro reports how fast your pages are for real visitors, and checks accessibility in the live preview. See the @strifeapp/astro README.