Build a dynamic, block-based page builder in Nuxt 3 using catch-all routing and live previews.
Build pages your editors assemble from reusable blocks — a hero, a features grid, a pricing table — and render them on any URL in a Nuxt 3 app, with live preview as they edit. Dyrected stores the content (it's a headless CMS: the content API and the frontend stay separate), and Nuxt's catch-all routing turns each page into a real URL.
There are two ways to get started:
With the CLI (recommended): Use the interactive setup tool to scaffold your config and routes for you. Pick this if you want a working starting point fast.
By hand: Install the packages and write the files yourself. Pick this if you want to understand each piece or you're adding Dyrected to an existing setup.
Dyrected comes with an interactive command-line setup wizard that detects your Nuxt project, asks for your database/storage preferences, installs the correct packages, and writes your config files automatically.
Run this command in the root of your Nuxt project:
Detects your framework: Auto-detects that you are using Nuxt 3.
Dashboard Mount Path: Prompts you for where the CMS editor dashboard should live (defaults to /admin or /cms).
Asks for Database/Storage Adapters:
Quick Setup: Installs SQLite (local file database) and Local Filesystem (local upload storage) — a good choice for local development.
Custom Setup: Allows you to pick from other databases (PostgreSQL, MySQL, MongoDB) and cloud storage providers (S3, Backblaze B2, Cloudinary).
Installs Dependencies: Installs the required @dyrected packages using your project's package manager (npm, pnpm, or yarn).
Writes Configuration Files: Creates dyrected.config.ts and scaffolding code for admin/catch-all routes automatically.
Saves an AI Prompt: Generates a custom dyrected-ai-prompt.md in your project containing your configuration details so you can share it with your AI assistant (e.g. Claude or Gemini) to help build components.
Dyrected CMS relies on Database and Storage adapters to store structure and media files. You must select one database adapter and one storage adapter for your project.
Register the @dyrected/nuxt module in your nuxt.config.ts:
// nuxt.config.tsexport default defineNuxtConfig({ modules: ["@dyrected/nuxt"], dyrected: { apiBase: "/api", // Exposes the public API under /api },});
The BlockRenderer resolves which Vue component should be loaded based on the CMS block type. It also adds the data-dy-path property which Dyrected needs to enable inline highlights in the editor.
This section builds the dispatcher by hand so you can see exactly how block resolution and data-dy-path scoping work. If you'd rather not maintain it yourself, @dyrected/nuxt auto-imports a <DyrectedBlocks> component that does the same dispatch and path scoping for you — see Building a Page Builder for that higher-level approach. The rest of this guide uses the hand-written renderer.
Nuxt uses catch-all routes ([...slug].vue) to load any page created in the CMS. Wrap the page data in the useLivePreview composable and the content updates inside the admin editor panel as you edit.
Create the page at pages/[...slug].vue:
<!-- pages/[...slug].vue --><script setup lang="ts">const route = useRoute();// 1. Resolve slug path - fallback empty slugs to 'home'const slug = computed(() => { const s = Array.isArray(route.params.slug) ? route.params.slug.join("/") : route.params.slug; return s || "home";});// 2. Query CMS database for matching page entryconst { data: response } = await useDyrectedCollection("pages", { where: { slug: { equals: slug.value } }, limit: 1,});const pageData = computed(() => response.value?.docs?.[0]);// 3. Wrap pageData to listen to incoming content updates from CMS editor iframeconst { data: page } = useLivePreview({ initialData: pageData.value,});// 4. Handle 404 for pages not created in the CMSif (!page.value && slug.value !== "home") { throw createError({ statusCode: 404, statusMessage: "Page Not Found" });}// 5. Dynamic SEO metadatauseHead({ title: page.value?.seo?.metaTitle || page.value?.title || "My CMS Website", meta: [ { name: "description", content: page.value?.seo?.metaDescription || "Welcome to my website.", }, ],});</script><template> <main v-if="page"> <BlockRenderer v-for="(block, i) in page.layout" :key="i" :block="block" :index="i" /> </main> <!-- Fallback home page info when database is empty --> <div v-else-if="slug === 'home'" class="min-h-screen flex flex-col items-center justify-center text-center p-6 bg-slate-900 text-white" > <h1 class="text-3xl font-bold mb-4">Welcome to your Dyrected CMS App!</h1> <p class="text-slate-400 mb-6">Log in to the dashboard to create your "home" page.</p> <NuxtLink to="/admin" class="px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded"> Go to Admin Dashboard </NuxtLink> </div></template>