Dyrected
Integrations

Next.js Integration

Complete guide to using Dyrected in a Next.js App Router project.

Dyrected integrates natively with the Next.js App Router via a catch-all API route. You can fetch data server-side, client-side, or use the SDK in Server Components.


Installation

pnpm add @dyrected/core @dyrected/sdk @dyrected/next @dyrected/db-postgres

[!NOTE] The @dyrected/next package provides the specialized <DyrectedAdmin /> component and utilities for seamless Next.js App Router integration.


Step 1 — Create your config

// dyrected.config.ts (project root)
import { defineConfig } from '@dyrected/core'
import { PostgresAdapter } from '@dyrected/db-postgres'

export default defineConfig({
  db: new PostgresAdapter({ url: process.env.DATABASE_URL! }),
  collections: [
    {
      slug: 'posts',
      admin: { useAsTitle: 'title' },
      fields: [
        { name: 'title', type: 'text', required: true },
        { name: 'slug',  type: 'text', required: true, unique: true },
        { name: 'body',  type: 'richText' },
        {
          name: 'status',
          type: 'select',
          options: ['draft', 'published'],
          defaultValue: 'draft',
        },
      ],
    },
  ],
})

Step 2 — Mount the API route

Create a catch-all route that forwards all Dyrected requests to the Hono router:

// app/dyrected/[...route]/route.ts
import { createDyrectedApp } from '@dyrected/core/server'
import config from '@/dyrected.config'

const app = createDyrectedApp(config)

export const GET    = app.fetch
export const POST   = app.fetch
export const PATCH  = app.fetch
export const DELETE = app.fetch

All Dyrected REST endpoints are now available at /dyrected/....


Step 3 — Fetch data in Server Components

// app/blog/page.tsx
import { createClient } from '@dyrected/sdk'

const client = createClient({
  baseUrl: process.env.NEXT_PUBLIC_DYRECTED_URL!,
  apiKey: process.env.DYRECTED_API_KEY!,
})

export default async function BlogPage() {
  const { docs: posts } = await client.collection('posts').find({
    where: { status: { equals: 'published' } },
    sort: '-createdAt',
    depth: 1,
  })

  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

When Dyrected is co-located with your Next.js app (self-hosted), you can bypass HTTP entirely:

// app/blog/page.tsx
import config from '@/dyrected.config'

export default async function BlogPage() {
  const { docs: posts } = await config.db.find({
    collection: 'posts',
    where: { status: { equals: 'published' } },
    limit: 10,
  })

  return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>
}

For environments where the SDK isn't available, use raw HTTP:

// app/blog/page.tsx
export default async function BlogPage() {
  const res = await fetch(
    `${process.env.NEXT_PUBLIC_DYRECTED_URL}/collections/posts?where[status][equals]=published`,
    { 
      headers: { 'x-api-key': process.env.DYRECTED_API_KEY! },
      next: { revalidate: 3600 } 
    }
  )
  const { docs: posts } = await res.json()

  return (
    <ul>
      {posts.map((post: any) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

This approach has zero network overhead and is the fastest option for self-hosted setups.


Step 4 — Embed the Admin UI

Create a catch-all page at app/admin/[[...route]]/page.tsx. This ensures that the root /admin and all internal admin sub-routes are handled by this single page.

The @dyrected/next package provides a <DyrectedAdmin /> component that handles all the Next.js router integration, CSS imports, and "Client Only" mounting for you.

// app/admin/[[...route]]/page.tsx
import { DyrectedAdmin } from '@dyrected/next/admin'

export default function AdminPage() {
  return (
    <DyrectedAdmin 
      basename="/admin"
    />
  )
}

[!TIP] DyrectedAdmin automatically reads your environment variables (like NEXT_PUBLIC_DYRECTED_URL) if they follow the standard naming convention. You only need to pass props if you want to override the defaults.


Environment Variables

# .env.local
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb

# Dyrected API base URL (the Next.js app URL + /dyrected)
NEXT_PUBLIC_DYRECTED_URL=http://localhost:3000/dyrected

# Server-side API key (never expose to browser)
DYRECTED_API_KEY=sk_live_...

# Client-side keys (safe to expose — access is controlled by Dyrected access functions)
NEXT_PUBLIC_DYRECTED_API_KEY=pk_live_...
NEXT_PUBLIC_SITE_ID=site_...

ISR Cache Revalidation

When content is published, you often want to revalidate cached Next.js pages. Use an afterChange hook to call revalidatePath or revalidateTag:

// dyrected.config.ts
import { revalidatePath } from 'next/cache'

{
  slug: 'posts',
  hooks: {
    afterChange: [
      async ({ doc, operation }) => {
        if (doc.status === 'published') {
          revalidatePath('/blog')
          revalidatePath(`/blog/${doc.slug}`)
        }
      }
    ]
  }
}

Or use webhook-style revalidation via a dedicated endpoint:

// app/api/revalidate/route.ts
import { revalidatePath } from 'next/cache'
import { NextRequest } from 'next/server'

export async function POST(req: NextRequest) {
  const secret = req.headers.get('x-revalidate-secret')
  if (secret !== process.env.REVALIDATE_SECRET) {
    return new Response('Unauthorized', { status: 401 })
  }
  const { path } = await req.json()
  revalidatePath(path)
  return Response.json({ revalidated: true })
}

Live Preview

Add live preview support to any page with the useLivePreview hook:

// app/blog/[slug]/preview/page.tsx
'use client'
import { useLivePreview } from '@dyrected/react'

export default function BlogPreview({ initialPost }: { initialPost: Post }) {
  const { data: post, isLive } = useLivePreview({
    initialData: initialPost,
    serverURL: process.env.NEXT_PUBLIC_DYRECTED_ADMIN_URL!,
  })

  return (
    <article>
      {isLive && <div className="preview-badge">Preview Mode</div>}
      <h1>{post.title}</h1>
    </article>
  )
}

Enable it on the collection:

{
  slug: 'posts',
  admin: {
    previewUrl: (doc) =>
      doc?.slug ? `${process.env.NEXT_PUBLIC_SITE_URL}/blog/${doc.slug}/preview` : null,
  }
}

See Live Preview for the full guide.


Middleware & Route Protection

Protect your admin route with Next.js middleware:

// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  if (request.nextUrl.pathname.startsWith('/admin')) {
    const token = request.cookies.get('dyrected-token')?.value
    if (!token) {
      return NextResponse.redirect(new URL('/admin/login', request.url))
    }
  }
  return NextResponse.next()
}

export const config = {
  matcher: ['/admin/:path*'],
}

On this page