npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@decocms/nextjs

v7.15.0

Published

Deco framework binding for Next.js App Router

Downloads

1,883

Readme

@decocms/nextjs

Deco framework binding for Next.js App Router — the Next.js sibling of @decocms/tanstack. Three surfaces, composed together:

  • @decocms/nextjs/configwithDeco(nextConfig), a next.config wrapper that adds the rewrites and transpilePackages Deco's admin protocol needs.
  • @decocms/nextjs/routeHandlerscreateDecoRouteHandlers({ setup }), a single catch-all dispatcher for the whole Studio admin protocol (decofile read/reload, meta schema, invoke, live previews).
  • @decocms/nextjs/setupcreateNextSetup(options), a one-call site bootstrap: the Next.js analogue of Vite's createSiteSetup + createAdminSetup + import.meta.glob.

This document is the complete recipe a new Next.js site follows to wire all three together. Every code block below is meant to be copied, not paraphrased.

Install

bun add @decocms/nextjs @decocms/blocks @decocms/blocks-admin

next, react, and react-dom are peer dependencies — the site already has them.

1. next.configwithDeco

withDeco adds three rewrites (mapping the Studio-protocol URLs Next.js cannot express as route segments onto /deco/*, where the catch-all route below serves them) and appends the three @decocms/* packages (which ship raw TypeScript, not a prebuilt dist/) to transpilePackages.

TypeScript (next.config.ts):

import type { NextConfig } from "next";
import { withDeco } from "@decocms/nextjs/config";

const nextConfig: NextConfig = {
  // ...your own config
};

export default withDeco(nextConfig);

CommonJS (next.config.js) — most Next.js sites still use this form:

/** @type {import('next').NextConfig} */
const { withDeco } = require("@decocms/nextjs/config");

const nextConfig = {
  // ...your own config
};

module.exports = withDeco(nextConfig);

withDeco merges with a rewrites/transpilePackages you already have — it never replaces them. If your own rewrites() returns the array form, Deco's rewrites are prepended; if it returns the { beforeFiles, afterFiles, fallback } object form, Deco's rewrites are prepended to beforeFiles.

2. The catch-all route

Mount createDecoRouteHandlers at app/deco/[[...deco]]/route.ts — this one file serves the entire admin protocol (decofile, meta, invoke, live previews) for both the rewritten public URLs (/.decofile, /live/_meta, /live/previews/*) and their /deco/* destinations directly.

// src/app/deco/[[...deco]]/route.ts
import { createDecoRouteHandlers } from "@decocms/nextjs/routeHandlers";
import { ensureSetup } from "../../../deco/setup";

export const dynamic = "force-dynamic";

export const { GET, POST, OPTIONS } = createDecoRouteHandlers({ setup: ensureSetup });

dynamic = "force-dynamic" is required — this route must never be statically cached (decofile reloads, invoke calls, and previews all need a fresh response per request).

Route-handler import rule: subpaths, never the root barrel

Always import from @decocms/nextjs/routeHandlers (or /config, /setup) in a route.ts file. Never import { ... } from "@decocms/nextjs" there.

App Router route handlers evaluate their entire module graph against React's react-server build, and this happens regardless of any "use client" directive in that graph — route handlers have no client bundle to move a "use client" module into, so the directive is simply ignored. The root barrel (@decocms/nextjs's . export) re-exports the render components (DecoRootLayout, SectionRenderer, DecoPageRenderer, ...), whose module graph reaches component code that uses client-only React APIs (createContext, useContext, class components with componentDidCatch, etc.) at module scope. Importing the root barrel from a route file pulls that whole graph into react-server evaluation and crashes at import time, before your handler ever runs, with errors like "...createContext is not a function" or "Class extends value undefined is not a constructor" — even if the route handler itself never touches those components. The /routeHandlers, /config, and /setup subpaths are each scoped to keep their own module graph free of component code, so they're safe to import from anywhere, including a route file.

3. src/deco/setup.tscreateNextSetup

The codegen artifacts (sections.gen.ts, meta.gen.json) are generated into .deco/ at the site root — the same default @decocms/blocks-cli's generators use everywhere else (framework artifacts live in the framework's folder, not scattered across src/). src/deco/setup.ts isn't adjacent to .deco/, so import it through a deco/* tsconfig path alias instead of a relative path:

// tsconfig.json
{
  "compilerOptions": {
    "paths": {
      "deco/*": [".deco/*"]
    }
  }
}
// src/deco/setup.ts
import { createNextSetup } from "@decocms/nextjs/setup";
import blocks from "deco/blocksManifest.gen"; // .deco/blocksManifest.gen.ts — see below
import { sectionImports, sectionMeta, syncComponents, loadingFallbacks } from "deco/sections.gen";

export const ensureSetup = createNextSetup({
  blocks,
  blocksDir: false, // the manifest replaces the runtime fs read
  sections: sectionImports,
  conventions: { meta: sectionMeta, syncComponents, loadingFallbacks },
  meta: () => import("deco/meta.gen.json").then((m) => m.default),
});

Recommended block source: the static-import manifest

generate-blocks-manifest (from @decocms/blocks-cli) emits .deco/blocksManifest.gen.ts — a module that statically imports every .deco/blocks/*.json file (raw on-disk filename in the specifier; keys are the filename minus .json, verbatim, exactly like loadDecofileDirectory). Passing its default export as blocks with blocksDir: false (as in the snippet above) makes the manifest the sole block source and the bootstrap pure — no filesystem access.

Why this is the recommended wiring over the default blocksDir runtime read:

  • CMS content hot-reloads in next dev. The block JSONs are part of Next's module graph, so editing one (Studio daemon write, sync, manual edit) invalidates the server module graph, re-evaluates the setup module, and rebuilds createNextSetup's memo with the fresh content — ~120–165ms per edit, measured on a 500-block site. The runtime fs read is invisible to the bundler: edits invalidate nothing and dev serves stale content until a restart.
  • Deploys need no outputFileTracingIncludes hack — the JSON is bundled into the build output instead of read from disk at runtime.

Trade-offs to know about:

  • Content is baked at build time in production. Studio's POST /.decofile still overrides the in-memory snapshot at runtime (live preview/publish keeps working on a warm instance); the baked manifest is the cold-start baseline.
  • Adding or removing a block file requires re-running the generator (content edits to existing files do not). Wire it into the site's generate chain — see the scripts section below.

If you skip the manifest, the default blocksDir: ".deco/blocks" runtime read still works — just without dev reload, and your deploy must ship the directory alongside the server bundle.

Keep the manifest out of any client-reachable import graph (import it only from the server-side setup module) so 5MB of CMS JSON never lands in a client bundle.

createNextSetup returns a memoized ensureSetup() function — a successful bootstrap is cached for the life of the warm serverless instance; a rejected bootstrap clears the memo so the next call retries from scratch (the triggering call still rejects with the original error).

Two call sites need ensureSetup():

  1. The catch-all route (above) passes it as { setup: ensureSetup }createDecoRouteHandlers awaits it before dispatching every admin request.

  2. The root layout (app/layout.tsx) must await it directly before rendering, since page rendering (createDecoPage's resolver) has no setup hook of its own:

    // src/app/layout.tsx
    import { DecoRootLayout } from "@decocms/nextjs";
    import { ensureSetup } from "../deco/setup";
    
    export default async function RootLayout({ children }: { children: React.ReactNode }) {
      await ensureSetup();
      return <DecoRootLayout siteName="my-site">{children}</DecoRootLayout>;
    }

    (app/layout.tsx is a Server Component, not a route handler, so it's fine to import the root barrel here — see the rule above.)

NextSetupOptions at a glance

| Option | Purpose | | --- | --- | | blocksDir | Directory of decofile JSON snapshots (.deco/blocks by default), read with a plain fs scan at bootstrap. Pass false to skip filesystem loading entirely — the recommended setting when blocks carries the static-import manifest (see above). | | blocks | Extra/override blocks, merged over the directory's blocks. Pass the manifest's default export here (with blocksDir: false) to make it the sole block source. | | sections | The lazy section registry — sectionImports from generate-sections --registry (see below). | | conventions | { meta, syncComponents, loadingFallbacks } from sections.gen.ts — wires the export const sync/layout/seo/cache/eager/clientOnly conventions (see below). | | meta | Lazy admin meta schema loader: () => import("deco/meta.gen.json").then(m => m.default). Wire this even with a trivial schema — without it, /deco/meta (and its /live/_meta alias) 503s with "Schema not initialized". | | renderShell | Admin preview shell config ({ css, fonts }). | | previewWrapper | Admin preview wrapper component. | | productionOrigins, customMatchers, onResolveError, onDanglingReference | Passed through to createSiteSetup. | | extend | Site-specific wiring that must run after core setup (section loaders, legacy SEO key shims, curated post-processing). Receives the loaded blocks. |

4. package.json scripts — non-colliding names

Add three codegen scripts. Do not name any of these generate:schema — FastStore sites already own that script name for their own commerce-schema codegen, and a collision silently shadows one of the two generators depending on script-merge order. Use these names instead:

{
  "scripts": {
    "generate:deco-meta": "tsx node_modules/@decocms/blocks-cli/scripts/generate-schema.ts",
    "generate:deco-sections": "tsx node_modules/@decocms/blocks-cli/scripts/generate-sections.ts --registry",
    "generate:deco-blocks": "tsx node_modules/@decocms/blocks-cli/scripts/generate-blocks-manifest.ts"
  }
}

Neither script needs an --out/--out-file flag — both generators default to .deco/, which is exactly where src/deco/setup.ts reads them from via the deco/* path alias (see above). Only pass --out/--out-file if your site has a reason to put the artifact somewhere else.

  • generate:deco-meta runs blocks-cli's generate-schema.ts, which scans src/sections/, src/loaders/, and src/apps/ for Props interfaces and emits the JSON Schema the admin's /deco/meta endpoint serves, to .deco/meta.gen.json by default.
  • generate:deco-sections runs generate-sections.ts with --registry — the flag that additionally emits sectionImports, the Next.js/webpack equivalent of Vite's import.meta.glob("./sections/**/*.tsx") (Next has no import.meta.glob or Vite plugin, so this is generated instead of computed at build time). Without --registry you only get sectionMeta/syncComponents/loadingFallbacks, not the lazy loader map setup.ts needs for its sections option. Defaults to .deco/sections.gen.ts.
  • generate:deco-blocks runs generate-blocks-manifest.ts, which emits the static-import blocks manifest (see the recommended-block-source section above) to .deco/blocksManifest.gen.ts by default. Regeneration is idempotent — an unchanged block set rewrites nothing, so no-op runs never tickle Next's file watcher.

Run the first two any time src/sections/ changes, and generate:deco-blocks any time a block file is added or removed in .deco/blocks/ (content edits to existing files don't need it — the static imports pick those up by themselves). Simplest is to wire all three into a predev/prebuild generate chain.

5. src/sections/ — the entry-file convention

Every non-test .tsx/.ts file directly under src/sections/ (recursively) becomes a section key, keyed as site/sections/<path-relative-to-sections-dir>. This is not opt-ingenerate-sections.ts walks the whole directory and turns every matching file into a registry entry, whether or not it carries any convention exports. Files ending in .test.ts(x), .spec.ts(x), .stories.ts(x), or .gen.ts(x) are the only ones excluded.

The established pattern is a thin re-export entry file per section, with the actual component implementation living elsewhere (e.g. alongside its own subcomponents, styles, and tests, outside src/sections/):

// src/sections/Hero.tsx — thin entry file, becomes "site/sections/Hero.tsx"
export { default } from "../components/Hero/Hero";
export type { HeroProps as Props } from "../components/Hero/Hero";

This keeps src/sections/ a clean, flat index of exactly what's CMS-addressable, instead of a directory contest between "real" component code and registry wiring.

Convention exports

A section's entry file (or the file it re-exports from — the scanner reads the entry file's own source, so re-exported export const conventions must be re-declared or forwarded on the entry file itself, not just the implementation file) can opt into these, each read as a literal export const <name> = <value>:

| Export | Effect | | --- | --- | | export const sync = true | Bundled synchronously (not lazy-loaded) — for above-the-fold, always-rendered sections. | | export const layout = true | Cached as a layout section (Header, Footer, Theme) — resolved once, not per-page. | | export const seo = true | SEO section — its resolved props are merged into page-level SEO. | | export const cache = "listing" | SWR-cached section loader results, keyed by the given cache name. | | export const eager = true | Prefers eager rendering (defers only past the fold threshold). | | export const neverDefer = true | Always eager, ignoring the fold threshold entirely. | | export const clientOnly = true | Skips SSR — client-only rendering. | | export function LoadingFallback | Skeleton component shown while the section loads. |

Verifying your setup

examples/nextjs-smoke in this monorepo is a minimal, real Next.js App Router build exercising all three surfaces end to end — withDeco rewrites, the catch-all route, createNextSetup, and a resolved page render. Use it as a working reference if any of the above doesn't compose the way you expect.