Adding Keystatic to a Next.js project
This guide assumes you are trying to add Keystatic to an existing Next.js 13 project.
Installing dependencies
We're going to need to install a few packages to get Keystatic going.
Let's install two packages from npm:
npm install @keystatic/core @keystatic/next
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.ts
(or .js
if not using TypeScript) in the root of the project:
// keystatic.config.ts
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
The Keystatic Admin UI runs in NextJS' app directory.
In your src/app
directory, we want every route within the /keystatic
segment to become a Keystatic Admin UI route.
Create a keystatic
folder in your src/app
directory, and add the following files to help with our keystatic admin layout.
// src/app/keystatic/keystatic.ts
"use client";
import { makePage } from "@keystatic/next/ui/app";
import config from "../../keystatic.config";
export default makePage(config);
// src/app/keystatic/layout.tsx
import KeystaticApp from "./keystatic";
export default function RootLayout() {
return (
<html>
<head />
<body>
<KeystaticApp />
</body>
</html>
);
}
To ensure all routes in the keystatic segment work as expected, we can leverage Next.js' Optional Catch-all Segments to match file paths of any depth.
Let's create a new folder called [[...params]]
, and add a page.tsx
file with the following:
// src/app/keystatic/[[...params]]/page.tsx
export default function Page() {
return null;
}
Before we can actually start reading and writing content via the Keystatic Admin UI, we need to create some API routes
for Keystatic.
Keystatic API Routes
Create a new file at app/api/keystatic/[...params]/route.ts
Once again, we use Next.js's dynamic route segments here.
// src/app/api/keystatic/[...params]/route.ts
import { makeRouteHandler } from '@keystatic/next/route-handler';
import config from '../../../../../keystatic.config';
export const { POST, GET } = makeRouteHandler({
config,
});
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. As it is a Node API it must be run server-side.
// 1. Create a reader
const reader = createReader(process.cwd(), config);
// 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.
Where you call the reader
API in your app will depend on whether you are building your site in Next.js' pages
or app
directory.
Data-fetching in the app directory
When building pages in Next.js' app
directory, getting the data from the reader
API can be done directly in a server-component.
// src/app/page.tsx
import { createReader } from '@keystatic/core/reader';
import config from '../../keystatic.config';
// 1. Create a reader
const reader = createReader(process.cwd(), config);
export default async function Index() {
// 2. Read the "Homepage" singleton
const homepageData = await reader.singletons.homepage.read();
return (
<main>
<h1>{homepageData?.headline}</h1>
</main>
);
}
Data-fetching in the pages directory
When building pages in Next.js' pages
directory, getting the data from the reader
API must be done in the getStaticProps
function for statically generated sites, or in getServerSideProps
for apps running on a server.
// src/pages/index.tsx
import { createReader } from '@keystatic/core/reader';
import config from '../../keystatic.config';
import { InferGetStaticPropsType } from 'next';
// 1. Create a reader
const reader = createReader(process.cwd(), config);
export async function getStaticProps() {
// 2. Read the "Homepage" singleton
const homepageData = await reader.singletons.homepage.read();
// 3. Return the homepage data as props
return {
props: { homepageData },
};
}
export default function Index({
homepageData,
}: InferGetStaticPropsType<typeof getStaticProps>) {
return (
<main>
<h1>{homepageData?.headline}</h1>
</main>
);
}