Adding Keystatic to an Astro project

This guide assumes you are trying to add Keystatic to an existing Astro v2 project.

Here, I've created a brand new Astro project, using the Include sample files option:

Installing dependencies

We're going to need to install a few packages to get Keystatic going.

Keystatic has a dependency on React, so first we need to add Astro's React integration:

npx astro add react

Let's install two packages from npm:

npm install @keystatic/core @keystatic/astro

Creating a Keystatic config file

Keystatic needs a config file. This is where you can connect a project with a specific GitHub repository and define a content schema.

Let's create a file called keystatic.config.tsx (or .jsx if not using TypeScript) in the root of the project:

// keystatic.config.tsx
import { config, fields, singleton } from '@keystatic/core'

export default config({
  storage: {
    kind: 'local',
  },
  singletons: {
    homepage: singleton({
      label: 'Homepage',
      path: 'src/content/_homepage',
      schema: {
        headline: fields.text({ label: 'Headline' }),
      },
    }),
  },
})

We export a config object wrapped in the config function imported from @keystatic/core.

For now, we set the storage strategy to local, and we create a “homepage” singleton which contains one text field: headline.

This is all Keystatic needs to start managing content, config wise.

Now, we need to display the Keystatic Admin UI in our site!


Keystatic Admin UI pages

In our pages directory, we want every route within the /keystatic segment to become a Keystatic Admin UI route.

We can leverage Astro's Rest parameters to match file paths of any depth.

Let's create a new page called keystatic/[...params].astro:

---
// src/pages/keystatic/[...params].astro

// TODO: Display Keystatic pages
---

The Keystatic Admin UI is built with React. So that we can import and mount this app here with React, let's create a new file outside of the pages directory.

I'll put it in the project root, alongside our Keystatic config file.

// keystatic.page.tsx
import { makePage } from '@keystatic/astro/ui'
import keystaticConfig from './keystatic.config'

export const Keystatic = makePage(keystaticConfig)

Now, we can import that file in our keystatic/[...params].astro page, and mount the component on the clientside only:

---
// src/pages/keystatic/[...params].astro
import { Keystatic } from '../../../keystatic.page'
---

<Keystatic client:only />

Great! So now, we should be able to visit the /keystatic router in the browser.

Oh no.

If you tried, you're getting this error:

getStaticPaths() function is required for dynamic routes. Make sure that you export a getStaticPaths function from your dynamic route.

Two lines further, we get this nugget of information:

Alternatively, set output: "server" in your Astro config file to switch to a non-static server build.

Let's do that!

Turning on SSR mode

In the astro.config.mjs, add the output: 'server' option:

export default defineConfig({
+  output: 'server',
  integrations: [react()],
})

Now, visit the /keystatic route once again in your browser:

Our Keystatic Admin UI is here! 🎉

We also have a “Homepage” singleton, since we created that in our Keystatic config.

Before we can use this, we also need to create some API routes for Keystatic

Keystatic API Routes

In the pages directory, create a new file called api/keystatic/[...params].ts

Once again, we use Astro's Rest parameters here.

// src/pages/api/keystatic/[...params].ts
import { makeHandler } from '@keystatic/astro/api'
import keystaticConfig from '../../../../keystatic.config'

export const all = makeHandler({
  config: keystaticConfig,
})

We should be all set to go for our Keystatic Admin UI now.

Try and visit the /keystatic page in the browser one more time, and click on the “Homepage” singleton:

It's working: our “Headline” text field is here.

Go ahead and fill that field with something, and hit Create:

Check your source code again. Notice anything?

In our Keystatic config file, we have set the storage kind to local. For our homepage singleton, we set the path property to the following:

path: 'src/content/_homepage',

If you look in the src directory, a new content folder should have been created.

Inside, you'll find a _homepage directory.

That directory contains an index.yaml file with… the headline text you've just created!

Niiiice ✨

Let's try display that on the frontend now.


Displaying Keystatic content on the frontend

Keystatic comes with its own reader Node API to bring content to the front end.

Inside of the site's homepage, let's use that API to get the headline field value:

---
// src/pages/index.astro
import { createReader } from '@keystatic/core/reader'

// 1. Create a reader
const reader = createReader('', keystaticConfig)

// 2. Read the "Homepage" singleton
const homepageData = await reader.singletons.homepage.read()
---

This homepageData will give us a JSON object with all the fields inside the homepage singleton.

Actually, there is only one field for now.

Try and replace the homepage's h1 tag with your homepage headline from Keystatic:

<h1>{homepageData.headline}</h1>

Beautiful!