Skip to content
Strife Docs

Tutorials

Astro

Build fast, flexible, and CMS-powered websites with Strife and Astro.

Getting Started with Strife and Astro

This guide walks you through how to integrate Strife CMS into your Astro project using TypeScript.

Note:

There's a newer, code-first alternative to steps 3–4 and 9 below. Strife CLI lets you define templates with defineType instead of hand-written Template objects registered via the integration's collections option, and deploys them explicitly with strife push rather than lazily on first request. The Template-object approach on this page still works and isn't being removed, but defineType + strife push is the recommended path for new projects — see the CLI reference for the full picture, including how credentials are provisioned automatically instead of the six manual .env values in step 6.


1. Create a new Astro project

Start by creating a fresh Astro project:

Terminal
npm create astro@latest

Follow the prompts to scaffold your site.


2. Install the Strife Astro Integration

Inside your Astro project, install the Strife integration:

Terminal
npx astro add @strifeapp/astro

After running this, your astro.config.mjs will be updated automatically:

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

export default defineConfig({
  integrations: [strife()],
  output: 'server',
});

Strife requires output: 'server' because content is fetched at request time. The integration sets this automatically on install, and also pulls in @strifeapp/types so TypeScript users get full type safety without any extra install step.

From @strifeapp/astro 1.6.0 the integration caps the RavenDB client's in-memory response cache at 8 MiB (set strife({ httpCache: { maxBytes } }) to change it). If your project also lists ravendb as its own dependency, keep it at 7.2.3 or later: older clients cannot be given a byte budget, so the integration limits them to 500 cached responses and logs a warning.

Warning:

Using an older version of the integration? Until the updated Astro plugin ships, three things need to be done manually:

  1. Install types manually if astro add doesn't include them automatically:

    Terminal
    npm install -D @strifeapp/types
  2. Add output: 'server' to your defineConfig({ ... }) call if it's missing after astro add.

  3. Add a name field to every Template, matching its collection value (e.g. name: 'Homes' on the Homes template in the next step). Without it, all templates silently collide at the same storage key and only the last registered one survives.

Once the new plugin version lands, all three are handled for you and these steps aren't needed.


3. Define your first Collection and Template

In Strife, content is structured via Collections and Templates. Each template describes the fields available for a collection item.

A typical site has a mix of singleton templates (one document per project — e.g. a Home page or Global Settings) and multi-instance templates (many documents — e.g. blog posts or product pages). Strife controls this with the composable field on the template.

For more information on how templates and collections work, see the full Strife Templates & Collections documentation.

Note:

A quick note on "collections": Strife uses the word "collections" in two places — the collections array on the strife() integration (the list of templates to register, see step 4) and the collection field on each Template (the RavenDB document collection name). Neither is related to Astro's native content collections feature (configured via src/content/ and content.config.ts). If you see a Content config not loaded warning at boot, that's Astro noticing you haven't set up its own content collections — it's safe to ignore if you're using Strife exclusively.

Start with a Home template for your site's root page. Create src/collections/Home.ts:

TypeScript
import { type Template } from '@strifeapp/types';

const Home: Template = {
  displayName: 'Home',
  collection: 'Homes',
  templateType: 'dt',
  normalizedName: 'home',
  composable: false,  // singleton — only one Home document per project
  editors: [
    {
      label: 'Heading',
      editor: { name: 'str-input', type: 'text', propertyName: 'heading' },
    },
    {
      label: 'Description',
      editor: { name: 'str-textarea', type: 'text', propertyName: 'description' },
    },
  ],
};

export default Home;

Then add a Pages template for any other content — about, contact, marketing pages, anything that isn't a one-off. Create src/collections/Pages.ts:

TypeScript
import { type Template } from '@strifeapp/types';

const Pages: Template = {
  displayName: 'Page',
  collection: 'Pages',
  templateType: 'dt',
  normalizedName: 'page',
  composable: true,  // multi-instance — editors can create many Pages
  editors: [
    {
      label: 'Heading',
      editor: { name: 'str-input', type: 'text', propertyName: 'heading' },
    },
    {
      label: 'Description',
      editor: { name: 'str-textarea', type: 'text', propertyName: 'description' },
    },
  ],
};

export default Pages;

The two templates illustrate the contrast: Home has composable: false (only one document allowed), while Pages has composable: true (editors can create many). Use composable: false for templates that should only ever have one document — Home, Global Settings, Footer config, etc. — and composable: true for repeatable content. See the templates reference for the full list of template properties.

Warning:

Singleton seeding: for templates with composable: false, the single document needs to exist before your site can render it. Open Strife Studio → your team → Content, create a new Home document, and set its url to /.

Future Strife versions auto-provision the root Home document when a team is created — so this specific step disappears. Other singleton templates you add later (Global Settings, Footer, etc.) follow the same manual-seed pattern and aren't auto-provisioned.


4. Register your Collection

Import your collection and register it in your Strife integration at astro.config.mjs

JavaScript
import { defineConfig } from 'astro/config';
import strife from '@strifeapp/astro';
import Home from './src/collections/Home';
import Pages from './src/collections/Pages';

export default defineConfig({
  integrations: [strife({
    collections: [Home, Pages]
  })],
  output: 'server',
});

5. Create your Content Types

Define TypeScript interfaces matching each collection for strong typing when querying:

TypeScript
import { type Content } from '@strifeapp/types';

export type Home = Content & {
  heading: string;
  description: string;
};

export type Page = Content & {
  heading: string;
  description: string;
};

In real projects these two might diverge — Home could have its own hero structure, Pages might be more flexible — but for the tutorial they share the same shape.


6. Add your Strife Credentials

Create a .env file in your project root with the credentials provided for your team:

Terminal
# .env

# RavenDB client certificate — base64-encoded PFX bundle
STRIFE_CERTIFICATE=...

# Password protecting the PFX certificate above
STRIFE_CERTIFICATE_PASSWORD=...

# Comma-separated list of RavenDB node URLs
STRIFE_DATABASE_URLS=https://a.example.strife.ravendb.cloud

# Name of your team's RavenDB database
STRIFE_DATABASE=...

# Your team's unique identifier
TEAM_ID=...

# Signing secret used for preview and webhook payloads
SECRET=...
Note:

These six values are provided during your Strife onboarding. The same cert-based authentication model is used by the Node.js SDK — if you've set that up before, the values are the same.

If you're using Strife CLI (npx strife / strife .), you won't need to set these six individually — the CLI packs the same information into a single STRIFE_SECRET value and writes it to .env for you. Both forms are read by the integration; use whichever your provisioning path gave you.

Warning:

Treat every value above as a secret. Never commit .env to version control — add it to .gitignore if npm create astro@latest didn't do so already.


7. Enable TypeScript Module Declarations

To support type-safe access to Strife's document store, update or create src/env.d.ts (recent Astro starters auto-generate types in .astro/ and don't ship a stub env.d.ts):

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

declare module 'strife:store' {
  import type { IDocumentSession, IDocumentStore } from 'ravendb';
  export const store: IDocumentStore;
  export { IDocumentSession };
}

8. Query Content from Strife

You can now query your content directly from RavenDB via Strife. The same query pattern works for both singleton and multi-instance templates — it matches by URL:

TypeScript
import { store } from 'strife:store';
import type { Home } from '../collections/Home';

const session: IDocumentSession = store.openSession();

// Returns the Home document for url='/'.
// For other URLs ('/about', '/contact', etc.), the same query returns
// whichever Page document has that url.
const home: Home = (await session
  .query({ indexName: 'Content/ByUrl' })
  .whereEquals('url', '/')
  .selectFields(['heading', 'description'])
  .firstOrNull()) as Home;

9. Deploy your templates

Warning:

Earlier versions of the integration deployed templates and the content index automatically, on the first request to a route that imported strife:store. That's no longer the case — the integration only connects to the database and reads content now. Deployment is explicit, via the CLI, as described below.

Templates and the Content/ByUrl query index are deployed with Strife CLI:

Terminal
npm install --save-dev @strifeapp/cli @strifeapp/strife
npx strife login
npx strife link

The templates you registered as Template objects in step 4 aren't picked up by strife push directly — push deploys schemas written with defineType. If you're following this tutorial's Template-object approach, create your templates directly in Strife Studio instead (Settings → Templates) rather than via push, or see the CLI reference for defining them in code as defineType schemas instead of the Template objects used in steps 3–4.

Once your templates exist (via Studio or defineType + push), start the dev server as usual:

Terminal
npm run dev

and visit the URL Astro prints (e.g. http://localhost:4321/).


10. Add live preview (optional)

Strife Studio includes a preview iframe that mirrors your site as content is edited. With the integration installed, your islands can subscribe to updates:

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

  let { initial } = $props<{ initial: { heading: string; description: string } }>();
  let data = $state(initial);

  onMount(() => subscribe((next) => { data = next; }));
</script>

<h1>{data.heading}</h1>
<p>{data.description}</p>

This requires loading @strifeapp/strife eagerly in your root layout (otherwise the iframe handshake completes before subscribers are ready). For full setup, the data-shape reference, and framework-agnostic patterns, see Custom Live Preview implementation.


Snapshots (optional)

Editors can hand a reviewer without a Strife account a preview link — https://example.com/snapshot/{token} — that shows a frozen, unpublished version of a document on your site for 7 days. The integration's snapshot middleware resolves the link; you add the page that renders it. Copy the package's templates/snapshot-page.astro to src/pages/snapshot/[token].astro:

Astro
---
export const prerender = false;              // rendered on demand — never prebuilt
import Layout from '../../layouts/Layout.astro';

const { name, expiresAt, locale, content } = Astro.locals.snapshot!;
---
<Layout title={content.displayName ?? 'Preview'}>
  <aside role="status">{name ? `Preview “${name}”` : 'Preview'} — expires {expiresAt.toISOString()}</aside>
  <h1>{content.heading}</h1>
</Layout>

Expired, revoked or unknown links render your 404 page. The route needs a Content/ByUrl index built from the @strifeapp/strife release that ships snapshots (run strife push after updating), and the name must be rendered as text only. Locale prefixes, snapshot.fields, sites that resolve locale in their own middleware, and the CDN/analytics rules are in the Snapshots reference.


11. Choose a deployment adapter

astro dev works out of the box. For production builds (astro build), Astro needs an adapter that matches your deploy target. Strife doesn't require a specific one — pick whichever suits your hosting:

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

See Astro's deploy guides for the full list and configuration details.

After installing an adapter, your astro.config.mjs will look something like:

JavaScript
import { defineConfig } from 'astro/config';
import strife from '@strifeapp/astro';
import node from '@astrojs/node';           // or vercel, netlify, cloudflare…
import Homes from './src/collections/Homes';

export default defineConfig({
  integrations: [strife({ collections: [Homes] })],
  output: 'server',
  adapter: node({ mode: 'standalone' }),     // adapter-specific options vary
});

Push your repository to your chosen host and your Strife-powered Astro site is live.