Visual Editor for
Next.js Content
Visual Markdown and MDX editing, typed frontmatter, image uploads, and version history. All inside your Next.js project.
@imjp/writenex-next · v1.14.0 · Next.js 15 & 16 · App Router · Node.js
Quick Start
Connect the App Router route, then open your editor.
Install the package
npm install @imjp/writenex-nextConnect your Next.js configuration
import { withWritenex } from "@imjp/writenex-next";
export default withWritenex({
// Keep your existing Next.js options here.
});Create the editor route
import { createWritenexHandler } from "@imjp/writenex-next/server";
export const runtime = "nodejs";
export const dynamic = "force-dynamic";
const handler = createWritenexHandler();
export const GET = handler;
export const HEAD = handler;
export const POST = handler;
export const PUT = handler;
export const DELETE = handler;
export const PATCH = handler;
export const OPTIONS = handler;Use the literal %5Fwritenex folder name: Next.js treats folders starting with an underscore as private. Keep [[...path]] to serve the editor, API, and assets. Projects using src/app should create the route there. Do not add a page.tsx alongside it.
Start editing
npm run dev
# Open http://localhost:3000/_writenexThe Writenex editing experience
Visual Markdown & MDX
Write with the visual editor or switch to source mode. Content stays in your project as Markdown and MDX files.
Content collections
Discover collections inside content/ and src/content/, or choose explicit paths in writenex.config.ts.
Typed frontmatter
Use the shared Fields API for text, selections, dates, images, structured data, validation, and more.
Image uploads
Upload images alongside content or into your public directory with the same storage strategies as Astro.
Version history
Save automatic snapshots and restore earlier drafts. Version files stay on your project's filesystem.
Your existing site
The editor runs in its own HTML document. Its styles and bundled React stay outside your site's layouts.
Content collections & Fields API
Subdirectories of content/ and src/content/ become collections automatically. Frontmatter forms are inferred from existing files. For explicit fields and preview URLs, add a configuration file:
import { defineConfig, collection, fields } from "@imjp/writenex-next/config";
export default defineConfig({
collections: [
collection({
name: "blog",
path: "content/blog",
filePattern: "{slug}.mdx",
previewUrl: "/blog/{slug}",
schema: {
title: fields.text({
label: "Title",
validation: { isRequired: true },
}),
description: fields.text({ label: "Description", multiline: true }),
draft: fields.checkbox({ label: "Draft", defaultValue: true }),
body: fields.mdx({ label: "Content" }),
},
}),
],
editor: { autosave: true, autosaveInterval: 3000 },
images: { strategy: "colocated" },
versionHistory: { enabled: true, maxVersions: 20 },
});Configured paths take precedence. Set discovery: { enabled: false } to use only configured collections. The Fields API matches Astro; import defineConfig, collection, singleton, and fields from @imjp/writenex-next/config.
Preview URLs must match your own site routes. Use source mode for custom MDX components or expressions that the visual editor cannot round-trip, and verify the result in your site's preview.
Runtime configuration
Pass these options to createWritenexHandler() in your route file.
| Option | Default | Purpose |
|---|---|---|
| projectRoot | process.cwd() | Project directory for content and configuration. |
| basePath | "/_writenex" | Full public editor URL, including the host basePath. |
| nextBasePath | "" | Host basePath removed by Next.js before route handling. |
| contentDirs | ["content", "src/content"] | Ordered discovery roots; the first matching collection wins. |
| trailingSlash | unspecified | Match your Next.js preview URL convention. |
| maxBodyBytes | 10485760 | Request body limit, including multipart uploads. |
| allowProduction | false | Enable production editing with complete authentication. |
| remoteCms | config file | CMS authentication settings; keep credentials server-side. |
If your Next.js site uses basePath: "/docs", keep the encoded route folder and configure both paths:
const handler = createWritenexHandler({
basePath: "/docs/_writenex",
nextBasePath: "/docs",
// Set trailingSlash: true if enabled in next.config.
});Production & authentication
The editor returns 404 in production by default. Enable it only on a Node.js server with persistent writable storage and complete authentication.
const handler = createWritenexHandler({
allowProduction: true,
remoteCms: { enabled: true },
});WRITENEX_CMS_USER=your-admin-username
WRITENEX_CMS_PASS=your-strong-password
WRITENEX_SECRET=your-long-random-signing-secretKeep credentials server-side; never use NEXT_PUBLIC_ variables. Missing credentials or a signing secret keep the editor closed. Use HTTPS and preserve content, image, and .writenex/versions directories across deployments.
Edge runtime, static export, and read-only or ephemeral serverless storage cannot support durable editing. Installing this package on Vercel does not sync edits to Git. Pages Router and Git synchronization are not included.
Troubleshooting
- 404 in development
- Check the literal %5Fwritenex directory, optional catch-all brackets, App Router location, and all route exports.
- No collections
- Create content/blog/ with Markdown or MDX files, or configure the exact collection path. Symlinks in editable paths are refused.
- Missing editor assets
- Wrap your existing config with withWritenex and match the handler basePath to the public URL.
- Uploads return 413
- Increase maxBodyBytes for the full multipart body and check your reverse proxy limits.
- Changes to configuration do not appear
- Restart the server after changing writenex.config.ts.