Next.js (Pages Router)
Build a statically generated blog with getStaticPaths and getStaticProps.
The Pages Router and PMKIN make a classic static blog: every post is prerendered at build time, and incremental static regeneration (ISR) picks up what you publish afterwards. Your delivery key only ever runs in getStaticProps, on the server.
Follow the Next.js App Router guide instead.
Install the SDK
npm install pmkinAdd your key
Create a delivery key on your project's API page in PMKIN and put it in
.env.local..env.localPMKIN_API_KEY=your_delivery_keyCreate the client
lib/pmkin.tsimport { PmkinClient } from 'pmkin'const token = process.env.PMKIN_API_KEYif (!token) {throw new Error('PMKIN_API_KEY is not set. Add it to .env.local.')}export const pmkin = new PmkinClient({ token })List your documents
getStaticPropsruns at build time and, withrevalidate, again in the background when the page is older than a minute.pages/blog/index.tsximport type { GetStaticProps } from 'next'import Link from 'next/link'import type { DocumentListing } from 'pmkin'import { pmkin } from '@/lib/pmkin'interface BlogProps {documents: DocumentListing[]}export const getStaticProps: GetStaticProps<BlogProps> = async () => {const documents = await pmkin.listDocuments()return {props: { documents },// Regenerate the page in the background at most once a minute.revalidate: 60}}export default function Blog({ documents }: BlogProps) {return (<main><h1>Blog</h1>{documents.map((document) => (<article key={document.id}><h2><Link href={`/blog/${document.slug}`}>{document.title}</Link></h2><p>{document.excerpt}</p></article>))}</main>)}Render a document
getStaticPathslists the slugs to prerender. Withfallback: 'blocking', a slug that wasn't there at build time renders on its first request. If PMKIN doesn't know the slug, returnnotFound: truefor a 404.pages/blog/[slug].tsximport type { GetStaticPaths, GetStaticProps } from 'next'import Head from 'next/head'import type { Document } from 'pmkin'import { pmkin } from '@/lib/pmkin'interface BlogPostProps {document: Document}export const getStaticPaths: GetStaticPaths = async () => {const documents = await pmkin.listDocuments()return {paths: documents.map((document) => ({ params: { slug: document.slug } })),// Render documents published after the build on their first visit.fallback: 'blocking'}}export const getStaticProps: GetStaticProps<BlogPostProps, { slug: string }> = async ({params}) => {const document = await pmkin.findDocumentBySlug(params?.slug ?? '')if (!document) {return { notFound: true, revalidate: 60 }}return { props: { document }, revalidate: 60 }}export default function BlogPost({ document }: BlogPostProps) {return (<><Head><title>{document.metaTitle || document.title}</title><metaname="description"content={document.metaDescription || document.excerpt}/></Head><article className="prose"><h1>{document.title}</h1><div dangerouslySetInnerHTML={{ __html: document.html }} /></article></>)}
Rendering the HTML#
html is sanitized by PMKIN: scripts, iframes, event handlers and inline styles are removed before it reaches you, so it's safe to render with dangerouslySetInnerHTML. If you'd rather map elements to your own components, render markdown with a library like react-markdown.
Publish instantly with on-demand ISR#
To skip the minute of waiting, add an API route that revalidates the list and the post, and call it after you publish:
import type { NextApiRequest, NextApiResponse } from 'next'export default async function handler(request: NextApiRequest, response: NextApiResponse) {if (request.headers['x-revalidate-secret'] !== process.env.REVALIDATE_SECRET) {return response.status(401).json({ message: 'Unauthorized' })}const slug = String(request.query.slug ?? '')await response.revalidate('/blog')if (slug) {await response.revalidate(`/blog/${slug}`)}return response.json({ revalidated: true })}
Next steps#
- See every field you can use in types.
- Preview drafts with includeDrafts.
- Learn what the SDK throws in the TypeScript SDK guide.