WebDNA
Stops Claude from scanning your entire codebase every single session: one file, always fresh, auto-generated on build.
55-70% fewer tokens on first context load.
→ ~70% fewer tokens on initial context load
npm install next-webdna
What it does
Claude burns tokens scanning your codebase. WebDNA fixes that by auto-generating a single manifest file on every build.
AI reads /webdna.json first and skips the file tree crawl. It is always in sync: no manual updates, ever.
On every build, WebDNA scans your project and generates a single JSON file at /.well-known/webdna.json containing:
- Routes - every page, its type (static/dynamic/private), descriptions
- Brand - colors, typography, tone of voice from Tailwind and CSS variables
- Components - names and descriptions from JSDoc comments
- API endpoints - methods, descriptions, shapes
- Meta - project name, version, generation timestamp
Setup
1. Install
npm install next-webdna
2. Wrap your config
// next.config.js const { withWebDNA } = require('next-webdna'); module.exports = withWebDNA({ // your existing config });
For ES modules or TypeScript:
import { withWebDNA } from 'next-webdna'; export default withWebDNA({ /* config */ });
3. Build
npm run build
Your manifest is at public/.well-known/webdna.json
AI Setup Prompts
Quick Setup (no exclusions)
For simple projects where everything should be visible to AI. Copy into Claude Code, Cursor, Windsurf, ChatGPT, Copilot, or any AI tool:
Guided Setup (with exclusion questionnaire)
For projects that need fine-grained control. The AI walks you through every exclusion decision and saves a summary file:
The Final Mile: CLAUDE.md
To ensure Claude actually uses the manifest, add this to your CLAUDE.md file. This stops the "file tree crawl" and forces the AI to use your structured blueprint first:
Generated Schema
Run npx webdna inspect to see your manifest in the terminal:
$ npx webdna inspect { "meta": { "name": "webdna-docs", "version": "1.0.0", "generatedAt": "2026-04-28T16:00:00Z" }, "brand": { "colors": { "primary": "#000000", "accent": "#66aa99" }, "typography": { "body": "Inter" } }, "routes": { "/": { "type": "static", "description": "Documentation Home" } } }
This is what /.well-known/webdna.json looks like internally:
{ "meta": { "name": "My Portfolio", "description": "Personal portfolio and blog", "version": "1.0.0", "generatedAt": "2026-04-01T12:00:00Z", "tier": "auto", "generator": "next-webdna" }, "brand": { "colors": { "primary": { "value": "#0F172A", "role": "primary" }, "accent": { "value": "#6366F1", "role": "accent" }, "background": { "value": "#ffffff", "raw": "var(--background)", "role": "background" } }, "typography": { "heading": "Cal Sans", "body": "Inter" }, "tone": "Professional, minimal, direct" }, "routes": { "/": { "type": "static", "description": "Homepage", "components": ["Navbar", "HeroSection"] }, "/blog/[slug]": { "type": "dynamic", "dataSource": "/api/posts", "components": ["Navbar", "BlogCard"], "apis": ["/api/posts"] }, "/dashboard": { "type": "private", "requiresAuth": true } }, "components": { "Navbar": { "description": "Top navigation with logo and page links", "role": "navigation", "props": ["links", "logo"], "usedIn": ["/", "/blog/[slug]"] }, "HeroSection": { "description": "Full-width hero with headline and CTA", "role": "content", "props": ["title", "subtitle"], "usedIn": ["/"] } }, "api": { "/api/posts": { "method": "GET", "description": "Returns all published blog posts", "consumedBy": ["/blog/[slug]"], "params": { "page": "query", "limit": "query" }, "responseSchema": { "posts": "array", "total": "number" } } } }
What it scans
| Source | Extracts |
|---|---|
| app/ or pages/ | Routes, page types, dynamic params, metadata descriptions |
| tailwind.config.js | Colors, fonts, design tokens |
| globals.css | CSS custom properties for colors and fonts |
| components/ | Component names and JSDoc descriptions |
| app/api/ or pages/api/ | API endpoints, HTTP methods, descriptions |
| package.json | Project name, description, version |
Configuration
Optional. Only needed when you want to control what AI can see. All exclusion fields support glob patterns (* wildcard).
// webdna.config.js module.exports = { name: "My Website", description: "A brief description", tone: "Professional, minimal, direct", // Route exclusions (glob patterns) exclude: ['/pricing', '/internal/*'], // Component exclusions (glob patterns) excludeComponents: ['InternalNav', 'Debug*'], // API endpoint exclusions (glob patterns) excludeApi: ['/api/internal/*', '/api/debug'], // Brand token exclusions excludeBrand: ['debug-*'], // Private routes (require auth) privateRoutes: ['/dashboard', '/settings'], // Custom component descriptions components: { HeroSection: "Full-width hero with animated headline and CTA", BlogCard: "Card showing post title, date, and excerpt" } };
Or pass config inline in next.config.js:
const { withWebDNA } = require('next-webdna'); module.exports = withWebDNA( { /* next config */ }, { exclude: ['/pricing'], excludeComponents: ['InternalNav'], excludeApi: ['/api/internal/*'], privateRoutes: ['/dashboard'] } );
Element-level exclusion
Exclude components directly from your JSX without touching config. WebDNA scans source files at build time:
// This component is excluded from webdna-custom.json export default function InternalMetrics() { return ( <div data-webdna="exclude"> <h2>Internal Metrics</h2> <p>Invisible to AI reading WebDNA.</p> </div> ); }
Both config-based and attribute-based exclusions are combined when generating the custom manifest.
Two-tier system
| Tier | File | When Generated |
|---|---|---|
| Auto | /.well-known/webdna.json | Always (full manifest) |
| Custom | /.well-known/webdna-custom.json | When any exclusions exist |
AI agents check for Custom first, fall back to Auto. The auto manifest always has everything. The custom manifest reflects your exclusion rules with dangling references cleaned up automatically.
CLI
npx webdna generate # Generate the manifest
npx webdna lint # Validate and check for issues
npx webdna inspect # Pretty-print the manifest
Lint rules
| Severity | Rule |
|---|---|
| Error | Private routes with no auth scopes declared |
| Warning | Dynamic routes with no content schema defined |
| Warning | Components with no JSDoc description |
| Warning | Components with no semantic role |
| Warning | Routes referencing missing components |
| Warning | Unresolved CSS variables in brand colors |
| Warning | Missing brand colors or typography |
| Warning | Missing project name or description |
Comparison
| Feature | sitemap.xml | llms.txt | MCP | WebDNA |
|---|---|---|---|---|
| Zero config | Partial | No | No | Yes |
| Component info | - | - | Partial | Yes |
| Brand / design tokens | - | - | - | Yes |
| Dynamic content schema | - | - | Yes | Yes |
| API examples | - | - | Partial | Yes |
| Auth / private routes | - | - | Yes | Yes |
| Element-level exclusion | - | - | - | Yes |
| Validator / linter | - | - | - | Yes |
| Always in sync | Yes | No | Partial | Yes |
Common Issues
Some hosting providers block or misconfigure static files in .well-known/ directories. AI tools like ChatGPT get CORS errors or 404s when trying to fetch the file.
/webdna.json), and a fallback API route at /api/webdna. Give the AI agent https://yoursite.com/api/webdna instead.Happens on Next.js 15+ or 16 with Turbopack. The withWebDNA wrapper injects a Webpack plugin, but Turbopack doesn't use the Webpack plugin lifecycle.
// package.json - update your build script "build": "npx webdna generate && next build"
WebDNA scans app/ and pages/ directories relative to your project root. If your source lives in src/, it checks src/app/ and src/pages/ automatically.
npx webdna inspect to see what was detected.WebDNA looks for tailwind.config.js/ts and CSS variables in globals.css.
WebDNA extracts descriptions from JSDoc comments above component exports. Without them, it uses a generic label.
/** Top navigation bar with logo and page links */ export default function Navbar() { ... }
If you mark a route as private, the linter expects OAuth scopes to be declared. This is a safety check.
TypeScript configs work fine. Use the ES module import syntax:
import { withWebDNA } from 'next-webdna'; import type { NextConfig } from 'next'; const config: NextConfig = { /* your config */ }; export default withWebDNA(config);
Still stuck? Contact me and I'll help debug your setup.
How AI Agents Access WebDNA
WebDNA is served at multiple URLs. AI agents should try them in order:
| URL | Description |
|---|---|
/.well-known/webdna-custom.json | Filtered manifest (if exclusions exist) |
/.well-known/webdna.json | Full manifest |
/api/webdna | API route fallback (auto-generated, CORS enabled) |
/webdna.json | Rewrite alias (no dotfile dependency) |
withWebDNA() automatically injects:
- CORS headers (
Access-Control-Allow-Origin: *) so AI agents can fetch cross-origin - Content-Type (
application/json) for correct parsing - Rewrite rules so
/webdna.jsonworks without the.well-knownprefix - A fallback API route at
app/api/webdna/route.tsfor hosts that block.well-known
The API route is auto-generated on each build and marked with @generated by next-webdna. It won't overwrite any file you created yourself.
How to get these features
If WebDNA is already installed on your project, update and rebuild:
npm update next-webdna npm run build
Then redeploy. The CORS headers, rewrite aliases, and fallback API route are all injected by withWebDNA() at build time.
Security
- WebDNA is a static JSON file - it cannot execute code
- Sensitive pages are excluded via config or
data-webdna="exclude" - Private routes declare auth requirements - no credentials stored in the file
- Write access is permanently disabled - no AI agent can modify WebDNA
- The manifest is generated from source code, never from external input
Roadmap
| Status | Phase |
|---|---|
| Done | Phase 1: Next.js Plugin MVP (withWebDNA, CLI, auto-generation) |
| Done | Phase 2: Custom tier with element-level exclusions |
| Next | Phase 3: Dynamic content + OAuth auth flows |
| Planned | Phase 4: Multi-tenant support |
| Planned | Phase 5: VS Code extension + CI/CD integration |
| Planned | Phase 6: Framework adapters (SvelteKit, Astro, Nuxt) |