
Security News
Rust 1.100 Will Add Dependency Cooldowns to Cargo
Rust 1.100 will add opt-in dependency cooldowns to Cargo, giving security scanners and maintainers time to catch malicious crate releases.
@mapled/nuxt
Advanced tools
Read published Mapled content in a Nuxt site — a server-side client with a tag cache, the publish webhook (purge the Nitro cache or call a deploy hook), preview, live updates and a Nuxt module that mounts it all.
Read published Mapled content in a Nuxt site — a server-side client with a tag cache, a publish webhook that purges it and Nitro's cache (or rebuilds a static site), preview of drafts, live updates for the tabs that are already open, and a Nuxt module that mounts it all.
Mapled is a hosted headless CMS built for sites created with AI. Your agent (or you) models collections and fills in content; this package is how a Nuxt site reads what's published. It is @mapled/client with Nuxt's idioms around it: the same reads, the same types from mapled types generate.
npm install @mapled/nuxt
Grab the delivery key in Mapled: project settings (the gear in the project's header) → Integrations → Your site, and put it in your site's env with the webhook secret:
MAPLED_KEY=mk_live_…
MAPLED_WEBHOOK_SECRET=whsec_…
Add the module — it mounts the webhook, the preview routes and the release route under /api/mapled, the middleware that keeps draft pages out of caches, the useMapled() composable and the <MapledLive /> component:
// nuxt.config.ts
export default defineNuxtConfig({
modules: ["@mapled/nuxt/module"],
});
The module takes options as ["@mapled/nuxt/module", { prefix: "/api/mapled", browser: true, routes: true, imports: true }]. The delivery key is the one key that may be public: with browser: true (the default) it also goes into runtimeConfig.public.mapled, so a read that runs in the browser after a client-side navigation works; the webhook secret and a server key never leave the server. browser: false keeps the key out of the client bundle and makes useMapled() server-only.
useMapled() is the site's client, typed by the schema npx @mapled/cli types generate writes. Read in useAsyncData, so the render waits for the content:
<!-- pages/index.vue -->
<script setup lang="ts">
import type { MapledSchema } from "~/mapled-types";
const cms = useMapled<MapledSchema>();
const { data: home } = await useAsyncData("home", () => cms.getSingle("homepage"));
const { data: posts } = await useAsyncData("posts", () => cms.getRecords("posts", { sort: "-date", limit: 10 }));
</script>
<template>
<h1>{{ home?.data.headline }}</h1>
<ul>
<li v-for="p in posts?.records" :key="p.id"><NuxtLink :to="`/blog/${p.data.slug}`">{{ p.data.title }}</NuxtLink></li>
</ul>
</template>
<!-- pages/blog/[slug].vue -->
<script setup lang="ts">
const route = useRoute();
const cms = useMapled();
const { data: post } = await useAsyncData(`post:${route.params.slug}`, () => cms.getRecordBySlug("posts", String(route.params.slug)));
if (!post.value) throw createError({ statusCode: 404, statusMessage: "Not found" });
</script>
On the server, useMapled() reads MAPLED_KEY and, when set, MAPLED_API_URL from the environment, and sees the request's preview cookie. The reads are those of @mapled/client: getRecords(collection, { filter, sort, limit, offset, fields, expand, search, locale }), getRecord, getRecordBySlug, getSingle, getRelease, isPreview. Each published answer is kept in the adapter's cache — in the server process, under the tags mapled and mapled:<collection> — for an hour, or for as long as you say:
// server/utils/reads.ts
import { mapled } from "@mapled/nuxt";
export async function homepage() {
const cms = mapled();
// kept until the next publish purges it; `revalidate: 60` keeps it a minute, `revalidate: 0` never
return cms.getSingle("homepage", { revalidate: false, tags: ["layout"] });
}
A page and its layout asking for the same record during one render make one request. The cache is this process's: a long-running server (the Node preset) holds one, serverless functions hold one each — keep revalidate short there, or generate the site statically. Route rules that cache whole pages (isr, swr, cache) sit on top: the webhook drops those too, wherever Nitro keeps them itself (the Node preset, useStorage("cache")). On Vercel, Nitro's preset hands an isr or swr rule to Vercel's own cache instead: the platform answers a hit without running the site, so the webhook can't drop that page and a draft request gets the published one — keep those rules off the pages that read Mapled there. mapled(event) is the client by hand on the server — the h3 event of the request, so drafts are seen — and createClient({ key, apiUrl, draft, cache: false }) builds one from parts, with the cache off when you say so.
rich_text values are Markdown — render them with a Markdown component, never as raw HTML. Image values are asset ids: assetUrl(id, { width, format }) gives the URL of a resized variant.
// utils/images.ts
import { assetUrl } from "@mapled/nuxt";
export const cover = (id: string) => assetUrl(id, { width: 1280, format: "webp" });
Point the project at the webhook the module mounted (Integrations → Your site → Revalidation webhook: https://your-site/api/mapled/revalidate, or let your agent call configure_revalidation). Every publish is signed with MAPLED_WEBHOOK_SECRET; the handler purges the adapter's cache and Nitro's (useStorage("cache"): the pages route rules cached, defineCachedEventHandler, defineCachedFunction), so the next render reads the new release.
A prerendered site can't re-render on its own: set MAPLED_DEPLOY_HOOK_URL to your host's build hook (Vercel, Netlify, Cloudflare Pages…) and the same handler calls it once per publish — a retry of the same publish doesn't build twice, and a hook that refuses fails the delivery so Mapled retries. The URL is read from the environment on the first webhook and never written anywhere. The handler needs a server to run on: prerender the pages with route rules (routeRules: { "/": { prerender: true } }) and nuxt build, which keeps /api/mapled/* as server routes. nuxt generate writes files and no server at all — nothing of such a site can take the webhook.
Mapled opens the site through a one-time link to /api/mapled/preview, which trades it for a draft session, keeps the session in the mapled_draft cookie (HttpOnly, Secure, SameSite=Lax) and redirects into the site. From then on useMapled() reads drafts for that visitor on the server, uncached, until /api/mapled/preview/exit clears the cookie — published content for everyone else. The middleware sees to the rest, through Nitro's page caches too (isr, swr, cache route rules serve a hit without rendering and render a miss without the cookie): it puts the session on the event's context, where useMapled() reads it first, gives the request a cache key nobody asks for twice (a random mark, never the session), so a draft request is never a cache hit — not the published page's, not an earlier draft's, not after the session expired, and forces cache-control: private, no-store on the answer, so no CDN keeps it. Both routes redirect only to a path of the site itself. A draft is read on the server: after a client-side navigation, reload the page to see the draft again.
Open tabs can follow Publish. Render the component once, in app.vue — it asks the release route (/api/mapled/release, or under the module's prefix) every few seconds and reloads the page when a publish lands:
<!-- app.vue -->
<template>
<NuxtLayout>
<NuxtPage />
<MapledLive />
</NuxtLayout>
</template>
The route names the release this server last acknowledged through the webhook — never one its cache hasn't been purged for — so a tab that reloads on it renders the new content, not the old one again. It keeps the delivery key on the server and never asks Mapled. A site without a server (nuxt generate) passes the delivery key instead, <MapledLive :delivery-key="config.public.mapled.key" /> — the one key that is safe in a page — and the tab asks Mapled directly, once a minute. From a script of your own: startLive({ endpoint }) (or { key }) from @mapled/nuxt/live reloads on a publish and returns a stop function; watchRelease is the client's own, for anything else.
Every piece is a function you can mount yourself. Each handler takes the h3 event (or a Request) and answers a Response, so it is a Nitro route as it is:
// server/api/mapled/revalidate.post.ts
import { createRevalidateHandler } from "@mapled/nuxt/server";
// the secret is MAPLED_WEBHOOK_SECRET of the server, read when the first webhook arrives; `{ secret }` passes one by hand
const handler = createRevalidateHandler({ storage: useStorage("cache") });
export default defineEventHandler((event) => handler(event));
// server/api/mapled/preview.get.ts
import { createPreviewHandler } from "@mapled/nuxt/server";
// trades the link at MAPLED_API_URL when the site's env has one — the API the reads go to
export default defineEventHandler(createPreviewHandler());
// server/api/mapled/preview/exit.get.ts
import { createExitPreviewHandler } from "@mapled/nuxt/server";
export default defineEventHandler(createExitPreviewHandler());
// server/api/mapled/release.get.ts
import { createReleaseHandler } from "@mapled/nuxt/server";
export default defineEventHandler(createReleaseHandler());
// server/middleware/mapled.ts
import { onRequest } from "@mapled/nuxt/middleware";
export default defineEventHandler((event) => onRequest(event));
// composables/useCms.ts
import { mapled } from "@mapled/nuxt";
import type { MapledSchema } from "../mapled-types";
/** the server's client for the request being served: drafts under the preview cookie */
export const useCms = () => mapled<MapledSchema>(useRequestEvent());
A handler of your own can verify a request with verifySignature(secret, body, header), drop what it needs with purge(["mapled:posts"]), read the Request of an event with requestOf(event) and its body with boundedText(request, WEBHOOK_BODY_LIMIT) — the webhook reads at most WEBHOOK_BODY_LIMIT bytes before it checks the signature and answers 413 past it (BodyTooLarge) — and answer with the header x-mapled-cache: cacheMark() so live updates follow it. draftOf(event) answers the draft session of a request, or null — the one the middleware left on event.context under DRAFT_CONTEXT_KEY, else the cookie's; draftCookieOf(event) the cookie's alone; cookieHeaderOf(event) the Cookie header it reads; DRAFT_KEY_MARKER is what the middleware appends to the request's originalUrl for Nitro's cache key, followed by draftCacheNonce(), a fresh random mark; NITRO_CACHE_BASES names the groups of Nitro's cache the webhook clears.
Operational collections — content the site's backend writes, which takes effect without a publish — are createAppClient({ serverKey }) from the same package, server-side only. Its calls, Site Identity and the data of signed-in people are documented in @mapled/client.
// server/api/orders.post.ts
import { createAppClient } from "@mapled/nuxt";
const db = createAppClient({ serverKey: process.env.MAPLED_SERVER_KEY! });
export default defineEventHandler(async (event) => {
const body = (await readBody(event)) as { email: string };
const order = await db.create("orders", { email: body.email });
return { id: order.id };
});
| Import | What it is |
|---|---|
useMapled() (auto-imported by the module) | The site's client: on the server with drafts and the cache, in the browser with the public delivery key |
mapled(event?) from @mapled/nuxt | The site's client from the environment; with the h3 event (or a request) it reads drafts under the preview cookie |
createClient({ key, apiUrl, release, locale, draft, fetch, cache }) | A client by hand |
draftOf(from), draftCookieOf(from), cookieHeaderOf(from), DRAFT_CONTEXT_KEY | The draft session a request carries (off the event's context, else the cookie), or null; the cookie's alone; the Cookie header it is read from; the context key |
assetUrl, createAppClient, MapledError, watchRelease | Re-exported from @mapled/client |
default from @mapled/nuxt/module | The Nuxt module: routes, middleware, useMapled, <MapledLive />; RUNTIME_FILES lists what it mounts |
createRevalidateHandler({ secret, tags, storage, storageBases, deployHook, fetch }) from @mapled/nuxt/server | The publish webhook: purges the cache and Nitro's, calls the deploy hook, answers with x-mapled-cache; secret defaults to MAPLED_WEBHOOK_SECRET of the server |
createPreviewHandler({ apiUrl, appUrl }), createExitPreviewHandler() | Into preview and out of it |
createReleaseHandler({ ttl }) | The release route open tabs ask |
verifySignature, purge, cacheMark, requestOf, boundedText, WEBHOOK_BODY_LIMIT, BodyTooLarge, NITRO_CACHE_BASES | For a webhook handler of your own |
onRequest from @mapled/nuxt/middleware; DRAFT_KEY_MARKER, draftCacheNonce | A draft request renders the draft through Nitro's page caches and answers private, no-store |
startLive from @mapled/nuxt/live; MapledLive.vue | Open tabs follow Publish |
Errors are MapledError with the API's status and code. Every handler takes the h3 event or a Request and returns a Response, so it is a Nitro route (h3 1.8+, Nuxt 3.7+) as it is.
FAQs
Read published Mapled content in a Nuxt site — a server-side client with a tag cache, the publish webhook (purge the Nitro cache or call a deploy hook), preview, live updates and a Nuxt module that mounts it all.
The npm package @mapled/nuxt receives a total of 0 weekly downloads. As such, @mapled/nuxt popularity was classified as not popular.
We found that @mapled/nuxt demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
Rust 1.100 will add opt-in dependency cooldowns to Cargo, giving security scanners and maintainers time to catch malicious crate releases.

Security News
Cloudflare is acquiring Deno’s team, which will provide another year of bug fixes and security updates before ending support for the runtime.

Security News
Anthropic’s free OSS Scanner uses Mythos and other Claude models to find open source vulnerabilities and send maintainers reports with proposed fixes.