Dyrecteddyrected
Integrations

Nuxt 3 Integration

Complete guide to using Dyrected in a Nuxt 3 application with SSR and composables.

The @dyrected/nuxt module provides first-class Nuxt 3 support: automatic server handler registration, SSR-aware composables, and live preview support.

Easiest path: paste the Dyrected setup prompt into an AI coding tool with access to your repo — it runs the CLI, models your content as collections and globals, and mounts the admin for you.

Setting up manually? The CLI is still the safest way to install — it detects your project layout, installs dependencies, writes the admin page, and patches nuxt.config.ts for you.

npx dyrected init

The steps below cover manual setup and all available options.


Installation

pnpm add @dyrected/nuxt @dyrected/sdk

Step 1 — Configure the module

Dyrected can be run in two modes: Cloud (Managed) or Self-Hosted (Core).

Option A: Cloud Mode (Managed)

Use this if you are using Dyrected Cloud to host your backend.

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ["@dyrected/nuxt"],
  dyrected: {
    // These are optional if you have NUXT_PUBLIC_DYRECTED_* env vars defined!
    // apiKey: 'sk_live_...',
    // siteId: 'site_...',
    // baseUrl: 'https://api.dyrected.cloud',
  },
});

Option B: Self-Hosted Mode (Core)

Use this if you want to run the Dyrected engine directly inside your Nuxt app. We recommend defining your schema in a separate dyrected.config.ts file.

// nuxt.config.ts
import config from "./dyrected.config";

export default defineNuxtConfig({
  modules: [["@dyrected/nuxt", { adminPath: "admin" }]],
  dyrected: {
    ...config,
    apiBase: "/api",
  },
});

The module automatically handles build.transpile and vite.optimizeDeps for React and the admin bundle — no manual configuration needed.

The adminPath option tells the module where your admin page lives, so it can print the URL when the dev server starts:

  ➜  Dyrected admin:  http://localhost:3000/admin
  ➜  Dyrected API:    http://localhost:3000/api

And your dyrected.config.ts (using SQLite as an example):

// dyrected.config.ts
import { defineConfig } from "@dyrected/core";
import { SqliteAdapter } from "@dyrected/db-sqlite";

export default defineConfig({
  db: new SqliteAdapter({
    filename: "dyrected.db",
  }),
  collections: [
    // ... your collections
  ],
});

For Cloud mode, you should use framework-prefixed environment variables. The Nuxt module will automatically pick these up:

# .env
NUXT_PUBLIC_DYRECTED_URL=https://api.dyrected.cloud
NUXT_PUBLIC_DYRECTED_API_KEY=sk_live_...
NUXT_PUBLIC_DYRECTED_SITE_ID=site_...

If you are using Self-Hosted mode, you'll also need your database and secret keys:

# .env
DATABASE_URL=postgres://...
JWT_SECRET=...
ENCRYPTION_KEY=...

# Standard prefixes work too
NUXT_PUBLIC_DYRECTED_URL=http://localhost:3000

Step 2 — Fetch data with composables

useDyrected

Returns the configured DyrectedClient instance. Use it to call any SDK method.

<script setup lang="ts">
// useDyrected() returns the raw SDK client
const client = useDyrected();

// Fetch a list of posts
const { data: posts } = await useAsyncData("posts", () =>
  client.collection("posts").find({
    where: { status: { equals: "published" } },
    sort: "-createdAt",
    depth: 1,
    limit: 10,
  }),
);
</script>

<template>
  <ul>
    <li v-for="post in posts?.docs" :key="post.id">
      {{ post.title }}
    </li>
  </ul>
</template>

Fetch a single document

<script setup lang="ts">
const route = useRoute();
const client = useDyrected();

const { data: post } = await useAsyncData(`post-${route.params.id}`, () =>
  client.collection("posts").findOne(route.params.id as string, { depth: 1 }),
);
</script>

useDyrectedDoc(collection, id, options?)

A convenience shortcut for fetching a single document by ID. It wraps useAsyncData, so destructure { data } from the result:

<script setup lang="ts">
const route = useRoute();
const { data: post } = await useDyrectedDoc("posts", route.params.id as string, { depth: 1 });
</script>

Fetch a global

<script setup lang="ts">
const client = useDyrected();
const { data: settings } = await useAsyncData("site-settings", () => client.global("site-settings").get());
</script>

Step 3 — Mutating data

For create/update/delete operations, call methods on the client directly:

<script setup lang="ts">
const client = useDyrected();

async function submitForm(data: any) {
  await client.collection("inquiries").create(data);
}
</script>

All Available Composables

ComposableStatusDescription
useDyrected()✅ AvailableReturns the configured DyrectedClient
useDyrectedDoc(slug, id, opts?)✅ AvailableShortcut for client.collection(slug).findOne(id)
useDyrectedGlobal(slug)✅ AvailableWraps client.global(slug).get() with useAsyncData
useDyrectedAuth(collection)✅ AvailableReturns login(), logout(), user, isLoggedIn
useLivePreview(options)✅ AvailableLive preview postMessage integration
useDyPath(field?)✅ AvailableReturns data-dy-path attrs for click-to-edit
provideDyPath(path)✅ AvailableScopes the base path for a section subtree

Auto-imported components

The module registers these components globally — use them in any template with no import:

ComponentPurpose
<DyrectedBlocks>Renders block arrays by blockType with click-to-edit paths
<DyrectedMedia>Renders image / video / YouTube / download by media type
<DyrectedIcon>Renders an icon field value (a Lucide icon name) as an SVG icon
<DyrectedAdmin>Embeds the Admin UI
<template>
  <!-- feature.icon === "ChartNoAxesCombined" -->
  <DyrectedIcon :name="feature.icon" class="w-6 h-6 text-primary" />
</template>

Every non-Nuxt-specific export (including <DyrectedImage>) is also available from @dyrected/vue — see the Vue integration for the full component reference.


Authentication

The useDyrectedAuth(collection) composable provides everything you need to manage user sessions. Pass the slug of your auth-enabled collection:

<script setup lang="ts">
const { login, logout, user, isLoggedIn } = useDyrectedAuth("users");

async function handleLogin() {
  await login("[email protected]", "my-password");
  navigateTo("/dashboard");
}
</script>

Live Preview

Enable real-time synchronization with the Admin UI using useLivePreview:

<script setup lang="ts">
const route = useRoute();
const config = useRuntimeConfig();

// Fetch initial data
const { data: initialData } = await useAsyncData("page", () =>
  useDyrected().collection("pages").findOne(route.params.slug),
);

// Sync with Admin UI
const { data: page, isLive } = useLivePreview({
  initialData: initialData.value,
  serverURL: config.public.dyrectedAdminUrl,
});
</script>

Click-to-edit

useDyPath and <DyrectedBlocks> are auto-imported. Annotate fields so clicking them in the preview focuses the field in the Admin — <DyrectedBlocks> supplies each block's base path, so components only reference their own field names:

<!-- pages/[...slug].vue -->
<script setup lang="ts">
import { defineAsyncComponent } from "vue";
const blockComponents = { hero: defineAsyncComponent(() => import("~/components/blocks/Hero.vue")) };
const { data: page } = useLivePreview({ initialData: null });
</script>

<template>
  <DyrectedBlocks v-if="page" :items="page.layout" :components="blockComponents" path="layout" />
</template>
<!-- components/blocks/Hero.vue -->
<script setup lang="ts">
defineProps<{ heading: string }>();
const dyHeading = useDyPath("heading"); // → data-dy-path="layout.<i>.heading"
</script>

<template>
  <h1 v-bind="dyHeading">{{ heading }}</h1>
</template>

See Live Preview for the full guide.


Step 4 — Embed the Admin UI

The Admin UI is a React-based dashboard. The @dyrected/nuxt module provides a <DyrectedAdmin /> component that handles all the React-to-Vue bridging, mounting, and routing isolation for you.

If you used npx dyrected init, this file is already created in the right place. The CLI detects whether your project uses an app/ directory (Nuxt's srcDir) and writes to the admin path you chose, defaulting to app/pages/admin/index.vue or pages/admin/index.vue.

For manual setup, create the file at pages/admin/index.vue (or app/pages/admin/index.vue if using srcDir):

<!-- pages/admin/index.vue -->
<script setup lang="ts">
definePageMeta({
  layout: false,
});
</script>

<template>
  <ClientOnly>
    <DyrectedAdmin />
  </ClientOnly>
</template>

No additional build.transpile or vite.optimizeDeps config is needed — the module handles all of that automatically.

If you are using Cloud mode and want to connect directly to the Cloud API from the browser (bypassing the proxy), you can pass baseUrl, apiKey, and siteId props to <AdminUI /> .


Server Handler

The module automatically registers a server handler at /dyrected/[...route]. This proxies requests from your Nuxt frontend to your Dyrected backend, injecting the API key server-side so it is never exposed to the browser.

You can customise the prefix:

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ["@dyrected/nuxt"],
  dyrected: {
    apiBase: "/api/cms", // defaults to '/dyrected'
  },
});

Cache Revalidation

Wrap data fetching in useAsyncData so Nuxt can cache and revalidate it:

<script setup lang="ts">
const client = useDyrected();
const { data: posts, refresh } = await useAsyncData("posts", () => client.collection("posts").find());

async function publish(id: string) {
  await client.collection("posts").update(id, { status: "published" });
  await refresh(); // Re-fetch the list
}
</script>

For full-page ISR-style revalidation on a Nuxt server, use Nitro's cache:

// server/api/revalidate.post.ts
export default defineEventHandler(async (event) => {
  await clearNuxtDataCache(event, "posts");
  return { ok: true };
});

TypeScript

The module auto-imports useDyrected and useDyrectedDoc. Pass your type to the SDK methods for type-safe responses:

interface Post {
  id: string;
  title: string;
  slug: string;
  status: "draft" | "published";
}

const client = useDyrected();
const { data } = await useAsyncData("posts", () => client.collection<Post>("posts").find());
// data.value?.docs is Post[]

Transformation Errors (e.g. Unexpected "!")

If you see errors like Unexpected "!" or Transform failed for files in @dyrected/admin, it usually means Vite is trying to parse the TypeScript code as plain JavaScript.

Solution: Ensure you have added @dyrected/admin to build.transpile in your nuxt.config.ts. If problems persist, you may need to force the tsx loader in your Vite config:

export default defineNuxtConfig({
  vite: {
    esbuild: {
      loader: "tsx",
      include: /@dyrected\/admin\/.*\.tsx$/,
    },
  },
});

Hash Routing

For self-hosted deployments embedded in Nuxt, we recommend using Hash Routing. This ensures that the Admin dashboard's internal navigation doesn't interfere with your Nuxt application's URL paths.

The <DyrectedAdmin /> component uses hash routing by default for internal navigation to ensure maximum compatibility.

On this page