AI Decision System
Replayable, offline-verifiable records for AI-assisted go/no-go decisions: experiment promotions, typed model answers and human-approved actions.
vagejs는 AI 웹사이트용 무료 정적 생성기로, 텍스트 파일로 페이지 작성 시 빠르고 검증된 사이트가 생성돼 Google·AI가 찾는다
vagejs is a free static site generator for AI website development. Write pages as text files and get a fast, checked site that Google and AI can find.
개발 도구TypeScript · JavaScriptGitHub @agevenkat온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
vagejs: a free static site generator for AI website development. Markdown pages, built-in SEO and llms.txt, checks before publishing, safe deploys.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗A free static site generator built for AI coding assistants. Your assistant (Claude Code, Cursor, Codex, Copilot) writes the pages; vagejs checks them before anything goes live. Write pages in Markdown and get a fast static site with SEO, llms.txt, accessibility and security handled for you. Pre-release (0.x).
Website · Docs · Use with AI assistants · Changelog · Discussions · LinkedIn
robots.txt, sitemap.xml, llms.txt, canonical URLs, Open Graph and JSON-LD, generated from your pages.vage check (links, SEO, structured data, accessibility) and vage audit (page-weight budgets, axe, optional Lighthouse and PageSpeed).AGENTS.md, and vage info --json describes every command and config option.npm run dev also starts a local editor: next steps for a new site, a form for each page, the text with a live preview in your site's design, image upload and the check results. It edits the same files and is never part of the built site.contact: { to, from } plus contactForm(site) in a layout; vage deploy adds a Worker that emails each message, with a spam trap and a per-visitor limit.npm run build and the dist folder.Requires Node.js 22.12 or newer.
npm create vagejs@latest my-site
It asks "Install and start the site now?". Press Enter, pick what to build the site with (Claude Code, Codex, Gemini CLI, VS Code or Cursor if you have them, or the browser editor), and it installs everything, opens the site in your browser and starts your assistant (with a first prompt to set up the site, except in Cursor, where it's printed to paste). Edit src/pages/index.md and the page reloads. Next time, start it with cd my-site and npm start (npm run dev does the same without opening a browser, and prints the address). When an AI assistant or a script runs the command, nothing is asked or started: it prints the next steps (cd my-site, npm install, npm start). -- --yes installs and starts without asking; -- --no-install only creates the files.
Prefer forms to files? While the dev server runs, open the editor: address it prints (or /_vage/edit/ on the site): a local editor with every page, its fields, the text with a live preview, image upload and the page's check results. It edits the same files, refuses to overwrite a page that changed since you opened it, only works on your own computer, and is never part of the built site.
vagejs works on Windows and is tested on Windows on every change.
node --version and npm --version.npm and npx commands in this README work in both. Command Prompt doesn't treat # as a comment, so leave out the # … notes shown after some commands.[::1] or 127.0.0.1) that npm start and npm run dev print. It's the same server, without the cookies other local projects left on localhost.my-site/
├── src/
│ ├── pages/ # every .md file is a page
│ │ ├── index.md # → /
│ │ ├── about.md # → /about/
│ │ └── blog/
│ │ └── first-post.md # → /blog/first-post/
│ └── layouts/
│ └── default.ts # HTML around each page
├── public/ # copied to the site as-is (favicons, images)
├── vage.config.ts # site settings
└── AGENTS.md # instructions for AI coding agents
---
title: Pricing
description: Plans and prices.
---
# Pricing
Our plans start at …
Save it as src/pages/pricing.md and it's served at /pricing/. title is required. description, draft, noindex, canonical, image, type: article, author, datePublished, dateModified and tags are optional (a future datePublished schedules the page; each tag gets a page at /tags/<tag>/); see templates/starter/AGENTS.md.
Images in public/ used as  get their width and height from the file, so the page doesn't jump while loading. With images: { optimize: true } (and npm install --save-dev sharp), vagejs also makes resized WebP copies of images in page content and offers them with srcset. Add avif: true for AVIF copies too, offered first through <picture> (usually smaller; slower to make). vage audit counts the copy browsers download, not the original.
menus in vage.config.ts holds the header and footer menus (links, groups and dropdowns); layouts get them as props.menus, with the current page and section marked. { page: "/pricing/" } entries take the page's label and fail the build if the page is gone; vage check flags menu links that go nowhere.order: and navTitle: in frontmatter, with tree(), breadcrumbs() and neighbors() in layouts, build sidebars, breadcrumbs and previous/next links.i18n: { locales: ["fr", "de"] } makes a multi-language site: lang stays the main language, src/pages/fr/about.md is the French version of about.md at /fr/about/ (a translated address like fr/a-propos.md says translationOf: /about/). Pages get the right <html lang> (page.lang) and hreflang links to their translations; a feed lists only its own language; layouts get page.translations for a language switcher and collection(pages, { lang }). vage check reports a wrong <html lang>, broken translationOf and missing translations (only for the pages in i18n.pages, when it's set).seo: { feeds: [{ under: "/blog/" }] } writes an RSS feed at /blog/feed.xml.seo: { markdownCopies: true } publishes a Markdown copy of every listed page (/docs/cli.md) for AI tools, made from the page's <main>, without comments or raw HTML (layouts leave extras out with data-vage-copy="skip"); seo: { lastmod: "git" } dates the sitemap from git history (CI needs fetch-depth: 0).Layouts are TypeScript functions. The html template tag escapes every value for you; raw() inserts trusted HTML such as the rendered page. (Escaping doesn't check URLs, so validate link targets that come from frontmatter.) pages lists every published page, and collection() picks and sorts them:
import { collection, html, raw, z, type Layout } from "vagejs";
// Optional: fields this layout accepts. Pages using it are checked strictly, so typos fail the build.
export const frontmatter = z.object({ intro: z.string().optional() });
const layout: Layout = ({ content, page, site, pages }) => html`<!doctype html>
<html lang="${site.lang}">
<head><title>${page.title}</title></head>
<body>
<main>${raw(content)}</main>
<h2>Latest posts</h2>
<ul>${collection(pages, { under: "/blog/", sort: "date", limit: 5 }).map((p) => html`<li><a href="${p.route}">${p.title}</a></li>`)}</ul>
</body>
</html>`;
export default layout;
Components render to plain HTML at build time, so pages stay at 0 KB of JavaScript unless you mark a component interactive (below). Install only what you use:
npm install react react-dom, then write src/layouts/default.tsx as a React component. It gets the same props; insert the page with <main dangerouslySetInnerHTML={{ __html: content }} />.npm install vue && npm install --save-dev @vitejs/plugin-vue, then src/layouts/default.vue with defineProps for the props it uses and <main v-html="content" />.npm install react react-dom && npm install --save-dev @mdx-js/mdx, then src/pages/pricing.mdx: the usual frontmatter, Markdown, and React components imported from src/components/. MDX components are React; Vue components work in layouts only.An .mdx page is code: it runs at build time like a layout and isn't sanitized like .md pages, so use it only for content you'd trust in a layout. vage check (links, link lock, hidden links, SEO) still checks its output. Interactive islands (React). Add client:load (start right away), client:visible (when scrolled into view) or client:idle (when the browser is idle) to a React component imported from src/components/, in an MDX page or a React layout: <Counter client:load start={3} />. It's still rendered to HTML first, so the page works before the JavaScript arrives; the build then bundles its code into /_vage/islands/ and adds one small loader script to pages that have islands. Props must be plain data (text, numbers, true/false, lists, plain objects), islands can't have content between their tags or contain other islands, and files they import other than JavaScript (CSS, images) aren't published: use paths to public/. Only pages with islands send JavaScript, and vage audit counts it, including React itself (about 215 KB, over the default 100 KB javascriptKb budget: raise it if you accept that). Vue islands aren't supported yet. Styles in components (a Vue <style> block, import "./x.css") aren't collected: put CSS in public/ and link it from the layout. For your own JSON-LD in a component layout, insert it with dangerouslySetInnerHTML (React) or v-html (Vue), or leave it to vagejs's automatic JSON-LD; Vue escapes quotes in a plain <script> template. src/pages/404.mdx works as the 404 page too.
| Command | What it does |
|---|---|
npm start | Dev server with live reload, opened in your browser |
npm run dev | The same, without opening a browser; prints the address (and a direct [::1] or 127.0.0.1 address to use if the browser shows HTTP 431) |
npm run check | Links, SEO, structured data and basic accessibility; writes nothing |
npm run build | Static site in dist/, plus robots.txt, sitemap.xml, llms.txt |
npm run audit | Page-weight budgets and automated accessibility (-- --lighthouse adds Lighthouse) |
npm run preview | Serves dist/ locally, the way a static host would |
Add --json to npx vage build, check or audit for machine-readable output. Failures exit with code 1.
vage.config.ts:
import { defineConfig } from "vagejs";
export default defineConfig({
site: "https://example.com", // your real domain (used for canonical URLs and the sitemap)
title: "My Website",
description: "What this site is about.",
seo: {
robots: { aiTraining: "disallow" }, // optional: block AI training crawlers, keep search
llmsTxt: { full: false },
},
redirects: { "/old-page/": "/new-page/" },
});
Unknown keys are an error, so typos don't silently do nothing. npx vage info --json prints the full schema.
assets: { minifyCss: true } minifies the stylesheets in public/ in the build, keeping fallbacks for older browsers (the files in public/ stay as you wrote them). With assets: { fingerprint: true }, each stylesheet, script and font in public/ is also published under /_vage/assets/ with a name made from its content (/styles.css becomes /_vage/assets/styles.3f9a1c2b4d.css), pages and stylesheets point to those copies, and on Cloudflare and Netlify _headers lets browsers cache them for a year. A changed file gets a new name, so visitors never keep an old one, and the originals stay where they are for links from elsewhere. Relative references inside those stylesheets keep working. Module scripts (type="module", .mjs) keep their names, because their imports are relative; leave fingerprinting off if another script loads files from public/ by path.
Moving a site that used addresses without a trailing slash (/about, not /about/)? Set trailingSlash: "never": pages are written as about.html, and canonical URLs, the sitemap, feeds, llms.txt and the routes layouts see all drop the slash, so existing links and rankings keep working. On Vercel, also add "cleanUrls": true to vercel.json.
npx vage deploy:github --remote https://github.com/you/site.git # check, build, safe commit + push (asks first)
npx vage deploy # GitHub Pages via Actions (project repositories: site: "https://<user>.github.io/<repo>")
npx vage deploy --target cloudflare-workers --project my-site # Cloudflare Workers: new version, no downtime (deploy.target: "cloudflare-workers")
npx vage deploy --target cloudflare-pages --project my-site # older Cloudflare Pages projects (deploy.target: "cloudflare-pages")
npx vage deploy --target netlify --project my-site # Netlify production deploy (deploy.target: "netlify")
Every deploy shows its plan and asks for confirmation (--dry-run to preview, --yes to skip). It reports success only after the host confirms. Cloudflare and Netlify deploys need deploy.target set to that host in vage.config.ts, so the build writes its _redirects and _headers.
Cloudflare now creates new static sites as Workers, so use cloudflare-workers for new projects. For a custom domain, add a wrangler.jsonc with your routes; vagejs uses it and switches versions without re-attaching domains, so the site stays up during deploys.
Optional, and off until configured: GA4 or Google Tag Manager (consent denied by default), Plausible, Microsoft Clarity, IndexNow. vage connect gsc|ga4 adds read-only Search Console and GA4 reports, and vage audit --url https://your-site runs PageSpeed and CrUX with your own Google API key. Details are in the generated AGENTS.md.
vagejs follows semver. While it's 0.x, a minor version (0.1 → 0.2) may include breaking changes, so read the release notes before moving up a minor version.
npm outdated vagejs # see what's available
npm update vagejs # latest patch in your range (with ^0.3.0: any 0.3.x)
npm install vagejs@latest # move to the newest version, including a new minor
npx vage --version # confirm
npx vage check && npx vage build
If your config pins the rule pack (guidelines: { rulePack: "…" }), a build after an update that ships new provider rules stops with a message. Review them with npx vage guidelines, then update the pin. New projects always use the latest version through npm create vagejs@latest.
Every site includes AGENTS.md (conventions, recipes, definition of done) and CLAUDE.md (imports AGENTS.md). Agents can discover the API instead of guessing:
npx vage info --json # commands, options, config and frontmatter JSON Schemas
npx vage check --json # findings with file, rule and fix hints
Or let the assistant call vagejs directly over MCP. npx vage mcp runs an MCP server with read-only tools (vage_info, vage_check, vage_pages, vage_links); it needs the optional SDK:
npm install --save-dev @modelcontextprotocol/sdk
claude mcp add --transport stdio --scope project vagejs -- npx -p vagejs vage mcp # Claude Code, in the project folder
Other assistants: { "mcpServers": { "vagejs": { "command": "npx", "args": ["-p", "vagejs", "vage", "mcp", "--root", "/absolute/path/to/the/site"] } } }. -p vagejs makes npx run the vagejs installed in the project, and never a different package that happens to be called vage. Approving link domains and deploying stay CLI commands for the site owner.
vage create <dir> [--vage-spec <spec>] [--json] # scaffold a Markdown site
vage dev [--root .] [--port N] [--host] # dev server with live reload
vage build [--root .] [--json] # static build into dist/ (+ robots.txt, sitemap.xml, llms.txt)
vage check [--root .] [--json] [--strict] # SEO, links, JSON-LD, basic a11y; writes nothing
vage audit [--root .] [--lighthouse] [--url U] [--strategy mobile|desktop|both] [--strict] [--json]
# budgets + axe a11y; optional Lighthouse; PageSpeed/CrUX for deployed URLs
vage preview [--root .] [--port N] [--host] # serve dist/ locally
vage deploy:github [--remote URL] [--branch B] [-m MSG] [--dry-run] [--yes] [--json] # check, build, safe commit + push (asks first)
vage deploy [--remote URL] [--no-wait] [--dry-run] [--yes] # GitHub Pages via Actions; confirms with GitHub
vage deploy --target cloudflare-workers [--project NAME] # new Worker version at 100%; custom domains stay up
vage deploy --target cloudflare-pages --project NAME [--branch B] # Wrangler upload; preview URL per branch
vage deploy --target netlify [--project SITE] # netlify-cli production deploy, then a live check
vage indexnow --changed-since REF | --urls … | --all # IndexNow URL notification (asks first)
vage connect gsc|ga4 / vage disconnect google # read-only Google OAuth (PKCE, loopback); token outside the repo
vage connect pagespeed|crux / vage disconnect … # save your own Google API key for audit --url (outside the repo)
vage report search|analytics [--days N] [--json] # Search Console top queries/pages; GA4 top pages
vage guidelines [--json] # rule pack: provider facts in use, sources, changelog
vage info [--json] # commands + config/frontmatter JSON Schemas for agents
Bing Webmaster Tools API, a Vercel adapter, CMS content sources, sitemap indexes, per-language llms.txt.
Page content is sanitized, dev/preview servers serve only what a site needs, and Cloudflare deploys get security headers with a computed Content-Security-Policy. Report vulnerabilities privately: see SECURITY.md.
A static site has no admin panel, database or plugins to break into, so attackers go after your content, your repository or your host instead, usually to plant hidden links and spam pages. vagejs checks for that on every build:
| Protection | What it does |
|---|---|
| Outbound link lock | links.lock.json lists the domains your site may link to. A link to any other domain fails vage check (and so every deploy) until you approve it with vage links approve <domain>. It covers links, SVG links, canonical and og:url, meta refresh, iframes and forms. With security.outboundLinks: "require" (the default for new sites), deleting the file is an error too. |
| No hidden links | Inline styles, SVG xlink:href and legacy tags like <font> and <marquee> are stripped from page content, and vage check fails on external links hidden with CSS, aria-hidden, zero-size SVG or no visible content, in content and layouts. |
| Spam words | vage check warns about common injected-spam terms (casino, pharmacy, payday loans…). |
| Live-site verification | vage verify compares the live site with your files: links and other targets, scripts, event handlers, JSON-LD, text (spam words are errors), robots.txt and sitemap pages. It also looks at every page as Googlebot, to catch spam shown only to search engines. New sites include a daily GitHub Actions check (opt in with the VAGE_VERIFY_URL variable). |
| Supply chain | New sites run npm audit in CI and get Dependabot updates. |
npx vage links # list external domains: approved or NEW
npx vage links approve github.com # approve one (asks first)
npx vage verify # compare the live site with your files
Also protect the accounts around the site: two-factor authentication on GitHub, npm, your host and your domain registrar; branch protection on main; and Google Search Console alerts for security issues.
New contributors are welcome: see CONTRIBUTING.md and the issues labeled good first issue. Questions and ideas go in Discussions.
npm install
npm run typecheck
npm test # unit + integration (dev server, preview, build)
npm run test:e2e # pack -> create -> npm install/ci -> build (needs npm registry or cache)
Design decisions: docs/DECISIONS.md. Provider rules and how they're monitored: docs/GUIDELINES.md. Releasing: docs/RELEASING.md.
assets/brand/vagejs-icon.svg: the icon, as received (with C2PA content credentials).assets/brand/vagejs-logo.svg and vagejs-logo-dark.svg: the wordmark for light and dark backgrounds, with Space Grotesk Bold embedded (a 1.6 KB WOFF2 subset of the glyphs used), so it renders the same without the font installed. Space Grotesk is © 2020 The Space Grotesk Project Authors, under the SIL Open Font License 1.1 (assets/brand/fonts/OFL.txt). Editing the file invalidated its C2PA signature, so that block was removed. The dark variant only changes the text colors (#F4F5FA and #A5B4FC).favicon.svg, favicon.ico (16/32/48) and apple-touch-icon.png (180) in public/.Regenerate (maintainer tools; need Chrome, and Python with fonttools and brotli):
node scripts/brand/favicons.mjs templates/starter/public/favicon.svg templates/starter/public
python -I scripts/brand/embed-logo-font.py "SpaceGrotesk[wght].ttf" <original-logo.svg> assets/brand/vagejs-logo.svg
MIT © 2026 vagejs.com