Preview and drafts

Seeing unpublished content, and sharing it with people outside.

The preview handshake

The editor's Preview button opens {your site}/api/contentbind-preview?token=...&redirect=... - a route your app provides. It stores the short-lived token in a cookie and redirects to the page. Without it, Preview lands on a 404.

App Router:

tsx
// app/api/contentbind-preview/route.ts
import { NextResponse, type NextRequest } from 'next/server'

export function GET(request: NextRequest) {
  const token = request.nextUrl.searchParams.get('token')
  const redirect = request.nextUrl.searchParams.get('redirect') ?? '/'
  if (!token) return NextResponse.json({ error: 'token required' }, { status: 400 })
  const target = redirect.startsWith('/') ? redirect : '/'
  const response = NextResponse.redirect(new URL(target, request.url), 303)
  response.cookies.set('contentbind_preview', token, {
    httpOnly: true, sameSite: 'lax', maxAge: 900, path: '/',
  })
  return response
}

Pages Router:

tsx
// pages/api/contentbind-preview.ts
import type { NextApiRequest, NextApiResponse } from 'next'

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  const token = typeof req.query.token === 'string' ? req.query.token : ''
  const redirect = typeof req.query.redirect === 'string' && req.query.redirect.startsWith('/')
    ? req.query.redirect : '/'
  if (!token) return res.status(400).json({ error: 'token required' })
  res.setHeader('Set-Cookie',
    `contentbind_preview=${encodeURIComponent(token)}; Path=/; Max-Age=900; HttpOnly; SameSite=Lax; Secure`)
  res.redirect(303, redirect)
}

The page being previewed must read the cookie on the server - statically generated pages cannot see it. In the Pages Router that means getServerSideProps (or checking req.cookies.contentbind_preview); in the App Router, cookies() as below.

Draft mode

tsx
import { fetchOneEntry, isPreviewing, getPreviewToken } from '@contentbind/sdk'
import { cookies } from 'next/headers'

const jar = await cookies()
const entry = await fetchOneEntry({
  model: 'page',
  publicKey: KEY,
  urlPath,
  options: {
    preview: isPreviewing(jar),
    previewToken: getPreviewToken(jar),
  },
})
if (isContentBindError(entry)) return null

With preview set, delivery returns the draft and skips the cache entirely.

A preview link lets someone read a draft with no account: a designer, a legal reviewer, a client. Links can carry a password and an expiry, and can be revoked. The token grants one entry, not a workspace: it cannot be pointed at another page.

All documentation · llms.txt · llms-full.txt · contentbind.com