Remix
Load PMKIN documents in Remix loaders.
Remix loaders run on the server, so they're the perfect place to talk to PMKIN: your key stays private, and pages arrive fully rendered. (This very site is a Remix app that reads its docs from PMKIN.)
Install the SDK
npm install pmkinAdd your key
Create a delivery key on your project's API page in PMKIN and add it to
.env..envPMKIN_API_KEY=your_delivery_keyCreate the client
The
.serversuffix keeps the module, and your key, out of the browser bundle.app/pmkin.server.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.')}export const pmkin = new PmkinClient({ token })List your documents
app/routes/blog._index.tsximport { json } from '@remix-run/node'import { Link, useLoaderData } from '@remix-run/react'import { pmkin } from '~/pmkin.server'export async function loader() {const documents = await pmkin.listDocuments()return json({ documents },// Let a CDN serve the list for a minute, then refresh it in the background.{ headers: { 'Cache-Control': 'public, max-age=60, stale-while-revalidate=600' } })}export default function Blog() {const { documents } = useLoaderData<typeof loader>()return (<main><h1>Blog</h1>{documents.map((document) => (<article key={document.id}><h2><Link prefetch="intent" to={`/blog/${document.slug}`}>{document.title}</Link></h2><p>{document.excerpt}</p></article>))}</main>)}Render a document
Throw a 404
Responsewhen the slug isn't found, so search engines see a real 404 instead of an empty page.app/routes/blog.$slug.tsximport { json, type LoaderFunctionArgs, type MetaFunction } from '@remix-run/node'import { useLoaderData } from '@remix-run/react'import { pmkin } from '~/pmkin.server'export async function loader({ params }: LoaderFunctionArgs) {const document = await pmkin.findDocumentBySlug(params.slug ?? '')if (!document) {throw new Response('Not found', { status: 404 })}return json({ document },{ headers: { 'Cache-Control': 'public, max-age=60, stale-while-revalidate=600' } })}export const meta: MetaFunction<typeof loader> = ({ data }) => {if (!data) {return [{ title: 'Not found' }]}const { document } = datareturn [{ title: document.metaTitle || document.title },{ name: 'description', content: document.metaDescription || document.excerpt }]}export default function BlogPost() {const { document } = useLoaderData<typeof loader>()return (<article className="prose"><h1>{document.title}</h1><div dangerouslySetInnerHTML={{ __html: document.html }} /></article>)}Handle errors
Add an error boundary to the same route for the 404 and for the rare failed request:
app/routes/blog.$slug.tsximport { isRouteErrorResponse, useRouteError } from '@remix-run/react'export function ErrorBoundary() {const error = useRouteError()if (isRouteErrorResponse(error) && error.status === 404) {return <h1>We couldn’t find that post.</h1>}return <h1>Something went wrong loading this post. Try again in a minute.</h1>}
Rendering the HTML#
PMKIN sanitizes html before it reaches you: scripts, iframes, event handlers and inline styles are removed. Render it with dangerouslySetInnerHTML and style it with the Tailwind Typography plugin's prose class, or render markdown yourself if you want custom components.
Caching#
Published content changes rarely, so cache it. The Cache-Control headers above let a CDN serve pages for a minute and refresh them in the background, which keeps you far from the rate limit even under heavy traffic.
Don't cache draft previews. If you fetch with includeDrafts, send Cache-Control: private, no-store.