Repository: nitrojs/nitro Branch: main Commit: 2218d4541344 Files: 771 Total size: 1.5 MB Directory structure: gitextract__vtgecx6/ ├── .agents/ │ ├── architecture.md │ ├── docs.md │ ├── presets.md │ ├── skills/ │ │ └── update-deps/ │ │ └── SKILL.md │ ├── testing.md │ └── vite.md ├── .devcontainer/ │ └── devcontainer.json ├── .editorconfig ├── .eslintcache ├── .gitattributes ├── .github/ │ ├── CODEOWNERS │ ├── ISSUE_TEMPLATE/ │ │ ├── bug-report.yml │ │ ├── config.yml │ │ └── feature-request.yml │ ├── PULL_REQUEST_TEMPLATE.md │ ├── agents/ │ │ └── maintainer.agent.md │ ├── codecov.yml │ ├── copilot-instructions.md │ └── workflows/ │ ├── autofix.yml │ ├── ci.yml │ ├── copilot-setup-steps.yml │ └── npm-publish.yml ├── .gitignore ├── .nvmrc ├── .oxfmtrc.json ├── .oxlintrc.json ├── AGENTS.md ├── CLAUDE.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── automd.config.ts ├── build.config.ts ├── changelog.config.ts ├── docs/ │ ├── .config/ │ │ ├── automd.config.ts │ │ └── docs.yaml │ ├── .docs/ │ │ ├── app.config.ts │ │ ├── assets/ │ │ │ └── css/ │ │ │ └── main.css │ │ ├── components/ │ │ │ ├── AppHero.vue │ │ │ ├── AppHeroLinks.vue │ │ │ ├── FeatureCard.vue │ │ │ ├── HeroBackground.client.vue │ │ │ ├── HeroFeatures.vue │ │ │ ├── LandingFeatures.vue │ │ │ └── PerformanceShowcase.vue │ │ ├── layouts/ │ │ │ └── examples.vue │ │ ├── nuxt.config.ts │ │ ├── pages/ │ │ │ ├── examples/ │ │ │ │ ├── [...slug].vue │ │ │ │ └── index.vue │ │ │ └── index.vue │ │ └── utils/ │ │ └── examples.ts │ ├── .npmrc │ ├── 1.docs/ │ │ ├── .navigation.yml │ │ ├── 1.index.md │ │ ├── 2.quick-start.md │ │ ├── 4.renderer.md │ │ ├── 5.routing.md │ │ ├── 50.assets.md │ │ ├── 50.configuration.md │ │ ├── 50.database.md │ │ ├── 50.lifecycle.md │ │ ├── 50.plugins.md │ │ ├── 50.tasks.md │ │ ├── 6.server-entry.md │ │ ├── 7.cache.md │ │ ├── 8.storage.md │ │ ├── 99.migration.md │ │ └── 99.nightly.md │ ├── 2.deploy/ │ │ ├── 0.index.md │ │ ├── 10.runtimes/ │ │ │ ├── 1.node.md │ │ │ ├── bun.md │ │ │ └── deno.md │ │ └── 20.providers/ │ │ ├── alwaysdata.md │ │ ├── aws-amplify.md │ │ ├── aws.md │ │ ├── azure.md │ │ ├── cleavr.md │ │ ├── cloudflare.md │ │ ├── deno-deploy.md │ │ ├── digitalocean.md │ │ ├── firebase.md │ │ ├── flightcontrol.md │ │ ├── genezio.md │ │ ├── github-pages.md │ │ ├── gitlab-pages.md │ │ ├── heroku.md │ │ ├── iis.md │ │ ├── koyeb.md │ │ ├── netlify.md │ │ ├── platform-sh.md │ │ ├── render.md │ │ ├── stormkit.md │ │ ├── vercel.md │ │ ├── zeabur.md │ │ ├── zephyr.md │ │ └── zerops.md │ ├── 3.config/ │ │ └── 0.index.md │ ├── 4.examples/ │ │ ├── 0.index.md │ │ ├── api-routes.md │ │ ├── auto-imports.md │ │ ├── cached-handler.md │ │ ├── custom-error-handler.md │ │ ├── database.md │ │ ├── elysia.md │ │ ├── express.md │ │ ├── fastify.md │ │ ├── hello-world.md │ │ ├── hono.md │ │ ├── import-alias.md │ │ ├── middleware.md │ │ ├── mono-jsx.md │ │ ├── nano-jsx.md │ │ ├── plugins.md │ │ ├── renderer.md │ │ ├── runtime-config.md │ │ ├── server-fetch.md │ │ ├── shiki.md │ │ ├── virtual-routes.md │ │ ├── vite-nitro-plugin.md │ │ ├── vite-rsc.md │ │ ├── vite-ssr-html.md │ │ ├── vite-ssr-preact.md │ │ ├── vite-ssr-react.md │ │ ├── vite-ssr-solid.md │ │ ├── vite-ssr-tsr-react.md │ │ ├── vite-ssr-tss-react.md │ │ ├── vite-ssr-vue-router.md │ │ ├── vite-trpc.md │ │ └── websocket.md │ ├── 9.blog/ │ │ ├── 1.v3-beta.md │ │ └── index.md │ ├── index.md │ ├── package.json │ └── pnpm-workspace.yaml ├── examples/ │ ├── api-routes/ │ │ ├── README.md │ │ ├── api/ │ │ │ ├── hello/ │ │ │ │ └── [name].ts │ │ │ ├── hello.ts │ │ │ ├── test.get.ts │ │ │ └── test.post.ts │ │ ├── index.html │ │ ├── nitro.config.ts │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── auto-imports/ │ │ ├── README.md │ │ ├── nitro.config.ts │ │ ├── package.json │ │ ├── server/ │ │ │ └── utils/ │ │ │ └── hello.ts │ │ ├── server.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── cached-handler/ │ │ ├── README.md │ │ ├── nitro.config.ts │ │ ├── package.json │ │ ├── server.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── custom-error-handler/ │ │ ├── README.md │ │ ├── error.ts │ │ ├── nitro.config.ts │ │ ├── package.json │ │ ├── server.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── database/ │ │ ├── README.md │ │ ├── nitro.config.ts │ │ ├── package.json │ │ ├── server.ts │ │ ├── tasks/ │ │ │ └── db/ │ │ │ └── migrate.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── elysia/ │ │ ├── README.md │ │ ├── nitro.config.ts │ │ ├── package.json │ │ ├── server.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── express/ │ │ ├── README.md │ │ ├── nitro.config.ts │ │ ├── package.json │ │ ├── server.node.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── fastify/ │ │ ├── README.md │ │ ├── nitro.config.ts │ │ ├── package.json │ │ ├── server.node.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── hello-world/ │ │ ├── README.md │ │ ├── nitro.config.ts │ │ ├── package.json │ │ ├── server.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── hono/ │ │ ├── README.md │ │ ├── nitro.config.ts │ │ ├── package.json │ │ ├── server.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── import-alias/ │ │ ├── README.md │ │ ├── nitro.config.ts │ │ ├── package.json │ │ ├── server/ │ │ │ ├── routes/ │ │ │ │ └── index.ts │ │ │ └── utils/ │ │ │ └── math.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── middleware/ │ │ ├── README.md │ │ ├── nitro.config.ts │ │ ├── package.json │ │ ├── server/ │ │ │ └── middleware/ │ │ │ └── auth.ts │ │ ├── server.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── mono-jsx/ │ │ ├── README.md │ │ ├── nitro.config.ts │ │ ├── package.json │ │ ├── server.tsx │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── nano-jsx/ │ │ ├── README.md │ │ ├── nitro.config.ts │ │ ├── package.json │ │ ├── server.tsx │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── plugins/ │ │ ├── README.md │ │ ├── nitro.config.ts │ │ ├── package.json │ │ ├── server/ │ │ │ └── plugins/ │ │ │ └── test.ts │ │ ├── server.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── renderer/ │ │ ├── README.md │ │ ├── api/ │ │ │ └── hello.ts │ │ ├── nitro.config.ts │ │ ├── package.json │ │ ├── renderer.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── runtime-config/ │ │ ├── .env │ │ ├── .gitignore │ │ ├── README.md │ │ ├── nitro.config.ts │ │ ├── package.json │ │ ├── server.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── server-fetch/ │ │ ├── README.md │ │ ├── nitro.config.ts │ │ ├── package.json │ │ ├── routes/ │ │ │ ├── hello.ts │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── shiki/ │ │ ├── README.md │ │ ├── api/ │ │ │ └── highlight.ts │ │ ├── index.html │ │ ├── nitro.config.ts │ │ ├── package.json │ │ ├── styles.css │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── virtual-routes/ │ │ ├── README.md │ │ ├── nitro.config.ts │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── vite-nitro-plugin/ │ │ ├── README.md │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── vite.config.mjs │ ├── vite-rsc/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── app/ │ │ │ ├── action.tsx │ │ │ ├── client.tsx │ │ │ ├── framework/ │ │ │ │ ├── entry.browser.tsx │ │ │ │ ├── entry.rsc.tsx │ │ │ │ ├── entry.ssr.tsx │ │ │ │ ├── error-boundary.tsx │ │ │ │ └── request.tsx │ │ │ ├── index.css │ │ │ └── root.tsx │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── vite-ssr-html/ │ │ ├── README.md │ │ ├── app/ │ │ │ └── entry-server.ts │ │ ├── index.html │ │ ├── package.json │ │ ├── routes/ │ │ │ └── quote.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── vite-ssr-preact/ │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── app.tsx │ │ │ ├── entry-client.tsx │ │ │ ├── entry-server.tsx │ │ │ └── styles.css │ │ ├── tsconfig.json │ │ └── vite.config.mjs │ ├── vite-ssr-react/ │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── app.tsx │ │ │ ├── entry-client.tsx │ │ │ ├── entry-server.tsx │ │ │ └── styles.css │ │ ├── tsconfig.json │ │ └── vite.config.mjs │ ├── vite-ssr-solid/ │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── app.tsx │ │ │ ├── entry-client.tsx │ │ │ ├── entry-server.tsx │ │ │ └── styles.css │ │ ├── tsconfig.json │ │ └── vite.config.mjs │ ├── vite-ssr-tsr-react/ │ │ ├── README.md │ │ ├── index.html │ │ ├── package.json │ │ ├── src/ │ │ │ ├── assets/ │ │ │ │ └── main.css │ │ │ ├── main.tsx │ │ │ ├── routeTree.gen.ts │ │ │ └── routes/ │ │ │ ├── __root.tsx │ │ │ └── index.tsx │ │ ├── tsconfig.json │ │ └── vite.config.mjs │ ├── vite-ssr-tss-react/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── package.json │ │ ├── server.ts │ │ ├── src/ │ │ │ ├── routeTree.gen.ts │ │ │ ├── router.tsx │ │ │ ├── routes/ │ │ │ │ ├── __root.tsx │ │ │ │ ├── api/ │ │ │ │ │ └── test.ts │ │ │ │ └── index.tsx │ │ │ └── styles/ │ │ │ └── app.css │ │ ├── tsconfig.json │ │ └── vite.config.mjs │ ├── vite-ssr-vue-router/ │ │ ├── README.md │ │ ├── app/ │ │ │ ├── app.vue │ │ │ ├── entry-client.ts │ │ │ ├── entry-server.ts │ │ │ ├── pages/ │ │ │ │ ├── about.vue │ │ │ │ ├── index.vue │ │ │ │ └── not-found.vue │ │ │ ├── routes.ts │ │ │ ├── shims.d.ts │ │ │ └── styles.css │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── vite.config.mjs │ ├── vite-trpc/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── index.html │ │ ├── package.json │ │ ├── server/ │ │ │ └── trpc.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ └── websocket/ │ ├── README.md │ ├── index.html │ ├── nitro.config.ts │ ├── package.json │ ├── routes/ │ │ └── _ws.ts │ ├── tsconfig.json │ └── vite.config.ts ├── lib/ │ ├── h3.d.mts │ ├── h3.mjs │ ├── tsconfig.json │ ├── vite.types.d.mts │ └── vite.types.mjs ├── package.json ├── playground/ │ ├── nitro.config.ts │ ├── package.json │ ├── server.ts │ ├── tsconfig.json │ └── vite.config.ts ├── pnpm-workspace.yaml ├── renovate.json ├── scripts/ │ ├── bump-nightly.ts │ ├── bump-version.ts │ ├── gen-node-compat.ts │ ├── gen-presets.ts │ ├── release.ts │ └── vite7.ts ├── skills/ │ └── nitro/ │ └── SKILL.md ├── src/ │ ├── build/ │ │ ├── assets.ts │ │ ├── build.ts │ │ ├── chunks.ts │ │ ├── config.ts │ │ ├── info.ts │ │ ├── plugins/ │ │ │ ├── externals.ts │ │ │ ├── oxc.ts │ │ │ ├── raw.ts │ │ │ ├── route-meta.ts │ │ │ ├── server-main.ts │ │ │ ├── sourcemap-min.ts │ │ │ └── virtual.ts │ │ ├── plugins.ts │ │ ├── prepare.ts │ │ ├── rolldown/ │ │ │ ├── build.ts │ │ │ ├── config.ts │ │ │ ├── dev.ts │ │ │ └── prod.ts │ │ ├── rollup/ │ │ │ ├── build.ts │ │ │ ├── config.ts │ │ │ ├── dev.ts │ │ │ ├── error.ts │ │ │ └── prod.ts │ │ ├── types.ts │ │ ├── virtual/ │ │ │ ├── _all.ts │ │ │ ├── database.ts │ │ │ ├── error-handler.ts │ │ │ ├── feature-flags.ts │ │ │ ├── plugins.ts │ │ │ ├── polyfills.ts │ │ │ ├── public-assets.ts │ │ │ ├── renderer-template.ts │ │ │ ├── routing-meta.ts │ │ │ ├── routing.ts │ │ │ ├── runtime-config.ts │ │ │ ├── server-assets.ts │ │ │ ├── storage.ts │ │ │ └── tasks.ts │ │ └── vite/ │ │ ├── build.ts │ │ ├── bundler.ts │ │ ├── dev.ts │ │ ├── env.ts │ │ ├── plugin.ts │ │ ├── preview.ts │ │ ├── prod.ts │ │ └── types.ts │ ├── builder.ts │ ├── cli/ │ │ ├── commands/ │ │ │ ├── build.ts │ │ │ ├── deploy.ts │ │ │ ├── dev.ts │ │ │ ├── docs.ts │ │ │ ├── prepare.ts │ │ │ ├── preview.ts │ │ │ └── task/ │ │ │ ├── index.ts │ │ │ ├── list.ts │ │ │ └── run.ts │ │ ├── common.ts │ │ └── index.ts │ ├── config/ │ │ ├── defaults.ts │ │ ├── loader.ts │ │ ├── resolvers/ │ │ │ ├── assets.ts │ │ │ ├── builder.ts │ │ │ ├── compatibility.ts │ │ │ ├── database.ts │ │ │ ├── error.ts │ │ │ ├── export-conditions.ts │ │ │ ├── imports.ts │ │ │ ├── open-api.ts │ │ │ ├── paths.ts │ │ │ ├── route-rules.ts │ │ │ ├── runtime-config.ts │ │ │ ├── storage.ts │ │ │ ├── tsconfig.ts │ │ │ ├── unenv.ts │ │ │ └── url.ts │ │ └── update.ts │ ├── dev/ │ │ ├── app.ts │ │ ├── server.ts │ │ └── vfs.ts │ ├── global.ts │ ├── module.ts │ ├── nitro.ts │ ├── prerender/ │ │ ├── prerender.ts │ │ └── utils.ts │ ├── presets/ │ │ ├── _all.gen.ts │ │ ├── _nitro/ │ │ │ ├── base-worker.ts │ │ │ ├── nitro-dev.ts │ │ │ ├── nitro-prerender.ts │ │ │ ├── preset.ts │ │ │ └── runtime/ │ │ │ ├── nitro-dev.ts │ │ │ ├── nitro-prerenderer.ts │ │ │ └── service-worker.ts │ │ ├── _resolve.ts │ │ ├── _static/ │ │ │ └── preset.ts │ │ ├── _types.gen.ts │ │ ├── _utils/ │ │ │ ├── fs.ts │ │ │ └── preset.ts │ │ ├── alwaysdata/ │ │ │ └── preset.ts │ │ ├── aws-amplify/ │ │ │ ├── preset.ts │ │ │ ├── runtime/ │ │ │ │ └── aws-amplify.ts │ │ │ ├── types.ts │ │ │ └── utils.ts │ │ ├── aws-lambda/ │ │ │ ├── preset.ts │ │ │ ├── runtime/ │ │ │ │ ├── _utils.ts │ │ │ │ ├── aws-lambda-streaming.ts │ │ │ │ └── aws-lambda.ts │ │ │ └── types.ts │ │ ├── azure/ │ │ │ ├── preset.ts │ │ │ ├── runtime/ │ │ │ │ ├── _utils.ts │ │ │ │ └── azure-swa.ts │ │ │ ├── types.ts │ │ │ └── utils.ts │ │ ├── bun/ │ │ │ ├── preset.ts │ │ │ └── runtime/ │ │ │ └── bun.ts │ │ ├── cleavr/ │ │ │ └── preset.ts │ │ ├── cloudflare/ │ │ │ ├── dev.ts │ │ │ ├── entry-exports.ts │ │ │ ├── preset.ts │ │ │ ├── runtime/ │ │ │ │ ├── _module-handler.ts │ │ │ │ ├── cloudflare-durable.ts │ │ │ │ ├── cloudflare-module.ts │ │ │ │ ├── cloudflare-pages.ts │ │ │ │ ├── plugin.dev.ts │ │ │ │ └── shims/ │ │ │ │ └── workers.dev.mjs │ │ │ ├── types.ts │ │ │ ├── unenv/ │ │ │ │ ├── node-compat.ts │ │ │ │ └── preset.ts │ │ │ ├── utils.ts │ │ │ └── wrangler/ │ │ │ ├── _utils.ts │ │ │ ├── config.ts │ │ │ └── environment.ts │ │ ├── deno/ │ │ │ ├── preset.ts │ │ │ ├── runtime/ │ │ │ │ ├── deno-deploy.ts │ │ │ │ └── deno-server.ts │ │ │ └── unenv/ │ │ │ ├── node-compat.ts │ │ │ └── preset.ts │ │ ├── digitalocean/ │ │ │ └── preset.ts │ │ ├── firebase/ │ │ │ ├── preset.ts │ │ │ └── types.ts │ │ ├── flightcontrol/ │ │ │ └── preset.ts │ │ ├── genezio/ │ │ │ └── preset.ts │ │ ├── heroku/ │ │ │ └── preset.ts │ │ ├── iis/ │ │ │ ├── preset.ts │ │ │ └── utils.ts │ │ ├── index.ts │ │ ├── koyeb/ │ │ │ └── preset.ts │ │ ├── netlify/ │ │ │ ├── preset.ts │ │ │ ├── runtime/ │ │ │ │ ├── netlify-edge.ts │ │ │ │ └── netlify.ts │ │ │ ├── types.ts │ │ │ └── utils.ts │ │ ├── node/ │ │ │ ├── cluster.ts │ │ │ ├── preset.ts │ │ │ └── runtime/ │ │ │ ├── node-cluster.ts │ │ │ ├── node-middleware.ts │ │ │ └── node-server.ts │ │ ├── platform.sh/ │ │ │ └── preset.ts │ │ ├── render.com/ │ │ │ └── preset.ts │ │ ├── standard/ │ │ │ ├── preset.ts │ │ │ └── runtime/ │ │ │ └── server.ts │ │ ├── stormkit/ │ │ │ ├── preset.ts │ │ │ └── runtime/ │ │ │ └── stormkit.ts │ │ ├── vercel/ │ │ │ ├── preset.ts │ │ │ ├── runtime/ │ │ │ │ ├── cron-handler.ts │ │ │ │ ├── isr.ts │ │ │ │ ├── vercel.node.ts │ │ │ │ └── vercel.web.ts │ │ │ ├── types.ts │ │ │ └── utils.ts │ │ ├── winterjs/ │ │ │ ├── preset.ts │ │ │ └── runtime/ │ │ │ └── winterjs.ts │ │ ├── zeabur/ │ │ │ ├── preset.ts │ │ │ └── runtime/ │ │ │ └── zeabur.ts │ │ ├── zephyr/ │ │ │ ├── preset.ts │ │ │ ├── runtime/ │ │ │ │ └── server.ts │ │ │ └── types.ts │ │ └── zerops/ │ │ └── preset.ts │ ├── preview.ts │ ├── routing.ts │ ├── runtime/ │ │ ├── app.ts │ │ ├── cache.ts │ │ ├── config.ts │ │ ├── context.ts │ │ ├── database.ts │ │ ├── internal/ │ │ │ ├── app.ts │ │ │ ├── cache.ts │ │ │ ├── context.ts │ │ │ ├── database.ts │ │ │ ├── empty.ts │ │ │ ├── error/ │ │ │ │ ├── dev.ts │ │ │ │ ├── hooks.ts │ │ │ │ ├── prod.ts │ │ │ │ └── utils.ts │ │ │ ├── meta.ts │ │ │ ├── plugin.ts │ │ │ ├── route-rules.ts │ │ │ ├── routes/ │ │ │ │ ├── dev-tasks.ts │ │ │ │ ├── openapi.ts │ │ │ │ ├── renderer-template.dev.ts │ │ │ │ ├── renderer-template.ts │ │ │ │ ├── scalar.ts │ │ │ │ └── swagger.ts │ │ │ ├── runtime-config.ts │ │ │ ├── static.ts │ │ │ ├── storage.ts │ │ │ ├── task.ts │ │ │ └── vite/ │ │ │ ├── dev-entry.mjs │ │ │ ├── dev-worker.mjs │ │ │ └── ssr-renderer.mjs │ │ ├── meta.ts │ │ ├── nitro.ts │ │ ├── runtime-config.ts │ │ ├── storage.ts │ │ ├── task.ts │ │ ├── virtual/ │ │ │ ├── _runtime_warn.ts │ │ │ ├── database.ts │ │ │ ├── error-handler.ts │ │ │ ├── feature-flags.ts │ │ │ ├── plugins.ts │ │ │ ├── polyfills.ts │ │ │ ├── public-assets.ts │ │ │ ├── renderer-template.ts │ │ │ ├── routing-meta.ts │ │ │ ├── routing.ts │ │ │ ├── runtime-config.ts │ │ │ ├── server-assets.ts │ │ │ ├── storage.ts │ │ │ └── tasks.ts │ │ └── vite.ts │ ├── scan.ts │ ├── task.ts │ ├── types/ │ │ ├── _utils.ts │ │ ├── build.ts │ │ ├── config.ts │ │ ├── fetch/ │ │ │ ├── _match.ts │ │ │ ├── _serialize.ts │ │ │ ├── fetch.ts │ │ │ └── index.ts │ │ ├── global.ts │ │ ├── h3.ts │ │ ├── handler.ts │ │ ├── hooks.ts │ │ ├── index.ts │ │ ├── module.ts │ │ ├── nitro.ts │ │ ├── openapi-ts.ts │ │ ├── openapi.ts │ │ ├── prerender.ts │ │ ├── preset.ts │ │ ├── route-rules.ts │ │ ├── runner.ts │ │ ├── runtime/ │ │ │ ├── asset.ts │ │ │ ├── cache.ts │ │ │ ├── index.ts │ │ │ ├── nitro.ts │ │ │ └── task.ts │ │ └── srvx.ts │ ├── utils/ │ │ ├── compress.ts │ │ ├── dep.ts │ │ ├── fs-tree.ts │ │ ├── fs.ts │ │ ├── parallel.ts │ │ └── regex.ts │ └── vite.ts ├── test/ │ ├── examples.test.ts │ ├── fixture/ │ │ ├── .env │ │ ├── .gitignore │ │ ├── error.ts │ │ ├── exports.cloudflare.ts │ │ ├── nitro.config.ts │ │ ├── package.json │ │ ├── public/ │ │ │ ├── _ignored.txt │ │ │ ├── _unignored.txt │ │ │ ├── build/ │ │ │ │ └── test.txt │ │ │ ├── cf-pages-exclude/ │ │ │ │ └── not-in-routes-json.txt │ │ │ ├── foo.css │ │ │ └── foo.js │ │ ├── server/ │ │ │ ├── assets/ │ │ │ │ ├── test.json │ │ │ │ └── test.md │ │ │ ├── files/ │ │ │ │ ├── index.html │ │ │ │ ├── sql.sql │ │ │ │ ├── sqlts.sql.ts │ │ │ │ └── test.txt │ │ │ ├── middleware/ │ │ │ │ └── _ignored.ts │ │ │ ├── plugins/ │ │ │ │ ├── errors.ts │ │ │ │ └── vary.ts │ │ │ ├── routes/ │ │ │ │ ├── (route-group)/ │ │ │ │ │ └── route-group.ts │ │ │ │ ├── 500.ts │ │ │ │ ├── api/ │ │ │ │ │ ├── _ignored.ts │ │ │ │ │ ├── cached.ts │ │ │ │ │ ├── db.ts │ │ │ │ │ ├── echo.ts │ │ │ │ │ ├── headers.ts │ │ │ │ │ ├── hello.ts │ │ │ │ │ ├── hey/ │ │ │ │ │ │ └── index.get.ts │ │ │ │ │ ├── kebab.ts │ │ │ │ │ ├── meta/ │ │ │ │ │ │ └── test.ts │ │ │ │ │ ├── methods/ │ │ │ │ │ │ ├── foo.get.get.ts │ │ │ │ │ │ └── get.ts │ │ │ │ │ ├── param/ │ │ │ │ │ │ └── [test-id].ts │ │ │ │ │ ├── storage/ │ │ │ │ │ │ ├── item.get.ts │ │ │ │ │ │ └── item.put.ts │ │ │ │ │ ├── upload.post.ts │ │ │ │ │ └── wildcard/ │ │ │ │ │ └── [...param].ts │ │ │ │ ├── assets/ │ │ │ │ │ ├── [id].ts │ │ │ │ │ ├── all.ts │ │ │ │ │ └── md.ts │ │ │ │ ├── config.ts │ │ │ │ ├── context.ts │ │ │ │ ├── env/ │ │ │ │ │ ├── index.dev.ts │ │ │ │ │ └── index.get.prod.ts │ │ │ │ ├── errors/ │ │ │ │ │ ├── captured.ts │ │ │ │ │ ├── stack.ts │ │ │ │ │ └── throw.ts │ │ │ │ ├── fetch.ts │ │ │ │ ├── file.ts │ │ │ │ ├── icon.png.ts │ │ │ │ ├── imports.ts │ │ │ │ ├── json-string.ts │ │ │ │ ├── jsx.tsx │ │ │ │ ├── modules.ts │ │ │ │ ├── node-compat.ts │ │ │ │ ├── prerender-custom.html.ts │ │ │ │ ├── prerender.ts │ │ │ │ ├── raw.ts │ │ │ │ ├── replace.ts │ │ │ │ ├── rules/ │ │ │ │ │ └── [...slug].ts │ │ │ │ ├── static-flags.ts │ │ │ │ ├── stream.ts │ │ │ │ ├── tasks/ │ │ │ │ │ └── [...name].ts │ │ │ │ ├── wait-until.ts │ │ │ │ └── wasm/ │ │ │ │ ├── dynamic-import.ts │ │ │ │ └── static-import.ts │ │ │ ├── tasks/ │ │ │ │ ├── db/ │ │ │ │ │ └── migrate.ts │ │ │ │ └── test.ts │ │ │ └── utils/ │ │ │ ├── foo/ │ │ │ │ ├── bar/ │ │ │ │ │ └── test.ts │ │ │ │ └── test.ts │ │ │ └── test.ts │ │ ├── server.config.ts │ │ ├── server.ts │ │ ├── tsconfig.json │ │ ├── vite.config.ts │ │ └── wrangler.toml │ ├── minimal/ │ │ ├── minimal.test.ts │ │ ├── nitro.config.ts │ │ ├── package.json │ │ ├── server.ts │ │ ├── tsconfig.json │ │ └── vite.config.mjs │ ├── presets/ │ │ ├── aws-lambda.test.ts │ │ ├── azure-swa.test.ts │ │ ├── bun.test.ts │ │ ├── cloudflare-module.test.ts │ │ ├── cloudflare-pages.test.ts │ │ ├── deno-server.test.ts │ │ ├── netlify.test.ts │ │ ├── nitro-dev.test.ts │ │ ├── node.test.ts │ │ ├── standard.test.ts │ │ ├── static.test.ts │ │ ├── vercel.test.ts │ │ └── winterjs.test.ts │ ├── scripts/ │ │ └── gen-fixture-types.ts │ ├── tests.ts │ ├── unit/ │ │ ├── azure.utils.test.ts │ │ ├── bump-version.test.ts │ │ ├── chunks.test.ts │ │ ├── config-loader-env.test.ts │ │ ├── runtime-config.env.test.ts │ │ ├── runtime-config.test.ts │ │ ├── sourcemap-min.test.ts │ │ ├── static-middleware.test.ts │ │ ├── virtual.test.ts │ │ └── zephyr-preset.test.ts │ └── vite/ │ ├── hmr-fixture/ │ │ ├── api/ │ │ │ └── state.ts │ │ ├── app/ │ │ │ ├── entry-client.ts │ │ │ └── entry-server.ts │ │ ├── shared.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ └── hmr.test.ts ├── tsconfig.json └── vitest.config.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .agents/architecture.md ================================================ # Nitro Architecture Deep Dive ## Core Instance (`src/nitro.ts`) `createNitro(config, opts)` creates the main context with: - `options: NitroOptions` — Resolved configuration - `hooks: Hookable` — Build lifecycle hooks - `vfs: Map` — Virtual file system - `routing: { routes, routeRules, globalMiddleware, routedMiddleware }` - `scannedHandlers: NitroEventHandler[]` - `unimport?: Unimport` — Auto-imports (optional) - `logger: ConsolaInstance` - `updateConfig(config)` — Hot-reload config - `close()` — Cleanup **Setup flow:** 1. Load options via `loadOptions()` 2. Install modules via `installModules()` 3. Init routing via `initNitroRouting()` 4. Scan handlers/plugins/tasks via `scanAndSyncOptions()` 5. Prepare unimport for auto-imports 6. Setup hooks ## Entry Points - `src/builder.ts` — Main public API: `createNitro()`, `build()`, `createDevServer()`, `prerender()`, `copyPublicAssets()`, `prepare()`, `writeTypes()`, `runTask()`, `listTasks()` - `src/vite.ts` — Vite plugin export from `src/build/vite/plugin.ts` ## Build System (`src/build/`) **Builder dispatch** (`build/build.ts`): delegates to `rollup`, `rolldown`, or `vite` based on `nitro.options.builder`. **Builder selection** (resolved in `config/resolvers/builder.ts`): - Check `NITRO_BUILDER` / `NITRO_VITE_BUILDER` env vars - Auto-detect available packages - Fallback: rolldown → vite → rollup **Base config** (`build/config.ts`): - Extensions: `.ts`, `.mjs`, `.js`, `.json`, `.node`, `.tsx`, `.jsx` - Import.meta replacements (`import.meta.dev`, `import.meta.preset`, etc.) - Unenv aliases for polyfills - External dependency patterns **Plugins** (`build/plugins.ts`): 1. Virtual modules — renders from `build/virtual/` 2. Auto imports — Unimport plugin 3. WASM loader — unwasm 4. Server main injection — `globalThis.__server_main__` 5. Raw imports — `?raw` suffix 6. Route meta — OpenAPI metadata 7. Replace plugin — variable substitution 8. Externals plugin — Node.js native resolution 9. Sourcemap minify (optional) **Virtual modules** (`build/virtual/`, 14 templates): All prefixed `#nitro/virtual/`: - `routing.ts` — Compiled router matcher - `plugins.ts` — Plugin registry - `error-handler.ts` — Error handler - `public-assets.ts` — Public asset metadata - `server-assets.ts` — Server asset metadata - `runtime-config.ts` — Runtime config object - `database.ts` — Database setup - `storage.ts` — Storage backends - `tasks.ts` — Task registry - `polyfills.ts` — Env polyfills - `feature-flags.ts` — Feature detection - `routing-meta.ts` — Route metadata (OpenAPI) - `renderer-template.ts` — SSR renderer - `_all.ts` — Aggregator ## Configuration System (`src/config/`) **Loader** (`config/loader.ts`): `loadOptions(config, opts)` 1. Merge with defaults (`NitroDefaults`) 2. Load c12 config files (`nitro.config.ts`, `package.json.nitro`, etc.) 3. Resolve preset 4. Run config resolvers sequentially **Resolvers** (`config/resolvers/`): `compatibility`, `tsconfig`, `paths`, `imports`, `route-rules`, `database`, `export-conditions`, `runtime-config`, `open-api`, `url`, `assets`, `storage`, `error`, `unenv`, `builder` **Defaults** (`config/defaults.ts`): All NitroConfig defaults. ## Runtime (`src/runtime/`) **Internal** (`runtime/internal/`): - `app.ts` — NitroApp creation, H3 app setup - `cache.ts` — Response caching - `context.ts` — Async context - `route-rules.ts` — Route rule middleware (headers, redirect, proxy, cache, cors) - `static.ts` — Static file serving - `task.ts` — Task execution - `plugin.ts` — Plugin helpers - `runtime-config.ts` — Config getter **Public exports**: `runtime/app.ts` (`defineConfig()`), `runtime/nitro.ts` (`serverFetch()`), `runtime/cache.ts`, `runtime/task.ts`, `runtime/storage.ts`, etc. ## Dev Server (`src/dev/`) - `dev/server.ts` — `NitroDevServer`: Worker management via `env-runner`, restart on failure (max 3 retries), WebSocket support, VFS debug endpoint (`/_vfs/**`) - `dev/app.ts` — `NitroDevApp`: H3 app with error handling, static serving with compression, dev proxy ## Prerender (`src/prerender/`) - `prerender/prerender.ts` — Main flow: parse routes → build prerenderer (preset: `nitro-prerender`) → execute in parallel → link crawling → write to disk → compress - `prerender/utils.ts` — `extractLinks()`, `matchesIgnorePattern()`, `formatPrerenderRoute()` ## Routing & Scanning (`src/routing.ts`, `src/scan.ts`) **Scanning**: Discovers routes, middleware, plugins, tasks, modules from filesystem. Route file conventions: - `routes/index.ts` → `GET /` - `routes/users/[id].ts` → `GET /users/:id` - `routes/users/[...slug].ts` → `GET /users/**:slug` - `api/users.post.ts` → `POST /api/users` - `.dev`/`.prod`/`.prerender` suffixes for environment filtering **Router** (`Router` class): Based on `rou3`, compiles to optimized string matcher, supports method routing + env conditions. ## Presets (`src/presets/`) 31 presets. Structure per preset: ``` presets// ├── preset.ts # defineNitroPreset() ├── runtime/ # Runtime entry (bundled) ├── types.ts # Types (optional) ├── utils.ts # Build-time utils (optional) └── unenv/ # Env overrides (optional) ``` Key presets: `standard`, `node` (server/middleware/cluster), `cloudflare` (pages/workers), `vercel`, `netlify`, `aws-lambda`, `deno`, `firebase`, `azure`, `bun`, `winterjs` Resolution: `presets/_resolve.ts` handles aliases, dev/prod, compat dates, static hosting. ## CLI (`src/cli/`) Uses `citty` with lazy-loaded commands: `dev`, `build`, `deploy`, `preview`, `prepare`, `task`, `docs`. ## Key Libraries | Library | Purpose | |---------|---------| | `h3` | HTTP framework | | `rou3` | Route matching | | `c12` | Config loading | | `citty` | CLI framework | | `hookable` | Hook system | | `unimport` | Auto-imports | | `unstorage` | Storage abstraction | | `unenv` | Runtime polyfills | | `defu` | Config merging | | `pathe` | Path operations | | `consola` | Logging | | `env-runner` | Worker management | ================================================ FILE: .agents/docs.md ================================================ # Documentation Guide ## Structure Documentation lives in `docs/` and is built with [UnDocs](https://github.com/unjs/undocs). ``` docs/ .docs/ # UnDocs Nuxt app (components, pages, layouts, utils) .config/ # docs.yaml (site config), automd.config.ts 1.docs/ # Core documentation (getting started, routing, cache, etc.) 2.deploy/ # Deployment docs (runtimes, providers) 3.config/ # Config reference 4.examples/ # Examples index index.md # Homepage ``` Numeric prefixes control navigation order. Files with the same prefix are sorted alphabetically. ## Conventions ### Preset Names Canonical preset names use **underscores** (e.g., `node_server`, `cloudflare_module`, `digital_ocean`). Both underscores and hyphens are supported at runtime (resolved via `kebabCase`), but docs should use underscore form. ### Import Paths Nitro v3 uses subpath exports — not deep runtime imports: ```ts import { defineHandler, readBody, getQuery } from "nitro/h3"; import { defineCachedHandler, defineCachedFunction } from "nitro/cache"; import { useStorage } from "nitro/storage"; import { useDatabase } from "nitro/database"; import { useRuntimeConfig } from "nitro/runtime-config"; import { defineNitroConfig } from "nitro/config"; import { definePlugin } from "nitro"; // runtime plugin import { defineRouteMeta } from "nitro"; // route meta macro ``` ### H3 v2 API Nitro v3 uses H3 v2. Key differences from v1: - **Handler**: `defineHandler()` (not `eventHandler` / `defineEventHandler`) - **Error**: `throw new HTTPError(message, { status })` (not `createError()`) - **Router**: `new H3()` (not `createApp()` / `createRouter()`) - **Response**: Return values directly; no `send()` function - **Headers**: `event.res.headers.set(name, value)` (not `setResponseHeader(event, name, value)`) - **Hooks**: `request` hook receives `(event: HTTPEvent)`, not `(req)` ### Code Examples - **Auto imports are not available** — always show explicit imports in examples - Always use `defineHandler` from `"nitro/h3"` (not `eventHandler`) - Always use `defineNitroConfig` from `"nitro/config"` (not `defineConfig`) - Include import statements in code examples - Use `"nitro/*"` imports, never `"nitropack/*"` ### Node.js Version Nitro v3 requires Node.js >= 20. All deployment docs should reference Node.js 20+ (not 16 or 18). ### Environment Variables The preset env var is `NITRO_PRESET` (not `SERVER_PRESET` or any other name). ### Runtime Config - Prefix: `NITRO_` for env var overrides - camelCase in config, UPPER_SNAKE_CASE in env vars ## Common Mistakes to Avoid - Using `send(event, value)` — removed in h3 v2, return values directly - Using `createError()` — use `new HTTPError()` or `HTTPError.status()` - Using `eventHandler()` — use `defineHandler()` - Using `defineConfig()` for nitro config — use `defineNitroConfig()` - Duplicate imports (e.g., importing `defineHandler` from both `nitro/h3` and `nitro/cache`) - Wrong env var names (e.g., `NITR_PRESET`, `SERVER_PRESET`) - Outdated Node.js versions in deployment examples - Using hyphen preset names in docs (use underscores) ## MDC Syntax Reference Docs use [MDC](https://content.nuxt.com/) (Markdown Components) syntax to embed Vue components in markdown. ### Block Components Use `::` for block components. Nesting increases the colon count: ```markdown ::component-name Content here :: ::component{prop="value" boolProp} Content :: ``` Nested (each level adds one `:`): ```markdown ::parent :::child Content ::: :: ``` ### Props **Inline:** `::alert{type="warning" icon="i-lucide-alert"}` **YAML block** (for multiple props): ```markdown ::component --- title: My Title icon: i-lucide-rocket --- Content :: ``` ### Slots Named slots use `#`: ```markdown ::hero Default slot content #title Title slot content #description Description slot content #links :::u-button{to="/docs"} Get Started ::: :: ``` ### Inline Components & Attributes ```markdown :inline-component{prop="value"} Hello [World]{.text-primary style="color: green;"} ``` ### Variables ```markdown --- title: My Page --- # {{ $doc.title }} ``` ## Prose Components (Typography) These are available in markdown files for documentation content. Provided by [Nuxt UI](https://ui.nuxt.com/). ### Callouts ```markdown ::note Additional information for the user. :: ::tip Helpful suggestion or best practice. :: ::warning Caution about potential unexpected results. :: ::caution Warning about irreversible or dangerous actions. :: ``` Generic callout with props: ```markdown ::callout{icon="i-lucide-info" color="primary"} Custom callout content with **markdown**. :: ``` Colors: `primary`, `secondary`, `success`, `info`, `warning`, `error`, `neutral`. ### Tabs ```markdown ::tabs :::tabs-item{label="npm" icon="i-lucide-package"} ```bash npm install nitro ``` ::: :::tabs-item{label="pnpm"} ```bash pnpm add nitro ``` ::: :: ``` Props: `orientation` (`horizontal`|`vertical`), `defaultValue`, `content`, `unmountOnHide`. ### Steps ```markdown ::steps{level="3"} ### Install Install the package. ### Configure Add to your config. ### Deploy Deploy your app. :: ``` `level` prop: `"2"`, `"3"` (default), `"4"` — determines which heading level becomes numbered steps. ### Code Group ```markdown ::code-group ```ts [nuxt.config.ts] export default defineNuxtConfig({}) ``` ```ts [nitro.config.ts] export default defineNitroConfig({}) ``` :: ``` Props: `defaultValue`, `sync` (persists selection to localStorage). ### Code Tree Interactive file tree with code preview: ```markdown ::code-tree{defaultValue="routes/hello.ts" expand-all} ::prose-pre{filename="routes/hello.ts"} ```ts export default defineHandler(() => 'Hello!') ``` :: ::prose-pre{filename="vite.config.ts"} ```ts import { nitro } from 'nitro/vite' export default defineConfig({ plugins: [nitro()] }) ``` :: :: ``` Props: `defaultValue`, `expandAll`, `items`. ### Card ```markdown ::card{title="Storage" icon="i-lucide-database" to="/docs/storage"} Access key-value storage in your handlers. :: ``` Props: `title`, `icon`, `color`, `to`, `target`, `variant` (`solid`|`outline`|`soft`|`subtle`). ### Field Document API parameters: ```markdown ::field{name="preset" type="string" required} The deployment preset to use. :: ``` Props: `name`, `type`, `description`, `required`. ### Collapsible ```markdown ::collapsible{name="Advanced Options"} Hidden content shown on expand. :: ``` Props: `name`, `size`, `color`, `defaultOpen`, `unmountOnHide`. ### Kbd (Keyboard) `:kbd[Ctrl]` + `:kbd[C]` renders keyboard shortcuts inline. ### Icon `:icon{name="i-lucide-rocket"}` renders an inline icon. ### Prose Pre (Code Block) Explicit code block with filename: ```markdown ::prose-pre{filename="server.ts"} ```ts export default { fetch: () => new Response('ok') } ``` :: ``` ## Landing Page Components These are Nuxt UI `Page*` components used in `docs/index.md` for the homepage. Prefix with `u-` in MDC. ### PageHero (`::u-page-hero`) ```markdown ::u-page-hero --- orientation: horizontal --- #title Ship [Full-Stack]{.text-primary} Vite Apps #description Build production-ready server applications. #links :::u-button{size="xl" to="/docs"} Get Started ::: #default :::some-illustration ::: :: ``` Props: `title`, `description`, `headline`, `orientation` (`vertical`|`horizontal`), `reverse`, `links` (ButtonProps[]). Slots: `top`, `header`, `headline`, `title`, `description`, `body`, `footer`, `links`, `default`, `bottom`. ### PageSection (`::u-page-section`) ```markdown ::u-page-section --- orientation: horizontal features: - title: Feature One description: Description here icon: i-lucide-zap --- #title Section Title #description Section description text. :: ``` Props: `headline`, `icon`, `title`, `description`, `orientation`, `reverse`, `links` (ButtonProps[]), `features` (PageFeatureProps[]). Slots: `top`, `header`, `leading`, `headline`, `title`, `description`, `body`, `features`, `footer`, `links`, `default`, `bottom`. ### PageFeature (`::u-page-feature`) ```markdown :::::u-page-feature #title Feature Name #description Feature description text. ::::: ``` Props: `icon`, `title`, `description`, `orientation` (`horizontal`|`vertical`), `to`, `target`. Slots: `leading`, `title`, `description`, `default`. ### PageGrid (`::u-page-grid`) Responsive grid (1→2→3 columns). Wraps `PageCard` or `PageFeature` children: ```markdown ::::u-page-grid :::::u-page-card{title="Card" icon="i-lucide-box"} Card content ::::: :::: ``` ### PageCard (`::u-page-card`) ```markdown ::u-page-card{title="Title" icon="i-lucide-box" to="/link"} Card body content. :: ``` Props: `icon`, `title`, `description`, `orientation`, `reverse`, `highlight`, `highlightColor`, `spotlight`, `spotlightColor`, `variant`, `to`, `target`. Slots: `header`, `leading`, `title`, `description`, `body`, `footer`, `default`. ### PageCTA (`::u-page-cta`) Call-to-action block: ```markdown ::u-page-cta --- variant: solid links: - label: Get Started to: /docs color: neutral --- #title Ready to get started? #description Deploy your app in minutes. :: ``` Props: `title`, `description`, `orientation`, `reverse`, `variant` (`outline`|`solid`|`soft`|`subtle`|`naked`), `links`. ### PageLogos (`::u-page-logos`) ```markdown ::u-page-logos --- title: Trusted by marquee: true items: - i-simple-icons-github - i-simple-icons-vercel --- :: ``` Props: `title`, `items` (icon strings or `{src, alt}` objects), `marquee` (boolean or MarqueeProps). ### PageLinks (`::u-page-links`) ```markdown ::u-page-links --- title: Community links: - label: GitHub icon: i-simple-icons-github to: https://github.com/nitrojs/nitro --- :: ``` ### Other Page Components - **PageHeader** — Page title/description header - **PageBody** — Main content wrapper - **PageColumns** — Multi-column layout - **PageList** — Vertical list of items - **PageAnchors** — Anchor link navigation - **PageAside** — Sidebar content ## Nuxt Content Querying (for custom components) ```ts // Single page by path const page = await queryCollection('docs').path('/hello').first() // Filtered list const posts = await queryCollection('blog') .where('draft', '=', false) .order('date', 'DESC') .all() // Navigation tree const nav = await queryCollectionNavigation('docs') // Prev/next const [prev, next] = await queryCollectionItemSurroundings('docs', '/current') ``` ## Custom Components Project-specific components live in `docs/.docs/components/` and can be used in markdown with `:component-name` or `::component-name` syntax (e.g., `:page-sponsors`, `:hero-background` as seen in `index.md`). ================================================ FILE: .agents/presets.md ================================================ # Nitro Presets Reference ## All Presets (31) ### Core - `_nitro/` — Internal presets (dev, prerender, worker modes) - `standard/` — Framework-agnostic standard server - `node/` — Node.js (server, middleware, cluster) - `bun/` — Bun runtime ### Cloud Providers - `aws-lambda/` — AWS Lambda - `aws-amplify/` — AWS Amplify - `azure/` — Azure Static Web Apps - `cloudflare/` — Cloudflare Pages/Workers - `deno/` — Deno Deploy - `firebase/` — Firebase Hosting - `netlify/` — Netlify Functions/Edge - `vercel/` — Vercel Functions/Edge - `digitalocean/` — DigitalOcean App Platform - `heroku/` — Heroku - `koyeb/` — Koyeb - `zeabur/` — Zeabur - `render.com/` — Render - `stormkit/` — Stormkit - `genezio/` — Genezio - `winterjs/` — WinterJS - `zephyr/` — Zephyr - `alwaysdata/` - `cleavr/` - `flightcontrol/` - `iis/` - `platform.sh/` ## Preset Structure ``` presets// ├── preset.ts # defineNitroPreset() — config overrides, hooks ├── runtime/ # Runtime entry points (bundled into output) │ └── .ts # Platform-specific request handler ├── types.ts # TypeScript types (optional) ├── utils.ts # Build-time utilities (optional) └── unenv/ # Environment polyfill overrides (optional) ├── preset.ts └── node-compat.ts ``` ## Creating a Preset Use `defineNitroPreset()` from `src/presets/_utils/preset.ts`: ```ts import { defineNitroPreset } from "../_utils/preset.ts"; export default defineNitroPreset({ // Preset metadata entry: "./runtime/.ts", // NitroConfig overrides node: false, // Hooks hooks: { "build:before": async (nitro) => { /* ... */ }, }, }); ``` ## Preset Resolution (`presets/_resolve.ts`) `resolvePreset(name, opts)` considers: - Preset name aliases - Dev vs production mode - Compatibility dates - Static hosting detection - Generated mappings in `_all.gen.ts` and `_types.gen.ts` ================================================ FILE: .agents/skills/update-deps/SKILL.md ================================================ # Update Dependencies Skill This skill guides you through the process of updating dependencies in the Nitro repository. ## Step-by-Step Process ### Ensure Clean State Check that you're on a clean main branch with latest changes. - Clean working directory on main branch - Latest changes pulled from remote ```bash git checkout main git pull origin main git status # Should show "nothing to commit, working tree clean" ``` (if branch name starts with chore, you can stay in it, no need to pull or change branch or clean state) ### Initial Install Run an initial install to ensure everything is up to date: ```bash pnpm install ``` ### Run pnpm upgrade -r Run `pnpm upgrade -r` to update non-major versions. After upgrade, check git diff: - Make sure range types does not change in `dependencies` field (example: `"h3": "^2.0.1-rc.7"` should remain `"h3": "^2.0.1-rc.7",` not `"h3": "2.0.1-rc.7",`) - Make sure dependencies are not converted to `link:..` (example: `"nitro": "latest",` should remain same, instead of `"nitro": "link:../.."`) **Fix workspace package link references:** `pnpm upgrade -r` often incorrectly converts workspace package references (like `"nitro": "latest"`) to link format (`"nitro": "link:../.."`) in monorepo packages. Check git diff for any workspace packages that were converted to `link:` format: ```bash # Check for any link: conversions in modified files git diff --name-only | xargs grep -l '"link:' 2>/dev/null ``` If found, revert them back to their original format. For this repo, `"nitro"` should always be `"latest"`: ```bash # Revert nitro link references back to latest in all modified package.json files git diff --name-only | grep 'package.json$' | while read file; do if grep -q '"nitro": "link:' "$file" 2>/dev/null; then sed -i 's/"nitro": "link:[^"]*"/"nitro": "latest"/g' "$file" echo "Fixed: $file" fi done ``` **Fix caret prefix removal:** If any dependencies in root `package.json` lost their `^` prefix, restore them manually. ### Check for Outdated Dependencies Find outdated dependencies: ```bash pnpm outdated -r ``` **IMPORTANT**: Check for newer beta/alpha/rc versions manually. `pnpm outdated` doesn't show pre-release updates. Check each package with beta/alpha/rc versions in package.json: ```bash # List all versions including pre-releases pnpm show vite versions --json | grep -E "beta|alpha|rc" | tail -5 pnpm show youch versions --json | grep -E "beta|alpha|rc" | tail -5 ``` Or check all versions for a specific package: ```bash pnpm show versions ``` ### 4. Update Dependencies Manually update all dependencies to their latest versions in [package.json](../package.json): - Update both `dependencies` and `devDependencies` - Keep the range prefix (e.g., `^` for caret ranges) - **For beta/alpha/rc packages**: Update to the latest pre-release tag found in step 3 - Example: `vite: "8.0.0-beta.6"` → `"8.0.0-beta.7"` - Example: `h3: "^2.0.1-rc.7"` → `"^2.0.1-rc.8"` (if available) - Maintain version range conventions (prefer `^` over exact versions) - **Do not update** `@azure/functions` ### 5. Clean Install Remove lock file and node_modules, then reinstall: ```bash rm -rf node_modules pnpm-lock.yaml pnpm i ``` ### 6. Lint and Fix Run linting and auto-fix issues: ```bash pnpm format ``` ### 7. Build Project Build the project to ensure compatibility: ```bash pnpm build ``` ### 9. Fix Remaining Issues If there are lint or type errors: 1. Review the output carefully 2. Fix issues manually following the project conventions 3. Re-run `pnpm format` to verify lint fixes 4. Re-run `pnpm typecheck` to verify type fixes. Ignore errors, only report them in the end. ### 10. Final Do not commit changes. Only summarize what happened. ## Common Issues ### Breaking Changes If a dependency has breaking changes: - Check the package's changelog/release notes - Update code to match new API if needed - Consider pinning to previous major version if breaking changes are too extensive ### Build Failures If the build fails after updates: - Check for TypeScript errors first: `pnpm typecheck` - Review error messages for deprecated APIs - Consider updating dependencies one at a time to isolate issues ### Lock File Conflicts - Test thoroughly after updates, especially major version bumps - Review changelogs for significant updates ================================================ FILE: .agents/testing.md ================================================ # Nitro Testing Guide ## Test Structure ``` test/ ├── tests.ts # Main test definitions (shared across presets) ├── fixture/ # Test fixture Nitro app │ ├── nitro.config.ts │ ├── routes/ # Test route handlers │ ├── api/ # Test API handlers │ ├── middleware/ # Test middleware │ ├── plugins/ # Test plugins │ └── public/ # Test static assets ├── presets/ # Per-preset test setup │ ├── node.test.ts │ ├── cloudflare.test.ts │ ├── vercel.test.ts │ └── ... ├── unit/ # Isolated unit tests └── minimal/ # Minimal bundle output tests ``` ## How Tests Work 1. `test/tests.ts` defines shared test cases using vitest 2. Each `test/presets/.test.ts` imports shared tests and runs them against a specific preset 3. The test fixture in `test/fixture/` is a full Nitro app used as the test target 4. Preset tests build the fixture with the preset, then run HTTP assertions ## Adding Regression Tests 1. Add test route/handler to `test/fixture/` (e.g., `test/fixture/routes/new-feature.ts`) 2. Add test case to `test/tests.ts` 3. Run `pnpm vitest run test/presets/node.test.ts` to verify ## Running Tests ```bash # Run all tests pnpm test # Run specific preset test pnpm vitest run test/presets/node.test.ts # Run unit tests pnpm vitest run test/unit/ # Run minimal bundle test pnpm vitest run test/minimal/ ``` ## Bug Fix Workflow 1. Write regression test in `test/fixture/` + `test/tests.ts` 2. Confirm it **fails** (`pnpm vitest run test/presets/node.test.ts`) 3. Fix the implementation 4. Confirm it **passes** 5. Run full suite (`pnpm test`) ================================================ FILE: .agents/vite.md ================================================ # Nitro Vite Build System ## Overview `src/build/vite/` is Nitro's Vite-based build system using Vite 6+ multi-environment API. It integrates as a Vite plugin (`nitro()`) that manages server builds, service environments, dev server, and production output. ## File Map | File | Purpose | |------|---------| | `plugin.ts` | Main plugin — 6 sub-plugins orchestrating the build | | `env.ts` | Vite environment creation (nitro, services, env-runner) | | `dev.ts` | Dev server integration, `FetchableDevEnvironment`, middleware | | `prod.ts` | Production multi-env build, asset management, virtual setup module | | `bundler.ts` | Rollup/Rolldown config generation | | `build.ts` | CLI build entry for `nitro build` (`viteBuild()`) | | `preview.ts` | Preview server plugin | | `types.ts` | Type definitions (`NitroPluginConfig`, `NitroPluginContext`) | ## Plugin Architecture (`plugin.ts`) `nitro(config?)` returns an array of 6 sub-plugins: ### 1. `nitroInit` — Context Setup - Calls `setupNitroContext()` on first `config` hook - Creates Nitro instance via `createNitro()` - Detects Rolldown vs Rollup (`_isRolldown`) - Resolves bundler config via `getBundlerConfig()` - Initializes env-runner in dev mode - Attaches rollup plugins for dev environments ### 2. `nitroEnv` — Environment Registration - Registers Vite environments: `client`, `nitro`, and user services - Auto-detects `entry-server` for SSR - Configures per-environment build options (consumer type, externals, etc.) ### 3. `nitroMain` — Build Orchestration - Sets app type to `"custom"` - Configures resolve aliases, server port - `buildApp` hook → calls `buildEnvironments()` (production) - `generateBundle` hook → tracks entry points - `configureServer` → calls `configureViteDevServer()` (dev) - `hotUpdate` → server-only module reload ### 4. `nitroPrepare` — Output Cleanup - Cleans build directory before build starts ### 5. `nitroService` — Virtual Module Handler - Resolves `#nitro-vite-setup` virtual module - Provides production setup code for service environments ### 6. `nitroPreviewPlugin` — Preview Server - Routes all preview requests through Nitro - WebSocket upgrade support ## `setupNitroContext()` Flow 1. Merge plugin config with user config 2. Load dotenv files 3. Detect SSR entry (looks for `entry-server.{ts,js,tsx,jsx,mjs}`) 4. Create Nitro instance (`createNitro()`) 5. Resolve bundler config (`getBundlerConfig()`) 6. Initialize env-runner for dev (`initEnvRunner()`) ## Environments (`env.ts`) Nitro uses Vite 6+ environments API for multi-bundle builds: | Environment | Consumer | Purpose | |-------------|----------|---------| | `client` | `"client"` | Browser HTML/CSS/JS | | `nitro` | `"server"` | Main server bundle | | `ssr` | `"server"` | Optional SSR service | | Custom | `"server"` | User-defined services | ### `createNitroEnvironment()` - Consumer: `"server"` - Uses bundler config (Rollup/Rolldown) - Dev: creates `FetchableDevEnvironment` with hot reload - Prod: standard environment with minify, sourcemap, commonJS options - Resolve: `noExternal` differs for dev vs prod - Special conditions: `"workerd"` for miniflare, excludes `"node"` ### `initEnvRunner()` / `getEnvRunner()` - Uses `env-runner` package for worker management - Supports Node Worker or Miniflare runtime - Auto-restarts on failure (max 3 retries) - Custom evaluator for workerd (`AsyncFunction` not allowed) - Routes module imports through Vite's transform pipeline ### `reloadEnvRunner()` - Triggers full reload of the env-runner worker ## Dev Server (`dev.ts`) ### `FetchableDevEnvironment` (extends `DevEnvironment`) - Overrides `fetchModule()` for CJS/ESM resolution - For workerd: prevents externalization of bare imports - `dispatchFetch()` — routes requests to the dev server worker - Sends custom message on init with environment info ### `configureViteDevServer()` - Watches Nitro config file for changes (triggers full restart) - WebSocket upgrade handling - File watchers for route/API/middleware directories (debounced reload) - RPC for `transformHTML` messages ### Dev Middleware (`nitroDevMiddleware`) Two-stage request routing: 1. **Pre-processor** — checks if request should go to Nitro: - Skips Vite internal requests (`/@`, `/__`) - Skips file extension requests (`.js`, `.css`, etc.) - Uses `sec-fetch-dest` header for browser detection - Routes to `NitroDevApp` first (static/proxy/dev handlers) 2. **Main handler** — falls back to env-runner worker for server routes ### Request Flow (Dev) ``` Browser → Vite Dev Server → nitroDevMiddleware (pre-processor) → NitroDevApp (static assets, dev proxy, /_vfs) → env-runner worker (main server logic) → Vite static/HMR (if not handled) ``` ## Production Build (`prod.ts`) ### `buildEnvironments()` — 5 Stages **Stage 1: Build non-Nitro environments** - Client environment (browser bundle) - Service environments (SSR, API, custom) - Detailed logging per environment **Stage 2: Renderer template processing** - If client input == renderer template, replaces SSR outlet - Inlines `globalThis.__nitro_vite_envs__?.["ssr"]?.fetch($REQUEST)` - Moves processed template to build dir **Stage 3: Asset management** - Calls `builder.writeAssetsManifest?.()` - Registers asset dirs with `max-age=31536000, immutable` **Stage 4: Build Nitro environment** - `prepare()` → clean output - Build main server bundle - Close Nitro instance - Fire `compiled` hook - Write build info **Stage 5: Preview** - Start preview server, log success ### `prodSetup()` Virtual Module Generates `#nitro-vite-setup` content: ```js // For each service environment globalThis.__nitro_vite_envs__ = { "ssr": { fetch: (...args) => import("entry").then(m => m.default.fetch(...args)) } } ``` ## Bundler Config (`bundler.ts`) `getBundlerConfig()` returns: ```ts { base: BaseBuildConfig, rollupConfig?: RollupConfig, // if using Rollup rolldownConfig?: RolldownConfig // if using Rolldown } ``` Common config: ESM output, tree-shaking, chunking, sourcemaps. **Rolldown-specific**: Transform injection, library chunking, `inlineDynamicImports`/`iife` support. **Rollup-specific**: `@rollup/plugin-inject`, `@rollup/plugin-alias`, manual chunk naming. ## HMR (Dev Only) **Server-only module reload**: 1. `hotUpdate` hook detects file change 2. Determines if module is server-only or shared 3. Server-only → sends `full-reload` to nitro environment 4. Shared → returns for normal Vite client HMR 5. Optionally reloads browser (`experimental.vite.serverReload`) **Directory watchers** (debounced): - Routes, API, middleware, plugins, modules dirs - Add/delete → full routing rebuild + reload ## Runtime Integration ### Worker Entry (`src/runtime/internal/vite/`) | File | Purpose | |------|---------| | `dev-entry.mjs` | Dev entry: polyfills, WebSocket adapter, schedule runner | | `dev-worker.mjs` | Worker process: `ViteEnvRunner` class, RPC layer, env management | | `ssr-renderer.mjs` | SSR service: calls `fetchViteEnv("ssr", req)` | ### `ViteEnvRunner` (in `dev-worker.mjs`) - Manages Vite `ModuleRunner` per environment - Loads environment entry via `runner.import()` - Routes fetch requests to loaded entries - Exposes `__VITE_ENVIRONMENT_RUNNER_IMPORT__` for RSC ### Runtime API (`src/runtime/vite.ts`) - `fetchViteEnv(name, input, init)` — route fetch to named Vite environment - Accesses `globalThis.__nitro_vite_envs__` registry ## Dev vs Production | Aspect | Dev | Production | |--------|-----|-----------| | Runner | env-runner (node-worker / miniflare) | Bundled ESM | | HMR | Full reload on file change | N/A | | Errors | Interactive error page (Youch) | JSON or minimal HTML | | Services | Lazy-loaded via env-runner | Pre-bundled via `prodSetup()` | | Template | Dynamic (vite-env route) | Static (inlined SSR outlet) | | Sourcemaps | Enabled | Optional | ## Experimental Features `experimental.vite` options: - `assetsImport` (default: true) — `?assets` imports via `@hiogawa/vite-plugin-fullstack` - `serverReload` (default: true) — reload on server-only module changes - `services` — register custom service environments ## Key Connections - `src/vite.ts` — public export (`nitro` plugin) - `src/build/build.ts` — dispatcher calls `viteBuild()` - `src/build/config.ts` — base build config - `src/build/plugins.ts` — base build plugins (virtual modules, auto-imports, etc.) - `src/build/virtual/` — 14 virtual module templates - `src/dev/app.ts` — `NitroDevApp` for dev-only handlers - `src/dev/server.ts` — `NitroDevServer` with `RunnerManager` - `src/runtime/internal/vite/` — runtime worker and entry points ================================================ FILE: .devcontainer/devcontainer.json ================================================ // https://code.visualstudio.com/docs/devcontainers/containers // https://containers.dev/implementors/json_reference/ { "name": "nitro-devcontainer", "forwardPorts": [3000], "image": "node:lts", "features": {}, "customizations": { "vscode": { "settings": {}, "extensions": [ "ms-azuretools.vscode-docker", "github.vscode-github-actions", "oxc.oxc-vscode" ] } }, "postStartCommand": "npm i -fg corepack && corepack enable && pnpm install && pnpm build --stub", "mounts": ["type=volume,target=${containerWorkspaceFolder}/node_modules"] } ================================================ FILE: .editorconfig ================================================ root = true [*] end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true charset = utf-8 [*.js] indent_style = space indent_size = 2 [{package.json,*.yml,*.cjson}] indent_style = space indent_size = 2 ================================================ FILE: .eslintcache ================================================ [{"/home/pooya/Code/nitro/AGENTS.md":"1","/home/pooya/Code/nitro/CHANGELOG.md":"2","/home/pooya/Code/nitro/CLAUDE.md":"3","/home/pooya/Code/nitro/CODE_OF_CONDUCT.md":"4","/home/pooya/Code/nitro/CONTRIBUTING.md":"5","/home/pooya/Code/nitro/README.md":"6","/home/pooya/Code/nitro/SECURITY.md":"7","/home/pooya/Code/nitro/automd.config.ts":"8","/home/pooya/Code/nitro/build.config.ts":"9","/home/pooya/Code/nitro/changelog.config.ts":"10","/home/pooya/Code/nitro/docs/.config/automd.config.ts":"11","/home/pooya/Code/nitro/eslint.config.mjs":"12","/home/pooya/Code/nitro/examples/api-routes/api/hello/[name].ts":"13","/home/pooya/Code/nitro/examples/api-routes/api/hello.ts":"14","/home/pooya/Code/nitro/examples/api-routes/api/test.get.ts":"15","/home/pooya/Code/nitro/examples/api-routes/api/test.post.ts":"16","/home/pooya/Code/nitro/examples/api-routes/nitro.config.ts":"17","/home/pooya/Code/nitro/examples/api-routes/vite.config.ts":"18","/home/pooya/Code/nitro/examples/auto-imports/nitro.config.ts":"19","/home/pooya/Code/nitro/examples/auto-imports/server/utils/hello.ts":"20","/home/pooya/Code/nitro/examples/auto-imports/server.ts":"21","/home/pooya/Code/nitro/examples/auto-imports/vite.config.ts":"22","/home/pooya/Code/nitro/examples/cached-handler/nitro.config.ts":"23","/home/pooya/Code/nitro/examples/cached-handler/server.ts":"24","/home/pooya/Code/nitro/examples/cached-handler/vite.config.ts":"25","/home/pooya/Code/nitro/examples/custom-error-handler/error.ts":"26","/home/pooya/Code/nitro/examples/custom-error-handler/nitro.config.ts":"27","/home/pooya/Code/nitro/examples/custom-error-handler/server.ts":"28","/home/pooya/Code/nitro/examples/custom-error-handler/vite.config.ts":"29","/home/pooya/Code/nitro/examples/database/nitro.config.ts":"30","/home/pooya/Code/nitro/examples/database/server.ts":"31","/home/pooya/Code/nitro/examples/database/tasks/db/migrate.ts":"32","/home/pooya/Code/nitro/examples/database/vite.config.ts":"33","/home/pooya/Code/nitro/examples/elysia/nitro.config.ts":"34","/home/pooya/Code/nitro/examples/elysia/server.ts":"35","/home/pooya/Code/nitro/examples/elysia/vite.config.ts":"36","/home/pooya/Code/nitro/examples/express/nitro.config.ts":"37","/home/pooya/Code/nitro/examples/express/server.node.ts":"38","/home/pooya/Code/nitro/examples/express/vite.config.ts":"39","/home/pooya/Code/nitro/examples/fastify/nitro.config.ts":"40","/home/pooya/Code/nitro/examples/fastify/server.node.ts":"41","/home/pooya/Code/nitro/examples/fastify/vite.config.ts":"42","/home/pooya/Code/nitro/examples/hello-world/nitro.config.ts":"43","/home/pooya/Code/nitro/examples/hello-world/server.ts":"44","/home/pooya/Code/nitro/examples/hello-world/vite.config.ts":"45","/home/pooya/Code/nitro/examples/hono/nitro.config.ts":"46","/home/pooya/Code/nitro/examples/hono/server.ts":"47","/home/pooya/Code/nitro/examples/hono/vite.config.ts":"48","/home/pooya/Code/nitro/examples/import-alias/nitro.config.ts":"49","/home/pooya/Code/nitro/examples/import-alias/server/routes/index.ts":"50","/home/pooya/Code/nitro/examples/import-alias/server/utils/math.ts":"51","/home/pooya/Code/nitro/examples/import-alias/vite.config.ts":"52","/home/pooya/Code/nitro/examples/middleware/nitro.config.ts":"53","/home/pooya/Code/nitro/examples/middleware/server/middleware/auth.ts":"54","/home/pooya/Code/nitro/examples/middleware/server.ts":"55","/home/pooya/Code/nitro/examples/middleware/vite.config.ts":"56","/home/pooya/Code/nitro/examples/mono-jsx/nitro.config.ts":"57","/home/pooya/Code/nitro/examples/mono-jsx/server.tsx":"58","/home/pooya/Code/nitro/examples/mono-jsx/vite.config.ts":"59","/home/pooya/Code/nitro/examples/nano-jsx/nitro.config.ts":"60","/home/pooya/Code/nitro/examples/nano-jsx/server.tsx":"61","/home/pooya/Code/nitro/examples/nano-jsx/vite.config.ts":"62","/home/pooya/Code/nitro/examples/plugins/nitro.config.ts":"63","/home/pooya/Code/nitro/examples/plugins/server/plugins/test.ts":"64","/home/pooya/Code/nitro/examples/plugins/server.ts":"65","/home/pooya/Code/nitro/examples/plugins/vite.config.ts":"66","/home/pooya/Code/nitro/examples/renderer/api/hello.ts":"67","/home/pooya/Code/nitro/examples/renderer/nitro.config.ts":"68","/home/pooya/Code/nitro/examples/renderer/renderer.ts":"69","/home/pooya/Code/nitro/examples/renderer/vite.config.ts":"70","/home/pooya/Code/nitro/examples/runtime-config/nitro.config.ts":"71","/home/pooya/Code/nitro/examples/runtime-config/server.ts":"72","/home/pooya/Code/nitro/examples/runtime-config/vite.config.ts":"73","/home/pooya/Code/nitro/examples/server-fetch/nitro.config.ts":"74","/home/pooya/Code/nitro/examples/server-fetch/routes/hello.ts":"75","/home/pooya/Code/nitro/examples/server-fetch/routes/index.ts":"76","/home/pooya/Code/nitro/examples/server-fetch/vite.config.ts":"77","/home/pooya/Code/nitro/examples/shiki/api/highlight.ts":"78","/home/pooya/Code/nitro/examples/shiki/nitro.config.ts":"79","/home/pooya/Code/nitro/examples/shiki/vite.config.ts":"80","/home/pooya/Code/nitro/examples/virtual-routes/nitro.config.ts":"81","/home/pooya/Code/nitro/examples/virtual-routes/vite.config.ts":"82","/home/pooya/Code/nitro/examples/vite-nitro-plugin/vite.config.mjs":"83","/home/pooya/Code/nitro/examples/vite-rsc/app/action.tsx":"84","/home/pooya/Code/nitro/examples/vite-rsc/app/client.tsx":"85","/home/pooya/Code/nitro/examples/vite-rsc/app/framework/entry.browser.tsx":"86","/home/pooya/Code/nitro/examples/vite-rsc/app/framework/entry.rsc.tsx":"87","/home/pooya/Code/nitro/examples/vite-rsc/app/framework/entry.ssr.tsx":"88","/home/pooya/Code/nitro/examples/vite-rsc/app/framework/error-boundary.tsx":"89","/home/pooya/Code/nitro/examples/vite-rsc/app/framework/request.tsx":"90","/home/pooya/Code/nitro/examples/vite-rsc/app/root.tsx":"91","/home/pooya/Code/nitro/examples/vite-rsc/vite.config.ts":"92","/home/pooya/Code/nitro/examples/vite-ssr-html/app/entry-server.ts":"93","/home/pooya/Code/nitro/examples/vite-ssr-html/routes/quote.ts":"94","/home/pooya/Code/nitro/examples/vite-ssr-html/vite.config.ts":"95","/home/pooya/Code/nitro/examples/vite-ssr-preact/src/app.tsx":"96","/home/pooya/Code/nitro/examples/vite-ssr-preact/src/entry-client.tsx":"97","/home/pooya/Code/nitro/examples/vite-ssr-preact/src/entry-server.tsx":"98","/home/pooya/Code/nitro/examples/vite-ssr-preact/vite.config.mjs":"99","/home/pooya/Code/nitro/examples/vite-ssr-react/src/app.tsx":"100","/home/pooya/Code/nitro/examples/vite-ssr-react/src/entry-client.tsx":"101","/home/pooya/Code/nitro/examples/vite-ssr-react/src/entry-server.tsx":"102","/home/pooya/Code/nitro/examples/vite-ssr-react/vite.config.mjs":"103","/home/pooya/Code/nitro/examples/vite-ssr-solid/src/app.tsx":"104","/home/pooya/Code/nitro/examples/vite-ssr-solid/src/entry-client.tsx":"105","/home/pooya/Code/nitro/examples/vite-ssr-solid/src/entry-server.tsx":"106","/home/pooya/Code/nitro/examples/vite-ssr-solid/vite.config.mjs":"107","/home/pooya/Code/nitro/examples/vite-ssr-tsr-react/src/main.tsx":"108","/home/pooya/Code/nitro/examples/vite-ssr-tsr-react/src/routes/__root.tsx":"109","/home/pooya/Code/nitro/examples/vite-ssr-tsr-react/src/routes/index.tsx":"110","/home/pooya/Code/nitro/examples/vite-ssr-tsr-react/vite.config.mjs":"111","/home/pooya/Code/nitro/examples/vite-ssr-tss-react/server.ts":"112","/home/pooya/Code/nitro/examples/vite-ssr-tss-react/src/router.tsx":"113","/home/pooya/Code/nitro/examples/vite-ssr-tss-react/src/routes/__root.tsx":"114","/home/pooya/Code/nitro/examples/vite-ssr-tss-react/src/routes/api/test.ts":"115","/home/pooya/Code/nitro/examples/vite-ssr-tss-react/src/routes/index.tsx":"116","/home/pooya/Code/nitro/examples/vite-ssr-tss-react/vite.config.mjs":"117","/home/pooya/Code/nitro/examples/vite-ssr-vue-router/app/entry-client.ts":"118","/home/pooya/Code/nitro/examples/vite-ssr-vue-router/app/entry-server.ts":"119","/home/pooya/Code/nitro/examples/vite-ssr-vue-router/app/routes.ts":"120","/home/pooya/Code/nitro/examples/vite-ssr-vue-router/app/shims.d.ts":"121","/home/pooya/Code/nitro/examples/vite-ssr-vue-router/vite.config.mjs":"122","/home/pooya/Code/nitro/examples/vite-trpc/server/trpc.ts":"123","/home/pooya/Code/nitro/examples/vite-trpc/vite.config.ts":"124","/home/pooya/Code/nitro/examples/websocket/nitro.config.ts":"125","/home/pooya/Code/nitro/examples/websocket/routes/_ws.ts":"126","/home/pooya/Code/nitro/examples/websocket/vite.config.ts":"127","/home/pooya/Code/nitro/lib/h3.d.mts":"128","/home/pooya/Code/nitro/lib/h3.mjs":"129","/home/pooya/Code/nitro/lib/vite.types.d.mts":"130","/home/pooya/Code/nitro/lib/vite.types.mjs":"131","/home/pooya/Code/nitro/playground/nitro.config.ts":"132","/home/pooya/Code/nitro/playground/server.ts":"133","/home/pooya/Code/nitro/playground/vite.config.ts":"134","/home/pooya/Code/nitro/scripts/bump-nightly.ts":"135","/home/pooya/Code/nitro/scripts/gen-node-compat.ts":"136","/home/pooya/Code/nitro/scripts/gen-presets.ts":"137","/home/pooya/Code/nitro/src/build/assets.ts":"138","/home/pooya/Code/nitro/src/build/build.ts":"139","/home/pooya/Code/nitro/src/build/chunks.ts":"140","/home/pooya/Code/nitro/src/build/config.ts":"141","/home/pooya/Code/nitro/src/build/info.ts":"142","/home/pooya/Code/nitro/src/build/plugins/externals.ts":"143","/home/pooya/Code/nitro/src/build/plugins/oxc.ts":"144","/home/pooya/Code/nitro/src/build/plugins/raw.ts":"145","/home/pooya/Code/nitro/src/build/plugins/route-meta.ts":"146","/home/pooya/Code/nitro/src/build/plugins/server-main.ts":"147","/home/pooya/Code/nitro/src/build/plugins/sourcemap-min.ts":"148","/home/pooya/Code/nitro/src/build/plugins/virtual.ts":"149","/home/pooya/Code/nitro/src/build/plugins.ts":"150","/home/pooya/Code/nitro/src/build/prepare.ts":"151","/home/pooya/Code/nitro/src/build/rolldown/build.ts":"152","/home/pooya/Code/nitro/src/build/rolldown/config.ts":"153","/home/pooya/Code/nitro/src/build/rolldown/dev.ts":"154","/home/pooya/Code/nitro/src/build/rolldown/prod.ts":"155","/home/pooya/Code/nitro/src/build/rollup/build.ts":"156","/home/pooya/Code/nitro/src/build/rollup/config.ts":"157","/home/pooya/Code/nitro/src/build/rollup/dev.ts":"158","/home/pooya/Code/nitro/src/build/rollup/error.ts":"159","/home/pooya/Code/nitro/src/build/rollup/prod.ts":"160","/home/pooya/Code/nitro/src/build/types.ts":"161","/home/pooya/Code/nitro/src/build/virtual/_all.ts":"162","/home/pooya/Code/nitro/src/build/virtual/database.ts":"163","/home/pooya/Code/nitro/src/build/virtual/error-handler.ts":"164","/home/pooya/Code/nitro/src/build/virtual/feature-flags.ts":"165","/home/pooya/Code/nitro/src/build/virtual/plugins.ts":"166","/home/pooya/Code/nitro/src/build/virtual/polyfills.ts":"167","/home/pooya/Code/nitro/src/build/virtual/public-assets.ts":"168","/home/pooya/Code/nitro/src/build/virtual/renderer-template.ts":"169","/home/pooya/Code/nitro/src/build/virtual/routing-meta.ts":"170","/home/pooya/Code/nitro/src/build/virtual/routing.ts":"171","/home/pooya/Code/nitro/src/build/virtual/runtime-config.ts":"172","/home/pooya/Code/nitro/src/build/virtual/server-assets.ts":"173","/home/pooya/Code/nitro/src/build/virtual/storage.ts":"174","/home/pooya/Code/nitro/src/build/virtual/tasks.ts":"175","/home/pooya/Code/nitro/src/build/vite/build.ts":"176","/home/pooya/Code/nitro/src/build/vite/bundler.ts":"177","/home/pooya/Code/nitro/src/build/vite/dev.ts":"178","/home/pooya/Code/nitro/src/build/vite/env.ts":"179","/home/pooya/Code/nitro/src/build/vite/plugin.ts":"180","/home/pooya/Code/nitro/src/build/vite/preview.ts":"181","/home/pooya/Code/nitro/src/build/vite/prod.ts":"182","/home/pooya/Code/nitro/src/build/vite/types.ts":"183","/home/pooya/Code/nitro/src/builder.ts":"184","/home/pooya/Code/nitro/src/cli/commands/build.ts":"185","/home/pooya/Code/nitro/src/cli/commands/dev.ts":"186","/home/pooya/Code/nitro/src/cli/commands/prepare.ts":"187","/home/pooya/Code/nitro/src/cli/commands/task/index.ts":"188","/home/pooya/Code/nitro/src/cli/commands/task/list.ts":"189","/home/pooya/Code/nitro/src/cli/commands/task/run.ts":"190","/home/pooya/Code/nitro/src/cli/common.ts":"191","/home/pooya/Code/nitro/src/cli/index.ts":"192","/home/pooya/Code/nitro/src/config/defaults.ts":"193","/home/pooya/Code/nitro/src/config/loader.ts":"194","/home/pooya/Code/nitro/src/config/resolvers/assets.ts":"195","/home/pooya/Code/nitro/src/config/resolvers/builder.ts":"196","/home/pooya/Code/nitro/src/config/resolvers/compatibility.ts":"197","/home/pooya/Code/nitro/src/config/resolvers/database.ts":"198","/home/pooya/Code/nitro/src/config/resolvers/error.ts":"199","/home/pooya/Code/nitro/src/config/resolvers/export-conditions.ts":"200","/home/pooya/Code/nitro/src/config/resolvers/imports.ts":"201","/home/pooya/Code/nitro/src/config/resolvers/open-api.ts":"202","/home/pooya/Code/nitro/src/config/resolvers/paths.ts":"203","/home/pooya/Code/nitro/src/config/resolvers/route-rules.ts":"204","/home/pooya/Code/nitro/src/config/resolvers/runtime-config.ts":"205","/home/pooya/Code/nitro/src/config/resolvers/storage.ts":"206","/home/pooya/Code/nitro/src/config/resolvers/tsconfig.ts":"207","/home/pooya/Code/nitro/src/config/resolvers/unenv.ts":"208","/home/pooya/Code/nitro/src/config/resolvers/url.ts":"209","/home/pooya/Code/nitro/src/config/update.ts":"210","/home/pooya/Code/nitro/src/dev/app.ts":"211","/home/pooya/Code/nitro/src/dev/server.ts":"212","/home/pooya/Code/nitro/src/dev/vfs.ts":"213","/home/pooya/Code/nitro/src/global.ts":"214","/home/pooya/Code/nitro/src/module.ts":"215","/home/pooya/Code/nitro/src/nitro.ts":"216","/home/pooya/Code/nitro/src/prerender/prerender.ts":"217","/home/pooya/Code/nitro/src/prerender/utils.ts":"218","/home/pooya/Code/nitro/src/presets/_nitro/base-worker.ts":"219","/home/pooya/Code/nitro/src/presets/_nitro/nitro-dev.ts":"220","/home/pooya/Code/nitro/src/presets/_nitro/nitro-prerender.ts":"221","/home/pooya/Code/nitro/src/presets/_nitro/preset.ts":"222","/home/pooya/Code/nitro/src/presets/_nitro/runtime/nitro-dev.ts":"223","/home/pooya/Code/nitro/src/presets/_nitro/runtime/nitro-prerenderer.ts":"224","/home/pooya/Code/nitro/src/presets/_nitro/runtime/service-worker.ts":"225","/home/pooya/Code/nitro/src/presets/_resolve.ts":"226","/home/pooya/Code/nitro/src/presets/_static/preset.ts":"227","/home/pooya/Code/nitro/src/presets/_utils/fs.ts":"228","/home/pooya/Code/nitro/src/presets/_utils/preset.ts":"229","/home/pooya/Code/nitro/src/presets/alwaysdata/preset.ts":"230","/home/pooya/Code/nitro/src/presets/aws-amplify/preset.ts":"231","/home/pooya/Code/nitro/src/presets/aws-amplify/runtime/aws-amplify.ts":"232","/home/pooya/Code/nitro/src/presets/aws-amplify/types.ts":"233","/home/pooya/Code/nitro/src/presets/aws-amplify/utils.ts":"234","/home/pooya/Code/nitro/src/presets/aws-lambda/preset.ts":"235","/home/pooya/Code/nitro/src/presets/aws-lambda/runtime/_utils.ts":"236","/home/pooya/Code/nitro/src/presets/aws-lambda/runtime/aws-lambda-streaming.ts":"237","/home/pooya/Code/nitro/src/presets/aws-lambda/runtime/aws-lambda.ts":"238","/home/pooya/Code/nitro/src/presets/aws-lambda/types.ts":"239","/home/pooya/Code/nitro/src/presets/azure/preset.ts":"240","/home/pooya/Code/nitro/src/presets/azure/runtime/_utils.ts":"241","/home/pooya/Code/nitro/src/presets/azure/runtime/azure-swa.ts":"242","/home/pooya/Code/nitro/src/presets/azure/types.ts":"243","/home/pooya/Code/nitro/src/presets/azure/utils.ts":"244","/home/pooya/Code/nitro/src/presets/bun/preset.ts":"245","/home/pooya/Code/nitro/src/presets/bun/runtime/bun.ts":"246","/home/pooya/Code/nitro/src/presets/cleavr/preset.ts":"247","/home/pooya/Code/nitro/src/presets/cloudflare/dev.ts":"248","/home/pooya/Code/nitro/src/presets/cloudflare/entry-exports.ts":"249","/home/pooya/Code/nitro/src/presets/cloudflare/preset.ts":"250","/home/pooya/Code/nitro/src/presets/cloudflare/runtime/_module-handler.ts":"251","/home/pooya/Code/nitro/src/presets/cloudflare/runtime/cloudflare-durable.ts":"252","/home/pooya/Code/nitro/src/presets/cloudflare/runtime/cloudflare-module.ts":"253","/home/pooya/Code/nitro/src/presets/cloudflare/runtime/cloudflare-pages.ts":"254","/home/pooya/Code/nitro/src/presets/cloudflare/runtime/plugin.dev.ts":"255","/home/pooya/Code/nitro/src/presets/cloudflare/runtime/shims/workers.dev.mjs":"256","/home/pooya/Code/nitro/src/presets/cloudflare/types.ts":"257","/home/pooya/Code/nitro/src/presets/cloudflare/unenv/node-compat.ts":"258","/home/pooya/Code/nitro/src/presets/cloudflare/unenv/preset.ts":"259","/home/pooya/Code/nitro/src/presets/cloudflare/utils.ts":"260","/home/pooya/Code/nitro/src/presets/cloudflare/wrangler/_utils.ts":"261","/home/pooya/Code/nitro/src/presets/cloudflare/wrangler/config.ts":"262","/home/pooya/Code/nitro/src/presets/cloudflare/wrangler/environment.ts":"263","/home/pooya/Code/nitro/src/presets/deno/preset.ts":"264","/home/pooya/Code/nitro/src/presets/deno/runtime/deno-deploy.ts":"265","/home/pooya/Code/nitro/src/presets/deno/runtime/deno-server.ts":"266","/home/pooya/Code/nitro/src/presets/deno/unenv/node-compat.ts":"267","/home/pooya/Code/nitro/src/presets/deno/unenv/preset.ts":"268","/home/pooya/Code/nitro/src/presets/digitalocean/preset.ts":"269","/home/pooya/Code/nitro/src/presets/firebase/preset.ts":"270","/home/pooya/Code/nitro/src/presets/firebase/types.ts":"271","/home/pooya/Code/nitro/src/presets/flightcontrol/preset.ts":"272","/home/pooya/Code/nitro/src/presets/genezio/preset.ts":"273","/home/pooya/Code/nitro/src/presets/heroku/preset.ts":"274","/home/pooya/Code/nitro/src/presets/iis/preset.ts":"275","/home/pooya/Code/nitro/src/presets/iis/utils.ts":"276","/home/pooya/Code/nitro/src/presets/index.ts":"277","/home/pooya/Code/nitro/src/presets/koyeb/preset.ts":"278","/home/pooya/Code/nitro/src/presets/netlify/preset.ts":"279","/home/pooya/Code/nitro/src/presets/netlify/runtime/netlify-edge.ts":"280","/home/pooya/Code/nitro/src/presets/netlify/runtime/netlify.ts":"281","/home/pooya/Code/nitro/src/presets/netlify/types.ts":"282","/home/pooya/Code/nitro/src/presets/netlify/utils.ts":"283","/home/pooya/Code/nitro/src/presets/node/cluster.ts":"284","/home/pooya/Code/nitro/src/presets/node/preset.ts":"285","/home/pooya/Code/nitro/src/presets/node/runtime/node-cluster.ts":"286","/home/pooya/Code/nitro/src/presets/node/runtime/node-middleware.ts":"287","/home/pooya/Code/nitro/src/presets/node/runtime/node-server.ts":"288","/home/pooya/Code/nitro/src/presets/platform.sh/preset.ts":"289","/home/pooya/Code/nitro/src/presets/render.com/preset.ts":"290","/home/pooya/Code/nitro/src/presets/standard/preset.ts":"291","/home/pooya/Code/nitro/src/presets/standard/runtime/server.ts":"292","/home/pooya/Code/nitro/src/presets/stormkit/preset.ts":"293","/home/pooya/Code/nitro/src/presets/stormkit/runtime/stormkit.ts":"294","/home/pooya/Code/nitro/src/presets/vercel/preset.ts":"295","/home/pooya/Code/nitro/src/presets/vercel/runtime/isr.ts":"296","/home/pooya/Code/nitro/src/presets/vercel/runtime/vercel.node.ts":"297","/home/pooya/Code/nitro/src/presets/vercel/runtime/vercel.web.ts":"298","/home/pooya/Code/nitro/src/presets/vercel/types.ts":"299","/home/pooya/Code/nitro/src/presets/vercel/utils.ts":"300","/home/pooya/Code/nitro/src/presets/winterjs/preset.ts":"301","/home/pooya/Code/nitro/src/presets/winterjs/runtime/winterjs.ts":"302","/home/pooya/Code/nitro/src/presets/zeabur/preset.ts":"303","/home/pooya/Code/nitro/src/presets/zeabur/runtime/zeabur.ts":"304","/home/pooya/Code/nitro/src/presets/zerops/preset.ts":"305","/home/pooya/Code/nitro/src/routing.ts":"306","/home/pooya/Code/nitro/src/runner/node.ts":"307","/home/pooya/Code/nitro/src/runner/proxy.ts":"308","/home/pooya/Code/nitro/src/runtime/app.ts":"309","/home/pooya/Code/nitro/src/runtime/cache.ts":"310","/home/pooya/Code/nitro/src/runtime/config.ts":"311","/home/pooya/Code/nitro/src/runtime/context.ts":"312","/home/pooya/Code/nitro/src/runtime/database.ts":"313","/home/pooya/Code/nitro/src/runtime/internal/app.ts":"314","/home/pooya/Code/nitro/src/runtime/internal/cache.ts":"315","/home/pooya/Code/nitro/src/runtime/internal/context.ts":"316","/home/pooya/Code/nitro/src/runtime/internal/database.ts":"317","/home/pooya/Code/nitro/src/runtime/internal/empty.ts":"318","/home/pooya/Code/nitro/src/runtime/internal/error/dev.ts":"319","/home/pooya/Code/nitro/src/runtime/internal/error/hooks.ts":"320","/home/pooya/Code/nitro/src/runtime/internal/error/prod.ts":"321","/home/pooya/Code/nitro/src/runtime/internal/error/utils.ts":"322","/home/pooya/Code/nitro/src/runtime/internal/meta.ts":"323","/home/pooya/Code/nitro/src/runtime/internal/plugin.ts":"324","/home/pooya/Code/nitro/src/runtime/internal/route-rules.ts":"325","/home/pooya/Code/nitro/src/runtime/internal/routes/dev-tasks.ts":"326","/home/pooya/Code/nitro/src/runtime/internal/routes/openapi.ts":"327","/home/pooya/Code/nitro/src/runtime/internal/routes/renderer-template.dev.ts":"328","/home/pooya/Code/nitro/src/runtime/internal/routes/renderer-template.ts":"329","/home/pooya/Code/nitro/src/runtime/internal/routes/scalar.ts":"330","/home/pooya/Code/nitro/src/runtime/internal/routes/swagger.ts":"331","/home/pooya/Code/nitro/src/runtime/internal/runtime-config.ts":"332","/home/pooya/Code/nitro/src/runtime/internal/static.ts":"333","/home/pooya/Code/nitro/src/runtime/internal/storage.ts":"334","/home/pooya/Code/nitro/src/runtime/internal/task.ts":"335","/home/pooya/Code/nitro/src/runtime/internal/vite/dev-entry.mjs":"336","/home/pooya/Code/nitro/src/runtime/internal/vite/node-runner.mjs":"337","/home/pooya/Code/nitro/src/runtime/internal/vite/ssr-renderer.mjs":"338","/home/pooya/Code/nitro/src/runtime/meta.ts":"339","/home/pooya/Code/nitro/src/runtime/nitro.ts":"340","/home/pooya/Code/nitro/src/runtime/runtime-config.ts":"341","/home/pooya/Code/nitro/src/runtime/storage.ts":"342","/home/pooya/Code/nitro/src/runtime/task.ts":"343","/home/pooya/Code/nitro/src/runtime/virtual/_runtime_warn.ts":"344","/home/pooya/Code/nitro/src/runtime/virtual/database.ts":"345","/home/pooya/Code/nitro/src/runtime/virtual/error-handler.ts":"346","/home/pooya/Code/nitro/src/runtime/virtual/feature-flags.ts":"347","/home/pooya/Code/nitro/src/runtime/virtual/plugins.ts":"348","/home/pooya/Code/nitro/src/runtime/virtual/polyfills.ts":"349","/home/pooya/Code/nitro/src/runtime/virtual/public-assets.ts":"350","/home/pooya/Code/nitro/src/runtime/virtual/renderer-template.ts":"351","/home/pooya/Code/nitro/src/runtime/virtual/routing-meta.ts":"352","/home/pooya/Code/nitro/src/runtime/virtual/routing.ts":"353","/home/pooya/Code/nitro/src/runtime/virtual/runtime-config.ts":"354","/home/pooya/Code/nitro/src/runtime/virtual/server-assets.ts":"355","/home/pooya/Code/nitro/src/runtime/virtual/storage.ts":"356","/home/pooya/Code/nitro/src/runtime/virtual/tasks.ts":"357","/home/pooya/Code/nitro/src/runtime/vite.ts":"358","/home/pooya/Code/nitro/src/scan.ts":"359","/home/pooya/Code/nitro/src/task.ts":"360","/home/pooya/Code/nitro/src/types/_utils.ts":"361","/home/pooya/Code/nitro/src/types/build.ts":"362","/home/pooya/Code/nitro/src/types/config.ts":"363","/home/pooya/Code/nitro/src/types/fetch/_match.ts":"364","/home/pooya/Code/nitro/src/types/fetch/_serialize.ts":"365","/home/pooya/Code/nitro/src/types/fetch/fetch.ts":"366","/home/pooya/Code/nitro/src/types/fetch/index.ts":"367","/home/pooya/Code/nitro/src/types/global.ts":"368","/home/pooya/Code/nitro/src/types/h3.ts":"369","/home/pooya/Code/nitro/src/types/handler.ts":"370","/home/pooya/Code/nitro/src/types/hooks.ts":"371","/home/pooya/Code/nitro/src/types/index.ts":"372","/home/pooya/Code/nitro/src/types/module.ts":"373","/home/pooya/Code/nitro/src/types/nitro.ts":"374","/home/pooya/Code/nitro/src/types/openapi-ts.ts":"375","/home/pooya/Code/nitro/src/types/openapi.ts":"376","/home/pooya/Code/nitro/src/types/prerender.ts":"377","/home/pooya/Code/nitro/src/types/preset.ts":"378","/home/pooya/Code/nitro/src/types/route-rules.ts":"379","/home/pooya/Code/nitro/src/types/runner.ts":"380","/home/pooya/Code/nitro/src/types/runtime/asset.ts":"381","/home/pooya/Code/nitro/src/types/runtime/cache.ts":"382","/home/pooya/Code/nitro/src/types/runtime/index.ts":"383","/home/pooya/Code/nitro/src/types/runtime/nitro.ts":"384","/home/pooya/Code/nitro/src/types/runtime/task.ts":"385","/home/pooya/Code/nitro/src/types/srvx.ts":"386","/home/pooya/Code/nitro/src/utils/compress.ts":"387","/home/pooya/Code/nitro/src/utils/dep.ts":"388","/home/pooya/Code/nitro/src/utils/fs-tree.ts":"389","/home/pooya/Code/nitro/src/utils/fs.ts":"390","/home/pooya/Code/nitro/src/utils/parallel.ts":"391","/home/pooya/Code/nitro/src/utils/regex.ts":"392","/home/pooya/Code/nitro/src/vite.ts":"393","/home/pooya/Code/nitro/test/examples.test.ts":"394","/home/pooya/Code/nitro/test/fixture/error.ts":"395","/home/pooya/Code/nitro/test/fixture/exports.cloudflare.ts":"396","/home/pooya/Code/nitro/test/fixture/nitro.config.ts":"397","/home/pooya/Code/nitro/test/fixture/public/foo.js":"398","/home/pooya/Code/nitro/test/fixture/server/files/sqlts.sql.ts":"399","/home/pooya/Code/nitro/test/fixture/server/middleware/_ignored.ts":"400","/home/pooya/Code/nitro/test/fixture/server/plugins/errors.ts":"401","/home/pooya/Code/nitro/test/fixture/server/plugins/vary.ts":"402","/home/pooya/Code/nitro/test/fixture/server/routes/(route-group)/route-group.ts":"403","/home/pooya/Code/nitro/test/fixture/server/routes/500.ts":"404","/home/pooya/Code/nitro/test/fixture/server/routes/api/_ignored.ts":"405","/home/pooya/Code/nitro/test/fixture/server/routes/api/cached.ts":"406","/home/pooya/Code/nitro/test/fixture/server/routes/api/db.ts":"407","/home/pooya/Code/nitro/test/fixture/server/routes/api/echo.ts":"408","/home/pooya/Code/nitro/test/fixture/server/routes/api/error.ts":"409","/home/pooya/Code/nitro/test/fixture/server/routes/api/errors.ts":"410","/home/pooya/Code/nitro/test/fixture/server/routes/api/headers.ts":"411","/home/pooya/Code/nitro/test/fixture/server/routes/api/hello.ts":"412","/home/pooya/Code/nitro/test/fixture/server/routes/api/hey/index.get.ts":"413","/home/pooya/Code/nitro/test/fixture/server/routes/api/kebab.ts":"414","/home/pooya/Code/nitro/test/fixture/server/routes/api/meta/test.ts":"415","/home/pooya/Code/nitro/test/fixture/server/routes/api/methods/foo.get.get.ts":"416","/home/pooya/Code/nitro/test/fixture/server/routes/api/methods/get.ts":"417","/home/pooya/Code/nitro/test/fixture/server/routes/api/param/[test-id].ts":"418","/home/pooya/Code/nitro/test/fixture/server/routes/api/storage/item.get.ts":"419","/home/pooya/Code/nitro/test/fixture/server/routes/api/storage/item.put.ts":"420","/home/pooya/Code/nitro/test/fixture/server/routes/api/upload.post.ts":"421","/home/pooya/Code/nitro/test/fixture/server/routes/api/wildcard/[...param].ts":"422","/home/pooya/Code/nitro/test/fixture/server/routes/assets/[id].ts":"423","/home/pooya/Code/nitro/test/fixture/server/routes/assets/all.ts":"424","/home/pooya/Code/nitro/test/fixture/server/routes/assets/md.ts":"425","/home/pooya/Code/nitro/test/fixture/server/routes/config.ts":"426","/home/pooya/Code/nitro/test/fixture/server/routes/context.ts":"427","/home/pooya/Code/nitro/test/fixture/server/routes/env/index.dev.ts":"428","/home/pooya/Code/nitro/test/fixture/server/routes/env/index.get.prod.ts":"429","/home/pooya/Code/nitro/test/fixture/server/routes/error-stack.ts":"430","/home/pooya/Code/nitro/test/fixture/server/routes/fetch.ts":"431","/home/pooya/Code/nitro/test/fixture/server/routes/file.ts":"432","/home/pooya/Code/nitro/test/fixture/server/routes/icon.png.ts":"433","/home/pooya/Code/nitro/test/fixture/server/routes/imports.ts":"434","/home/pooya/Code/nitro/test/fixture/server/routes/json-string.ts":"435","/home/pooya/Code/nitro/test/fixture/server/routes/jsx.tsx":"436","/home/pooya/Code/nitro/test/fixture/server/routes/modules.ts":"437","/home/pooya/Code/nitro/test/fixture/server/routes/node-compat.ts":"438","/home/pooya/Code/nitro/test/fixture/server/routes/prerender-custom.html.ts":"439","/home/pooya/Code/nitro/test/fixture/server/routes/prerender.ts":"440","/home/pooya/Code/nitro/test/fixture/server/routes/raw.ts":"441","/home/pooya/Code/nitro/test/fixture/server/routes/replace.ts":"442","/home/pooya/Code/nitro/test/fixture/server/routes/rules/[...slug].ts":"443","/home/pooya/Code/nitro/test/fixture/server/routes/static-flags.ts":"444","/home/pooya/Code/nitro/test/fixture/server/routes/stream.ts":"445","/home/pooya/Code/nitro/test/fixture/server/routes/tasks/[...name].ts":"446","/home/pooya/Code/nitro/test/fixture/server/routes/wait-until.ts":"447","/home/pooya/Code/nitro/test/fixture/server/routes/wasm/dynamic-import.ts":"448","/home/pooya/Code/nitro/test/fixture/server/routes/wasm/static-import.ts":"449","/home/pooya/Code/nitro/test/fixture/server/tasks/db/migrate.ts":"450","/home/pooya/Code/nitro/test/fixture/server/tasks/test.ts":"451","/home/pooya/Code/nitro/test/fixture/server/utils/foo/bar/test.ts":"452","/home/pooya/Code/nitro/test/fixture/server/utils/foo/test.ts":"453","/home/pooya/Code/nitro/test/fixture/server/utils/test.ts":"454","/home/pooya/Code/nitro/test/fixture/server.config.ts":"455","/home/pooya/Code/nitro/test/fixture/server.ts":"456","/home/pooya/Code/nitro/test/fixture/vite.config.ts":"457","/home/pooya/Code/nitro/test/minimal/minimal.test.ts":"458","/home/pooya/Code/nitro/test/minimal/nitro.config.ts":"459","/home/pooya/Code/nitro/test/minimal/server.ts":"460","/home/pooya/Code/nitro/test/minimal/vite.config.mjs":"461","/home/pooya/Code/nitro/test/presets/aws-lambda.test.ts":"462","/home/pooya/Code/nitro/test/presets/azure-swa.test.ts":"463","/home/pooya/Code/nitro/test/presets/bun.test.ts":"464","/home/pooya/Code/nitro/test/presets/cloudflare-module.test.ts":"465","/home/pooya/Code/nitro/test/presets/cloudflare-pages.test.ts":"466","/home/pooya/Code/nitro/test/presets/deno-server.test.ts":"467","/home/pooya/Code/nitro/test/presets/netlify.test.ts":"468","/home/pooya/Code/nitro/test/presets/nitro-dev.test.ts":"469","/home/pooya/Code/nitro/test/presets/node.test.ts":"470","/home/pooya/Code/nitro/test/presets/standard.test.ts":"471","/home/pooya/Code/nitro/test/presets/static.test.ts":"472","/home/pooya/Code/nitro/test/presets/vercel.test.ts":"473","/home/pooya/Code/nitro/test/presets/winterjs.test.ts":"474","/home/pooya/Code/nitro/test/scripts/gen-fixture-types.ts":"475","/home/pooya/Code/nitro/test/tests.ts":"476","/home/pooya/Code/nitro/test/unit/azure.utils.test.ts":"477","/home/pooya/Code/nitro/test/unit/runtime-config.env.test.ts":"478","/home/pooya/Code/nitro/test/unit/runtime-config.test.ts":"479","/home/pooya/Code/nitro/test/unit/virtual.test.ts":"480","/home/pooya/Code/nitro/vitest.config.ts":"481"},{"size":6278,"mtime":1768935766630,"results":"482","hashOfConfig":"483"},{"size":11098,"mtime":1769020993118,"results":"484","hashOfConfig":"483"},{"size":11,"mtime":1768935766630,"results":"485","hashOfConfig":"483"},{"size":5220,"mtime":1768485628956,"results":"486","hashOfConfig":"483"},{"size":2878,"mtime":1768485628956,"results":"487","hashOfConfig":"483"},{"size":658,"mtime":1769019507194,"results":"488","hashOfConfig":"483"},{"size":622,"mtime":1768485628956,"results":"489","hashOfConfig":"483"},{"size":726,"mtime":1768485628956,"results":"490","hashOfConfig":"491"},{"size":5625,"mtime":1768935766630,"results":"492","hashOfConfig":"491"},{"size":200,"mtime":1769103053294,"results":"493","hashOfConfig":"491"},{"size":47,"mtime":1765885654431,"results":"494","hashOfConfig":"491"},{"size":585,"mtime":1768935766633,"results":"495","hashOfConfig":"496"},{"size":137,"mtime":1768485628958,"results":"497","hashOfConfig":"491"},{"size":100,"mtime":1768485628958,"results":"498","hashOfConfig":"491"},{"size":99,"mtime":1768485628958,"results":"499","hashOfConfig":"491"},{"size":188,"mtime":1768485628958,"results":"500","hashOfConfig":"491"},{"size":92,"mtime":1768485628958,"results":"501","hashOfConfig":"491"},{"size":127,"mtime":1768485628958,"results":"502","hashOfConfig":"491"},{"size":107,"mtime":1768485628958,"results":"503","hashOfConfig":"491"},{"size":75,"mtime":1768485628959,"results":"504","hashOfConfig":"491"},{"size":172,"mtime":1768485628958,"results":"505","hashOfConfig":"491"},{"size":127,"mtime":1768485628959,"results":"506","hashOfConfig":"491"},{"size":72,"mtime":1768485628959,"results":"507","hashOfConfig":"491"},{"size":428,"mtime":1768485628959,"results":"508","hashOfConfig":"491"},{"size":127,"mtime":1768485628959,"results":"509","hashOfConfig":"491"},{"size":240,"mtime":1768485628959,"results":"510","hashOfConfig":"491"},{"size":178,"mtime":1768485628959,"results":"511","hashOfConfig":"491"},{"size":153,"mtime":1768485628959,"results":"512","hashOfConfig":"491"},{"size":127,"mtime":1768485628959,"results":"513","hashOfConfig":"491"},{"size":190,"mtime":1768485628959,"results":"514","hashOfConfig":"491"},{"size":635,"mtime":1768485628959,"results":"515","hashOfConfig":"491"},{"size":548,"mtime":1768485628959,"results":"516","hashOfConfig":"491"},{"size":127,"mtime":1768485628959,"results":"517","hashOfConfig":"491"},{"size":72,"mtime":1768485628959,"results":"518","hashOfConfig":"491"},{"size":141,"mtime":1768485628959,"results":"519","hashOfConfig":"491"},{"size":127,"mtime":1768485628959,"results":"520","hashOfConfig":"491"},{"size":72,"mtime":1768485628959,"results":"521","hashOfConfig":"491"},{"size":157,"mtime":1768870633712,"results":"522","hashOfConfig":"491"},{"size":127,"mtime":1768485628959,"results":"523","hashOfConfig":"491"},{"size":72,"mtime":1768485628959,"results":"524","hashOfConfig":"491"},{"size":155,"mtime":1768485628959,"results":"525","hashOfConfig":"491"},{"size":127,"mtime":1768485628959,"results":"526","hashOfConfig":"491"},{"size":72,"mtime":1768485628959,"results":"527","hashOfConfig":"491"},{"size":90,"mtime":1768485628959,"results":"528","hashOfConfig":"491"},{"size":127,"mtime":1768485628959,"results":"529","hashOfConfig":"491"},{"size":72,"mtime":1768485628959,"results":"530","hashOfConfig":"491"},{"size":146,"mtime":1768485628959,"results":"531","hashOfConfig":"491"},{"size":127,"mtime":1768485628959,"results":"532","hashOfConfig":"491"},{"size":140,"mtime":1768485628959,"results":"533","hashOfConfig":"491"},{"size":239,"mtime":1768485628960,"results":"534","hashOfConfig":"491"},{"size":190,"mtime":1768485628960,"results":"535","hashOfConfig":"491"},{"size":127,"mtime":1768485628960,"results":"536","hashOfConfig":"491"},{"size":92,"mtime":1768485628960,"results":"537","hashOfConfig":"491"},{"size":171,"mtime":1768485628960,"results":"538","hashOfConfig":"491"},{"size":119,"mtime":1768485628960,"results":"539","hashOfConfig":"491"},{"size":127,"mtime":1768485628960,"results":"540","hashOfConfig":"491"},{"size":72,"mtime":1768485628960,"results":"541","hashOfConfig":"491"},{"size":83,"mtime":1768485628960,"results":"542","hashOfConfig":"491"},{"size":127,"mtime":1768485628960,"results":"543","hashOfConfig":"491"},{"size":72,"mtime":1768485628960,"results":"544","hashOfConfig":"491"},{"size":188,"mtime":1768485628960,"results":"545","hashOfConfig":"491"},{"size":127,"mtime":1768485628960,"results":"546","hashOfConfig":"491"},{"size":92,"mtime":1768485628960,"results":"547","hashOfConfig":"491"},{"size":269,"mtime":1768485628960,"results":"548","hashOfConfig":"491"},{"size":96,"mtime":1768485628960,"results":"549","hashOfConfig":"491"},{"size":127,"mtime":1768485628960,"results":"550","hashOfConfig":"491"},{"size":100,"mtime":1768485628960,"results":"551","hashOfConfig":"491"},{"size":131,"mtime":1768485628960,"results":"552","hashOfConfig":"491"},{"size":533,"mtime":1768485628960,"results":"553","hashOfConfig":"491"},{"size":127,"mtime":1768485628960,"results":"554","hashOfConfig":"491"},{"size":132,"mtime":1768485628960,"results":"555","hashOfConfig":"491"},{"size":218,"mtime":1768485628960,"results":"556","hashOfConfig":"491"},{"size":127,"mtime":1768485628960,"results":"557","hashOfConfig":"491"},{"size":315,"mtime":1768485628960,"results":"558","hashOfConfig":"491"},{"size":89,"mtime":1768485628960,"results":"559","hashOfConfig":"491"},{"size":127,"mtime":1768485628961,"results":"560","hashOfConfig":"491"},{"size":127,"mtime":1768485628961,"results":"561","hashOfConfig":"491"},{"size":621,"mtime":1768485628961,"results":"562","hashOfConfig":"491"},{"size":92,"mtime":1768485628961,"results":"563","hashOfConfig":"491"},{"size":130,"mtime":1768485628961,"results":"564","hashOfConfig":"491"},{"size":244,"mtime":1768485628961,"results":"565","hashOfConfig":"491"},{"size":127,"mtime":1768485628961,"results":"566","hashOfConfig":"491"},{"size":440,"mtime":1768485628961,"results":"567","hashOfConfig":"496"},{"size":198,"mtime":1768935766633,"results":"568","hashOfConfig":"491"},{"size":247,"mtime":1768935766633,"results":"569","hashOfConfig":"491"},{"size":4359,"mtime":1768935766633,"results":"570","hashOfConfig":"491"},{"size":4641,"mtime":1768935766634,"results":"571","hashOfConfig":"491"},{"size":2850,"mtime":1768935766634,"results":"572","hashOfConfig":"491"},{"size":2258,"mtime":1768935766634,"results":"573","hashOfConfig":"491"},{"size":1889,"mtime":1768935766634,"results":"574","hashOfConfig":"491"},{"size":2353,"mtime":1768935766634,"results":"575","hashOfConfig":"491"},{"size":567,"mtime":1768935766634,"results":"576","hashOfConfig":"491"},{"size":748,"mtime":1768485628961,"results":"577","hashOfConfig":"491"},{"size":570,"mtime":1768485628961,"results":"578","hashOfConfig":"491"},{"size":234,"mtime":1768485628961,"results":"579","hashOfConfig":"491"},{"size":202,"mtime":1768485628961,"results":"580","hashOfConfig":"491"},{"size":150,"mtime":1768485628962,"results":"581","hashOfConfig":"491"},{"size":1223,"mtime":1768485628962,"results":"582","hashOfConfig":"491"},{"size":329,"mtime":1768485628962,"results":"583","hashOfConfig":"496"},{"size":265,"mtime":1768485628962,"results":"584","hashOfConfig":"491"},{"size":177,"mtime":1768485628962,"results":"585","hashOfConfig":"491"},{"size":1062,"mtime":1768485628962,"results":"586","hashOfConfig":"491"},{"size":294,"mtime":1768485628962,"results":"587","hashOfConfig":"496"},{"size":283,"mtime":1768485628962,"results":"588","hashOfConfig":"491"},{"size":154,"mtime":1768485628962,"results":"589","hashOfConfig":"491"},{"size":1192,"mtime":1768485628962,"results":"590","hashOfConfig":"491"},{"size":452,"mtime":1768485628962,"results":"591","hashOfConfig":"496"},{"size":706,"mtime":1768485628962,"results":"592","hashOfConfig":"491"},{"size":439,"mtime":1768485628962,"results":"593","hashOfConfig":"491"},{"size":364,"mtime":1768485628962,"results":"594","hashOfConfig":"491"},{"size":323,"mtime":1768485628962,"results":"595","hashOfConfig":"496"},{"size":180,"mtime":1768485628962,"results":"596","hashOfConfig":"491"},{"size":390,"mtime":1768485628962,"results":"597","hashOfConfig":"491"},{"size":1475,"mtime":1768485628962,"results":"598","hashOfConfig":"491"},{"size":511,"mtime":1768485628962,"results":"599","hashOfConfig":"491"},{"size":266,"mtime":1768485628962,"results":"600","hashOfConfig":"491"},{"size":553,"mtime":1768485628962,"results":"601","hashOfConfig":"496"},{"size":422,"mtime":1768485628962,"results":"602","hashOfConfig":"491"},{"size":1784,"mtime":1768485628962,"results":"603","hashOfConfig":"491"},{"size":898,"mtime":1768485628962,"results":"604","hashOfConfig":"491"},{"size":150,"mtime":1768485628962,"results":"605","hashOfConfig":"491"},{"size":773,"mtime":1768485628962,"results":"606","hashOfConfig":"496"},{"size":589,"mtime":1768935766634,"results":"607","hashOfConfig":"491"},{"size":211,"mtime":1768935766634,"results":"608","hashOfConfig":"491"},{"size":155,"mtime":1768485628962,"results":"609","hashOfConfig":"491"},{"size":683,"mtime":1768485628962,"results":"610","hashOfConfig":"491"},{"size":127,"mtime":1768485628962,"results":"611","hashOfConfig":"491"},{"size":20,"mtime":1768485628962,"results":"612","hashOfConfig":"491"},{"size":20,"mtime":1768485628963,"results":"613","hashOfConfig":"496"},{"size":630,"mtime":1768935766634,"results":"614","hashOfConfig":"491"},{"size":73,"mtime":1768935766634,"results":"615","hashOfConfig":"496"},{"size":98,"mtime":1769017480816,"results":"616","hashOfConfig":"491"},{"size":106,"mtime":1769017481745,"results":"617","hashOfConfig":"491"},{"size":130,"mtime":1768485628963,"results":"618","hashOfConfig":"491"},{"size":3765,"mtime":1768485628963,"results":"619","hashOfConfig":"491"},{"size":554,"mtime":1768485628963,"results":"620","hashOfConfig":"491"},{"size":3432,"mtime":1768485628963,"results":"621","hashOfConfig":"491"},{"size":2654,"mtime":1768485628963,"results":"622","hashOfConfig":"491"},{"size":592,"mtime":1768485628964,"results":"623","hashOfConfig":"491"},{"size":3107,"mtime":1769018727869,"results":"624","hashOfConfig":"491"},{"size":3313,"mtime":1769020222179,"results":"625","hashOfConfig":"491"},{"size":2914,"mtime":1768485628964,"results":"626","hashOfConfig":"491"},{"size":7864,"mtime":1768935766636,"results":"627","hashOfConfig":"491"},{"size":1007,"mtime":1768485628964,"results":"628","hashOfConfig":"491"},{"size":2685,"mtime":1769112665825,"results":"629","hashOfConfig":"491"},{"size":3323,"mtime":1768935766636,"results":"630","hashOfConfig":"491"},{"size":375,"mtime":1768485628964,"results":"631","hashOfConfig":"491"},{"size":939,"mtime":1768485628964,"results":"632","hashOfConfig":"491"},{"size":2580,"mtime":1768485628964,"results":"633","hashOfConfig":"491"},{"size":2833,"mtime":1769111694906,"results":"634","hashOfConfig":"491"},{"size":500,"mtime":1768485628964,"results":"635","hashOfConfig":"491"},{"size":494,"mtime":1768935766636,"results":"636","hashOfConfig":"491"},{"size":2730,"mtime":1769018634698,"results":"637","hashOfConfig":"491"},{"size":2885,"mtime":1768485628964,"results":"638","hashOfConfig":"491"},{"size":2044,"mtime":1768485628964,"results":"639","hashOfConfig":"491"},{"size":481,"mtime":1768935766636,"results":"640","hashOfConfig":"491"},{"size":3117,"mtime":1768935766636,"results":"641","hashOfConfig":"491"},{"size":3140,"mtime":1768485628964,"results":"642","hashOfConfig":"491"},{"size":865,"mtime":1768485628964,"results":"643","hashOfConfig":"491"},{"size":2133,"mtime":1768485628964,"results":"644","hashOfConfig":"491"},{"size":9200,"mtime":1768868651078,"results":"645","hashOfConfig":"491"},{"size":1242,"mtime":1768485628964,"results":"646","hashOfConfig":"491"},{"size":1322,"mtime":1768485628964,"results":"647","hashOfConfig":"491"},{"size":1146,"mtime":1768485628964,"results":"648","hashOfConfig":"491"},{"size":885,"mtime":1769020222179,"results":"649","hashOfConfig":"491"},{"size":565,"mtime":1768485628964,"results":"650","hashOfConfig":"491"},{"size":359,"mtime":1768485628964,"results":"651","hashOfConfig":"491"},{"size":5808,"mtime":1768935766636,"results":"652","hashOfConfig":"491"},{"size":2048,"mtime":1768485628964,"results":"653","hashOfConfig":"491"},{"size":879,"mtime":1768485628964,"results":"654","hashOfConfig":"491"},{"size":3501,"mtime":1768485628964,"results":"655","hashOfConfig":"491"},{"size":309,"mtime":1768485628964,"results":"656","hashOfConfig":"491"},{"size":2793,"mtime":1768485628964,"results":"657","hashOfConfig":"491"},{"size":1492,"mtime":1768485628964,"results":"658","hashOfConfig":"491"},{"size":1436,"mtime":1768485628964,"results":"659","hashOfConfig":"491"},{"size":613,"mtime":1768935766636,"results":"660","hashOfConfig":"491"},{"size":3531,"mtime":1769111910945,"results":"661","hashOfConfig":"491"},{"size":7854,"mtime":1769020222179,"results":"662","hashOfConfig":"491"},{"size":3580,"mtime":1769018634698,"results":"663","hashOfConfig":"491"},{"size":14692,"mtime":1769018634698,"results":"664","hashOfConfig":"491"},{"size":4130,"mtime":1768935766637,"results":"665","hashOfConfig":"491"},{"size":5798,"mtime":1768485628964,"results":"666","hashOfConfig":"491"},{"size":1469,"mtime":1769018634698,"results":"667","hashOfConfig":"491"},{"size":561,"mtime":1768485628964,"results":"668","hashOfConfig":"491"},{"size":1617,"mtime":1768485628964,"results":"669","hashOfConfig":"491"},{"size":2090,"mtime":1768485628964,"results":"670","hashOfConfig":"491"},{"size":508,"mtime":1768485628964,"results":"671","hashOfConfig":"491"},{"size":306,"mtime":1768485628964,"results":"672","hashOfConfig":"491"},{"size":862,"mtime":1768485628964,"results":"673","hashOfConfig":"491"},{"size":1561,"mtime":1768485628964,"results":"674","hashOfConfig":"491"},{"size":288,"mtime":1768485628964,"results":"675","hashOfConfig":"491"},{"size":573,"mtime":1768485628964,"results":"676","hashOfConfig":"491"},{"size":1743,"mtime":1769018634698,"results":"677","hashOfConfig":"491"},{"size":6134,"mtime":1768485628965,"results":"678","hashOfConfig":"491"},{"size":1877,"mtime":1768485628965,"results":"679","hashOfConfig":"491"},{"size":2489,"mtime":1768485628965,"results":"680","hashOfConfig":"491"},{"size":286,"mtime":1768485628965,"results":"681","hashOfConfig":"491"},{"size":739,"mtime":1768485628965,"results":"682","hashOfConfig":"491"},{"size":674,"mtime":1768485628965,"results":"683","hashOfConfig":"491"},{"size":1613,"mtime":1768485628965,"results":"684","hashOfConfig":"491"},{"size":1251,"mtime":1768485628965,"results":"685","hashOfConfig":"491"},{"size":1747,"mtime":1768485628965,"results":"686","hashOfConfig":"491"},{"size":5347,"mtime":1768485628965,"results":"687","hashOfConfig":"491"},{"size":2152,"mtime":1768485628965,"results":"688","hashOfConfig":"491"},{"size":2094,"mtime":1768485628965,"results":"689","hashOfConfig":"491"},{"size":126,"mtime":1768485628965,"results":"690","hashOfConfig":"491"},{"size":2237,"mtime":1768485628965,"results":"691","hashOfConfig":"491"},{"size":1368,"mtime":1768485628965,"results":"692","hashOfConfig":"491"},{"size":250,"mtime":1768485628965,"results":"693","hashOfConfig":"491"},{"size":633,"mtime":1768485628965,"results":"694","hashOfConfig":"491"},{"size":4444,"mtime":1768485628965,"results":"695","hashOfConfig":"491"},{"size":7333,"mtime":1768485628966,"results":"696","hashOfConfig":"491"},{"size":5919,"mtime":1768485628966,"results":"697","hashOfConfig":"491"},{"size":1061,"mtime":1768485628966,"results":"698","hashOfConfig":"491"},{"size":1270,"mtime":1768485628966,"results":"699","hashOfConfig":"491"},{"size":2311,"mtime":1768935766637,"results":"700","hashOfConfig":"491"},{"size":12420,"mtime":1768485628966,"results":"701","hashOfConfig":"491"},{"size":3209,"mtime":1768485628966,"results":"702","hashOfConfig":"491"},{"size":492,"mtime":1768485628966,"results":"703","hashOfConfig":"491"},{"size":742,"mtime":1768485628966,"results":"704","hashOfConfig":"491"},{"size":373,"mtime":1768485628966,"results":"705","hashOfConfig":"491"},{"size":179,"mtime":1768485628966,"results":"706","hashOfConfig":"491"},{"size":2341,"mtime":1769020222179,"results":"707","hashOfConfig":"491"},{"size":713,"mtime":1768485628966,"results":"708","hashOfConfig":"491"},{"size":947,"mtime":1768485628966,"results":"709","hashOfConfig":"491"},{"size":3441,"mtime":1768485628967,"results":"710","hashOfConfig":"491"},{"size":1434,"mtime":1768485628967,"results":"711","hashOfConfig":"491"},{"size":756,"mtime":1768485628967,"results":"712","hashOfConfig":"491"},{"size":510,"mtime":1768485628967,"results":"713","hashOfConfig":"491"},{"size":368,"mtime":1768485628967,"results":"714","hashOfConfig":"491"},{"size":950,"mtime":1768485628967,"results":"715","hashOfConfig":"491"},{"size":493,"mtime":1768935766637,"results":"716","hashOfConfig":"491"},{"size":5386,"mtime":1768485628967,"results":"717","hashOfConfig":"491"},{"size":2894,"mtime":1768485628967,"results":"718","hashOfConfig":"491"},{"size":547,"mtime":1768485628967,"results":"719","hashOfConfig":"491"},{"size":4383,"mtime":1768485628967,"results":"720","hashOfConfig":"491"},{"size":1473,"mtime":1768935766637,"results":"721","hashOfConfig":"491"},{"size":734,"mtime":1768485628967,"results":"722","hashOfConfig":"491"},{"size":61,"mtime":1765879310418,"results":"723","hashOfConfig":"491"},{"size":756,"mtime":1768485628967,"results":"724","hashOfConfig":"491"},{"size":1737,"mtime":1768485628967,"results":"725","hashOfConfig":"491"},{"size":1762,"mtime":1768485628967,"results":"726","hashOfConfig":"491"},{"size":249,"mtime":1762360155096,"results":"727","hashOfConfig":"491"},{"size":4758,"mtime":1768485628967,"results":"728","hashOfConfig":"491"},{"size":404,"mtime":1768485628967,"results":"729","hashOfConfig":"491"},{"size":1414,"mtime":1769020222179,"results":"730","hashOfConfig":"491"},{"size":250,"mtime":1768485628967,"results":"731","hashOfConfig":"491"},{"size":2618,"mtime":1768485628967,"results":"732","hashOfConfig":"491"},{"size":1299,"mtime":1768485628967,"results":"733","hashOfConfig":"491"},{"size":4485,"mtime":1768485628967,"results":"734","hashOfConfig":"491"},{"size":3179,"mtime":1768935766637,"results":"735","hashOfConfig":"491"},{"size":3116,"mtime":1769020222179,"results":"736","hashOfConfig":"491"},{"size":969,"mtime":1769020222179,"results":"737","hashOfConfig":"491"},{"size":1997,"mtime":1769020222180,"results":"738","hashOfConfig":"491"},{"size":3442,"mtime":1768485628967,"results":"739","hashOfConfig":"491"},{"size":879,"mtime":1767619337864,"results":"740","hashOfConfig":"496"},{"size":4704,"mtime":1768485628967,"results":"741","hashOfConfig":"491"},{"size":2733,"mtime":1768485628967,"results":"742","hashOfConfig":"491"},{"size":922,"mtime":1768485628968,"results":"743","hashOfConfig":"491"},{"size":10545,"mtime":1768485628968,"results":"744","hashOfConfig":"491"},{"size":440,"mtime":1765879310419,"results":"745","hashOfConfig":"491"},{"size":9729,"mtime":1768485628968,"results":"746","hashOfConfig":"491"},{"size":34941,"mtime":1767619337864,"results":"747","hashOfConfig":"491"},{"size":2078,"mtime":1768485628968,"results":"748","hashOfConfig":"491"},{"size":952,"mtime":1769020222180,"results":"749","hashOfConfig":"491"},{"size":1261,"mtime":1769020222180,"results":"750","hashOfConfig":"491"},{"size":5281,"mtime":1768485628968,"results":"751","hashOfConfig":"491"},{"size":713,"mtime":1768485628968,"results":"752","hashOfConfig":"491"},{"size":246,"mtime":1768485628968,"results":"753","hashOfConfig":"491"},{"size":1626,"mtime":1768485628968,"results":"754","hashOfConfig":"491"},{"size":2382,"mtime":1768485628968,"results":"755","hashOfConfig":"491"},{"size":249,"mtime":1768485628968,"results":"756","hashOfConfig":"491"},{"size":205,"mtime":1768485628968,"results":"757","hashOfConfig":"491"},{"size":227,"mtime":1768485628968,"results":"758","hashOfConfig":"491"},{"size":700,"mtime":1768485628968,"results":"759","hashOfConfig":"491"},{"size":6279,"mtime":1768485628968,"results":"760","hashOfConfig":"491"},{"size":138,"mtime":1768485628968,"results":"761","hashOfConfig":"491"},{"size":224,"mtime":1768485628968,"results":"762","hashOfConfig":"491"},{"size":5043,"mtime":1768485628968,"results":"763","hashOfConfig":"491"},{"size":899,"mtime":1768935766637,"results":"764","hashOfConfig":"491"},{"size":1071,"mtime":1768935766637,"results":"765","hashOfConfig":"491"},{"size":2082,"mtime":1768485628968,"results":"766","hashOfConfig":"491"},{"size":3999,"mtime":1768485628968,"results":"767","hashOfConfig":"491"},{"size":1103,"mtime":1768485628968,"results":"768","hashOfConfig":"491"},{"size":560,"mtime":1768485628968,"results":"769","hashOfConfig":"491"},{"size":1517,"mtime":1769020222180,"results":"770","hashOfConfig":"491"},{"size":599,"mtime":1769020222180,"results":"771","hashOfConfig":"491"},{"size":1270,"mtime":1769020222180,"results":"772","hashOfConfig":"491"},{"size":240,"mtime":1768485628968,"results":"773","hashOfConfig":"491"},{"size":237,"mtime":1768485628968,"results":"774","hashOfConfig":"491"},{"size":565,"mtime":1768485628968,"results":"775","hashOfConfig":"491"},{"size":155,"mtime":1768485628969,"results":"776","hashOfConfig":"491"},{"size":373,"mtime":1768485628969,"results":"777","hashOfConfig":"491"},{"size":1650,"mtime":1768485628969,"results":"778","hashOfConfig":"491"},{"size":2883,"mtime":1768935766637,"results":"779","hashOfConfig":"491"},{"size":702,"mtime":1768485628969,"results":"780","hashOfConfig":"491"},{"size":1121,"mtime":1768935766638,"results":"781","hashOfConfig":"491"},{"size":1127,"mtime":1768935766638,"results":"782","hashOfConfig":"491"},{"size":4585,"mtime":1768485628969,"results":"783","hashOfConfig":"491"},{"size":14440,"mtime":1769100988355,"results":"784","hashOfConfig":"491"},{"size":473,"mtime":1768485628969,"results":"785","hashOfConfig":"491"},{"size":3264,"mtime":1768485628969,"results":"786","hashOfConfig":"491"},{"size":2037,"mtime":1768485628969,"results":"787","hashOfConfig":"491"},{"size":171,"mtime":1768485628969,"results":"788","hashOfConfig":"491"},{"size":488,"mtime":1768485628969,"results":"789","hashOfConfig":"491"},{"size":6267,"mtime":1768935766638,"results":"790","hashOfConfig":"491"},{"size":5567,"mtime":1768485628969,"results":"791","hashOfConfig":"491"},{"size":3350,"mtime":1768485628969,"results":"792","hashOfConfig":"491"},{"size":110,"mtime":1768485628969,"results":"793","hashOfConfig":"491"},{"size":81,"mtime":1768485628969,"results":"794","hashOfConfig":"491"},{"size":218,"mtime":1768485628969,"results":"795","hashOfConfig":"491"},{"size":52,"mtime":1768485628969,"results":"796","hashOfConfig":"491"},{"size":54,"mtime":1768485628969,"results":"797","hashOfConfig":"491"},{"size":7922,"mtime":1768485628969,"results":"798","hashOfConfig":"491"},{"size":10407,"mtime":1768485628969,"results":"799","hashOfConfig":"491"},{"size":1098,"mtime":1768485628969,"results":"800","hashOfConfig":"491"},{"size":555,"mtime":1768485628969,"results":"801","hashOfConfig":"491"},{"size":0,"mtime":1765879310420,"results":"802","hashOfConfig":"491"},{"size":5313,"mtime":1768935766638,"results":"803","hashOfConfig":"491"},{"size":442,"mtime":1768485628969,"results":"804","hashOfConfig":"491"},{"size":2114,"mtime":1768485628969,"results":"805","hashOfConfig":"491"},{"size":337,"mtime":1768485628969,"results":"806","hashOfConfig":"491"},{"size":125,"mtime":1768485628969,"results":"807","hashOfConfig":"491"},{"size":172,"mtime":1768485628969,"results":"808","hashOfConfig":"491"},{"size":2804,"mtime":1768485628969,"results":"809","hashOfConfig":"491"},{"size":860,"mtime":1768485628969,"results":"810","hashOfConfig":"491"},{"size":3302,"mtime":1768485628969,"results":"811","hashOfConfig":"491"},{"size":930,"mtime":1768485628969,"results":"812","hashOfConfig":"491"},{"size":214,"mtime":1768485628969,"results":"813","hashOfConfig":"491"},{"size":5542,"mtime":1768485628969,"results":"814","hashOfConfig":"491"},{"size":1690,"mtime":1768485628969,"results":"815","hashOfConfig":"491"},{"size":2237,"mtime":1768485628970,"results":"816","hashOfConfig":"491"},{"size":2613,"mtime":1768485628970,"results":"817","hashOfConfig":"491"},{"size":410,"mtime":1768485628970,"results":"818","hashOfConfig":"491"},{"size":2446,"mtime":1768485628970,"results":"819","hashOfConfig":"491"},{"size":404,"mtime":1769020222180,"results":"820","hashOfConfig":"496"},{"size":9214,"mtime":1768935766638,"results":"821","hashOfConfig":"496"},{"size":179,"mtime":1768485628970,"results":"822","hashOfConfig":"496"},{"size":846,"mtime":1768485628970,"results":"823","hashOfConfig":"491"},{"size":1522,"mtime":1768485628970,"results":"824","hashOfConfig":"491"},{"size":65,"mtime":1768485628970,"results":"825","hashOfConfig":"491"},{"size":52,"mtime":1768485628970,"results":"826","hashOfConfig":"491"},{"size":58,"mtime":1768485628970,"results":"827","hashOfConfig":"491"},{"size":212,"mtime":1768485628970,"results":"828","hashOfConfig":"491"},{"size":197,"mtime":1768485628971,"results":"829","hashOfConfig":"491"},{"size":463,"mtime":1768485628971,"results":"830","hashOfConfig":"491"},{"size":294,"mtime":1769020222180,"results":"831","hashOfConfig":"491"},{"size":126,"mtime":1768485628971,"results":"832","hashOfConfig":"491"},{"size":49,"mtime":1768485628971,"results":"833","hashOfConfig":"491"},{"size":477,"mtime":1768485628971,"results":"834","hashOfConfig":"491"},{"size":305,"mtime":1768485628971,"results":"835","hashOfConfig":"491"},{"size":182,"mtime":1768485628971,"results":"836","hashOfConfig":"491"},{"size":591,"mtime":1768485628971,"results":"837","hashOfConfig":"491"},{"size":163,"mtime":1768485628971,"results":"838","hashOfConfig":"491"},{"size":433,"mtime":1768485628971,"results":"839","hashOfConfig":"491"},{"size":156,"mtime":1768485628971,"results":"840","hashOfConfig":"491"},{"size":259,"mtime":1768485628971,"results":"841","hashOfConfig":"491"},{"size":563,"mtime":1768485628971,"results":"842","hashOfConfig":"491"},{"size":5096,"mtime":1768485628971,"results":"843","hashOfConfig":"491"},{"size":3415,"mtime":1768485628971,"results":"844","hashOfConfig":"491"},{"size":679,"mtime":1768485628971,"results":"845","hashOfConfig":"491"},{"size":643,"mtime":1769018634698,"results":"846","hashOfConfig":"491"},{"size":10091,"mtime":1769018634698,"results":"847","hashOfConfig":"491"},{"size":3511,"mtime":1768485628971,"results":"848","hashOfConfig":"491"},{"size":2006,"mtime":1766053453788,"results":"849","hashOfConfig":"491"},{"size":3555,"mtime":1768935766638,"results":"850","hashOfConfig":"491"},{"size":90,"mtime":1768485628971,"results":"851","hashOfConfig":"491"},{"size":500,"mtime":1769020222180,"results":"852","hashOfConfig":"491"},{"size":846,"mtime":1768935766638,"results":"853","hashOfConfig":"491"},{"size":1928,"mtime":1768485628971,"results":"854","hashOfConfig":"491"},{"size":1276,"mtime":1769018634698,"results":"855","hashOfConfig":"491"},{"size":650,"mtime":1769018634698,"results":"856","hashOfConfig":"491"},{"size":265,"mtime":1768485628971,"results":"857","hashOfConfig":"491"},{"size":2256,"mtime":1768485628971,"results":"858","hashOfConfig":"491"},{"size":38164,"mtime":1765879310420,"results":"859","hashOfConfig":"491"},{"size":1031,"mtime":1768485628971,"results":"860","hashOfConfig":"491"},{"size":391,"mtime":1768485628971,"results":"861","hashOfConfig":"491"},{"size":371,"mtime":1768485628971,"results":"862","hashOfConfig":"491"},{"size":2195,"mtime":1768485628971,"results":"863","hashOfConfig":"491"},{"size":1003,"mtime":1768485628971,"results":"864","hashOfConfig":"491"},{"size":236,"mtime":1768485628971,"results":"865","hashOfConfig":"491"},{"size":1121,"mtime":1768485628971,"results":"866","hashOfConfig":"491"},{"size":111,"mtime":1768485628971,"results":"867","hashOfConfig":"491"},{"size":1245,"mtime":1768485628971,"results":"868","hashOfConfig":"491"},{"size":702,"mtime":1762360155102,"results":"869","hashOfConfig":"491"},{"size":43,"mtime":1768485628971,"results":"870","hashOfConfig":"491"},{"size":4022,"mtime":1768485628971,"results":"871","hashOfConfig":"491"},{"size":1553,"mtime":1768485628971,"results":"872","hashOfConfig":"491"},{"size":2138,"mtime":1768485628971,"results":"873","hashOfConfig":"491"},{"size":1911,"mtime":1768485628971,"results":"874","hashOfConfig":"491"},{"size":1108,"mtime":1768935766638,"results":"875","hashOfConfig":"491"},{"size":691,"mtime":1768485628971,"results":"876","hashOfConfig":"491"},{"size":128,"mtime":1768485628971,"results":"877","hashOfConfig":"491"},{"size":2895,"mtime":1768935766638,"results":"878","hashOfConfig":"491"},{"size":288,"mtime":1768485628971,"results":"879","hashOfConfig":"491"},{"size":63,"mtime":1768485628971,"results":"880","hashOfConfig":"491"},{"size":4064,"mtime":1768935766639,"results":"881","hashOfConfig":"491"},{"size":23,"mtime":1768485628971,"results":"882","hashOfConfig":"496"},{"size":23,"mtime":1768485628972,"results":"883","hashOfConfig":"491"},{"size":120,"mtime":1768485628972,"results":"884","hashOfConfig":"491"},{"size":240,"mtime":1768485628972,"results":"885","hashOfConfig":"491"},{"size":299,"mtime":1768485628972,"results":"886","hashOfConfig":"491"},{"size":59,"mtime":1768485628972,"results":"887","hashOfConfig":"491"},{"size":131,"mtime":1768485628972,"results":"888","hashOfConfig":"491"},{"size":120,"mtime":1768485628972,"results":"889","hashOfConfig":"491"},{"size":234,"mtime":1768485628972,"results":"890","hashOfConfig":"491"},{"size":542,"mtime":1768485628972,"results":"891","hashOfConfig":"491"},{"size":210,"mtime":1768485628972,"results":"892","hashOfConfig":"491"},{"size":151,"mtime":1768485628972,"results":"893","hashOfConfig":"491"},{"size":184,"mtime":1768485628972,"results":"894","hashOfConfig":"491"},{"size":498,"mtime":1768485628972,"results":"895","hashOfConfig":"491"},{"size":49,"mtime":1768485628972,"results":"896","hashOfConfig":"491"},{"size":163,"mtime":1768485628972,"results":"897","hashOfConfig":"491"},{"size":82,"mtime":1768485628972,"results":"898","hashOfConfig":"491"},{"size":875,"mtime":1768935766639,"results":"899","hashOfConfig":"491"},{"size":32,"mtime":1768485628972,"results":"900","hashOfConfig":"491"},{"size":28,"mtime":1768485628972,"results":"901","hashOfConfig":"491"},{"size":195,"mtime":1768485628972,"results":"902","hashOfConfig":"491"},{"size":434,"mtime":1768485628972,"results":"903","hashOfConfig":"491"},{"size":390,"mtime":1768485628972,"results":"904","hashOfConfig":"491"},{"size":48,"mtime":1768485628972,"results":"905","hashOfConfig":"491"},{"size":137,"mtime":1768485628972,"results":"906","hashOfConfig":"491"},{"size":796,"mtime":1768485628972,"results":"907","hashOfConfig":"491"},{"size":683,"mtime":1768485628972,"results":"908","hashOfConfig":"491"},{"size":141,"mtime":1768485628972,"results":"909","hashOfConfig":"491"},{"size":238,"mtime":1768485628972,"results":"910","hashOfConfig":"491"},{"size":363,"mtime":1768485628972,"results":"911","hashOfConfig":"491"},{"size":32,"mtime":1768485628972,"results":"912","hashOfConfig":"491"},{"size":33,"mtime":1768485628972,"results":"913","hashOfConfig":"491"},{"size":114,"mtime":1768485628972,"results":"914","hashOfConfig":"491"},{"size":809,"mtime":1768485628972,"results":"915","hashOfConfig":"491"},{"size":335,"mtime":1768485628972,"results":"916","hashOfConfig":"491"},{"size":975,"mtime":1768485628972,"results":"917","hashOfConfig":"491"},{"size":273,"mtime":1768485628972,"results":"918","hashOfConfig":"491"},{"size":52,"mtime":1768485628972,"results":"919","hashOfConfig":"491"},{"size":82,"mtime":1768485628972,"results":"920","hashOfConfig":"491"},{"size":511,"mtime":1768485628972,"results":"921","hashOfConfig":"491"},{"size":1967,"mtime":1768485628972,"results":"922","hashOfConfig":"491"},{"size":447,"mtime":1768485628972,"results":"923","hashOfConfig":"491"},{"size":1208,"mtime":1768485628972,"results":"924","hashOfConfig":"491"},{"size":252,"mtime":1768485628972,"results":"925","hashOfConfig":"491"},{"size":138,"mtime":1768485628972,"results":"926","hashOfConfig":"491"},{"size":104,"mtime":1768485628972,"results":"927","hashOfConfig":"491"},{"size":350,"mtime":1768485628972,"results":"928","hashOfConfig":"491"},{"size":333,"mtime":1768485628972,"results":"929","hashOfConfig":"491"},{"size":334,"mtime":1768485628972,"results":"930","hashOfConfig":"491"},{"size":382,"mtime":1768485628972,"results":"931","hashOfConfig":"491"},{"size":299,"mtime":1768485628972,"results":"932","hashOfConfig":"491"},{"size":276,"mtime":1768485628973,"results":"933","hashOfConfig":"491"},{"size":231,"mtime":1768485628973,"results":"934","hashOfConfig":"491"},{"size":452,"mtime":1768485628973,"results":"935","hashOfConfig":"491"},{"size":41,"mtime":1768485628973,"results":"936","hashOfConfig":"491"},{"size":39,"mtime":1768485628973,"results":"937","hashOfConfig":"491"},{"size":35,"mtime":1768485628973,"results":"938","hashOfConfig":"491"},{"size":45,"mtime":1762360155104,"results":"939","hashOfConfig":"491"},{"size":236,"mtime":1768485628971,"results":"940","hashOfConfig":"491"},{"size":130,"mtime":1769111988400,"results":"941","hashOfConfig":"491"},{"size":2897,"mtime":1768485628973,"results":"942","hashOfConfig":"491"},{"size":135,"mtime":1768928617550,"results":"943","hashOfConfig":"491"},{"size":81,"mtime":1768485628973,"results":"944","hashOfConfig":"491"},{"size":151,"mtime":1768485628973,"results":"945","hashOfConfig":"496"},{"size":3012,"mtime":1768485628973,"results":"946","hashOfConfig":"491"},{"size":3973,"mtime":1768485628973,"results":"947","hashOfConfig":"491"},{"size":911,"mtime":1768485628973,"results":"948","hashOfConfig":"491"},{"size":1584,"mtime":1768485628973,"results":"949","hashOfConfig":"491"},{"size":2452,"mtime":1768485628973,"results":"950","hashOfConfig":"491"},{"size":1024,"mtime":1768485628973,"results":"951","hashOfConfig":"491"},{"size":7298,"mtime":1768485628973,"results":"952","hashOfConfig":"491"},{"size":2424,"mtime":1768935766639,"results":"953","hashOfConfig":"491"},{"size":1427,"mtime":1768485628973,"results":"954","hashOfConfig":"491"},{"size":555,"mtime":1768485628973,"results":"955","hashOfConfig":"491"},{"size":483,"mtime":1768485628973,"results":"956","hashOfConfig":"491"},{"size":18122,"mtime":1768485628973,"results":"957","hashOfConfig":"491"},{"size":1207,"mtime":1768485628973,"results":"958","hashOfConfig":"491"},{"size":515,"mtime":1768485628973,"results":"959","hashOfConfig":"491"},{"size":23391,"mtime":1769112905095,"results":"960","hashOfConfig":"491"},{"size":1967,"mtime":1768485628973,"results":"961","hashOfConfig":"491"},{"size":2728,"mtime":1768485628973,"results":"962","hashOfConfig":"491"},{"size":1688,"mtime":1768485628973,"results":"963","hashOfConfig":"491"},{"size":317,"mtime":1768485628973,"results":"964","hashOfConfig":"491"},{"size":316,"mtime":1768485628973,"results":"965","hashOfConfig":"491"},{"filePath":"966","messages":"967","suppressedMessages":"968","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"17lpsoa",{"filePath":"969","messages":"970","suppressedMessages":"971","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"972","messages":"973","suppressedMessages":"974","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"975","messages":"976","suppressedMessages":"977","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"978","messages":"979","suppressedMessages":"980","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"981","messages":"982","suppressedMessages":"983","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"984","messages":"985","suppressedMessages":"986","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"987","messages":"988","suppressedMessages":"989","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"1l1toe8",{"filePath":"990","messages":"991","suppressedMessages":"992","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"993","messages":"994","suppressedMessages":"995","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"996","messages":"997","suppressedMessages":"998","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"999","messages":"1000","suppressedMessages":"1001","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"pc2r9k",{"filePath":"1002","messages":"1003","suppressedMessages":"1004","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1005","messages":"1006","suppressedMessages":"1007","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1008","messages":"1009","suppressedMessages":"1010","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1011","messages":"1012","suppressedMessages":"1013","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1014","messages":"1015","suppressedMessages":"1016","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1017","messages":"1018","suppressedMessages":"1019","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1020","messages":"1021","suppressedMessages":"1022","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1023","messages":"1024","suppressedMessages":"1025","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1026","messages":"1027","suppressedMessages":"1028","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1029","messages":"1030","suppressedMessages":"1031","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1032","messages":"1033","suppressedMessages":"1034","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1035","messages":"1036","suppressedMessages":"1037","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1038","messages":"1039","suppressedMessages":"1040","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1041","messages":"1042","suppressedMessages":"1043","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1044","messages":"1045","suppressedMessages":"1046","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1047","messages":"1048","suppressedMessages":"1049","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1050","messages":"1051","suppressedMessages":"1052","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1053","messages":"1054","suppressedMessages":"1055","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1056","messages":"1057","suppressedMessages":"1058","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1059","messages":"1060","suppressedMessages":"1061","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1062","messages":"1063","suppressedMessages":"1064","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1065","messages":"1066","suppressedMessages":"1067","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1068","messages":"1069","suppressedMessages":"1070","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1071","messages":"1072","suppressedMessages":"1073","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1074","messages":"1075","suppressedMessages":"1076","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1077","messages":"1078","suppressedMessages":"1079","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1080","messages":"1081","suppressedMessages":"1082","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1083","messages":"1084","suppressedMessages":"1085","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1086","messages":"1087","suppressedMessages":"1088","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1089","messages":"1090","suppressedMessages":"1091","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1092","messages":"1093","suppressedMessages":"1094","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1095","messages":"1096","suppressedMessages":"1097","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1098","messages":"1099","suppressedMessages":"1100","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1101","messages":"1102","suppressedMessages":"1103","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1104","messages":"1105","suppressedMessages":"1106","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1107","messages":"1108","suppressedMessages":"1109","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1110","messages":"1111","suppressedMessages":"1112","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1113","messages":"1114","suppressedMessages":"1115","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1116","messages":"1117","suppressedMessages":"1118","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1119","messages":"1120","suppressedMessages":"1121","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1122","messages":"1123","suppressedMessages":"1124","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1125","messages":"1126","suppressedMessages":"1127","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1128","messages":"1129","suppressedMessages":"1130","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1131","messages":"1132","suppressedMessages":"1133","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1134","messages":"1135","suppressedMessages":"1136","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1137","messages":"1138","suppressedMessages":"1139","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1140","messages":"1141","suppressedMessages":"1142","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1143","messages":"1144","suppressedMessages":"1145","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1146","messages":"1147","suppressedMessages":"1148","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1149","messages":"1150","suppressedMessages":"1151","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1152","messages":"1153","suppressedMessages":"1154","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1155","messages":"1156","suppressedMessages":"1157","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1158","messages":"1159","suppressedMessages":"1160","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1161","messages":"1162","suppressedMessages":"1163","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1164","messages":"1165","suppressedMessages":"1166","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1167","messages":"1168","suppressedMessages":"1169","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1170","messages":"1171","suppressedMessages":"1172","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1173","messages":"1174","suppressedMessages":"1175","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1176","messages":"1177","suppressedMessages":"1178","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1179","messages":"1180","suppressedMessages":"1181","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1182","messages":"1183","suppressedMessages":"1184","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1185","messages":"1186","suppressedMessages":"1187","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1188","messages":"1189","suppressedMessages":"1190","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1191","messages":"1192","suppressedMessages":"1193","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1194","messages":"1195","suppressedMessages":"1196","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1197","messages":"1198","suppressedMessages":"1199","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1200","messages":"1201","suppressedMessages":"1202","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1203","messages":"1204","suppressedMessages":"1205","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1206","messages":"1207","suppressedMessages":"1208","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1209","messages":"1210","suppressedMessages":"1211","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1212","messages":"1213","suppressedMessages":"1214","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1215","messages":"1216","suppressedMessages":"1217","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1218","messages":"1219","suppressedMessages":"1220","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1221","messages":"1222","suppressedMessages":"1223","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1224","messages":"1225","suppressedMessages":"1226","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1227","messages":"1228","suppressedMessages":"1229","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1230","messages":"1231","suppressedMessages":"1232","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1233","messages":"1234","suppressedMessages":"1235","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1236","messages":"1237","suppressedMessages":"1238","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1239","messages":"1240","suppressedMessages":"1241","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1242","messages":"1243","suppressedMessages":"1244","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1245","messages":"1246","suppressedMessages":"1247","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1248","messages":"1249","suppressedMessages":"1250","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1251","messages":"1252","suppressedMessages":"1253","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1254","messages":"1255","suppressedMessages":"1256","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1257","messages":"1258","suppressedMessages":"1259","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1260","messages":"1261","suppressedMessages":"1262","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1263","messages":"1264","suppressedMessages":"1265","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1266","messages":"1267","suppressedMessages":"1268","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1269","messages":"1270","suppressedMessages":"1271","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1272","messages":"1273","suppressedMessages":"1274","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1275","messages":"1276","suppressedMessages":"1277","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1278","messages":"1279","suppressedMessages":"1280","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1281","messages":"1282","suppressedMessages":"1283","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1284","messages":"1285","suppressedMessages":"1286","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1287","messages":"1288","suppressedMessages":"1289","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1290","messages":"1291","suppressedMessages":"1292","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1293","messages":"1294","suppressedMessages":"1295","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1296","messages":"1297","suppressedMessages":"1298","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1299","messages":"1300","suppressedMessages":"1301","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1302","messages":"1303","suppressedMessages":"1304","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1305","messages":"1306","suppressedMessages":"1307","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1308","messages":"1309","suppressedMessages":"1310","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1311","messages":"1312","suppressedMessages":"1313","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1314","messages":"1315","suppressedMessages":"1316","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1317","messages":"1318","suppressedMessages":"1319","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1320","messages":"1321","suppressedMessages":"1322","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1323","messages":"1324","suppressedMessages":"1325","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1326","messages":"1327","suppressedMessages":"1328","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1329","messages":"1330","suppressedMessages":"1331","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1332","messages":"1333","suppressedMessages":"1334","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1335","messages":"1336","suppressedMessages":"1337","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1338","messages":"1339","suppressedMessages":"1340","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1341","messages":"1342","suppressedMessages":"1343","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1344","messages":"1345","suppressedMessages":"1346","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1347","messages":"1348","suppressedMessages":"1349","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1350","messages":"1351","suppressedMessages":"1352","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1353","messages":"1354","suppressedMessages":"1355","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1356","messages":"1357","suppressedMessages":"1358","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1359","messages":"1360","suppressedMessages":"1361","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1362","messages":"1363","suppressedMessages":"1364","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1365","messages":"1366","suppressedMessages":"1367","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1368","messages":"1369","suppressedMessages":"1370","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1371","messages":"1372","suppressedMessages":"1373","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1374","messages":"1375","suppressedMessages":"1376","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1377","messages":"1378","suppressedMessages":"1379","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1380","messages":"1381","suppressedMessages":"1382","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1383","messages":"1384","suppressedMessages":"1385","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1386","messages":"1387","suppressedMessages":"1388","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1389","messages":"1390","suppressedMessages":"1391","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1392","messages":"1393","suppressedMessages":"1394","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1395","messages":"1396","suppressedMessages":"1397","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1398","messages":"1399","suppressedMessages":"1400","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1401","messages":"1402","suppressedMessages":"1403","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1404","messages":"1405","suppressedMessages":"1406","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1407","messages":"1408","suppressedMessages":"1409","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1410","messages":"1411","suppressedMessages":"1412","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1413","messages":"1414","suppressedMessages":"1415","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1416","messages":"1417","suppressedMessages":"1418","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1419","messages":"1420","suppressedMessages":"1421","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1422","messages":"1423","suppressedMessages":"1424","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1425","messages":"1426","suppressedMessages":"1427","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1428","messages":"1429","suppressedMessages":"1430","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1431","messages":"1432","suppressedMessages":"1433","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1434","messages":"1435","suppressedMessages":"1436","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1437","messages":"1438","suppressedMessages":"1439","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1440","messages":"1441","suppressedMessages":"1442","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1443","messages":"1444","suppressedMessages":"1445","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1446","messages":"1447","suppressedMessages":"1448","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1449","messages":"1450","suppressedMessages":"1451","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1452","messages":"1453","suppressedMessages":"1454","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1455","messages":"1456","suppressedMessages":"1457","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1458","messages":"1459","suppressedMessages":"1460","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1461","messages":"1462","suppressedMessages":"1463","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1464","messages":"1465","suppressedMessages":"1466","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1467","messages":"1468","suppressedMessages":"1469","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1470","messages":"1471","suppressedMessages":"1472","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1473","messages":"1474","suppressedMessages":"1475","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1476","messages":"1477","suppressedMessages":"1478","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1479","messages":"1480","suppressedMessages":"1481","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1482","messages":"1483","suppressedMessages":"1484","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1485","messages":"1486","suppressedMessages":"1487","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1488","messages":"1489","suppressedMessages":"1490","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1491","messages":"1492","suppressedMessages":"1493","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1494","messages":"1495","suppressedMessages":"1496","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1497","messages":"1498","suppressedMessages":"1499","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1500","messages":"1501","suppressedMessages":"1502","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1503","messages":"1504","suppressedMessages":"1505","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1506","messages":"1507","suppressedMessages":"1508","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1509","messages":"1510","suppressedMessages":"1511","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1512","messages":"1513","suppressedMessages":"1514","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1515","messages":"1516","suppressedMessages":"1517","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1518","messages":"1519","suppressedMessages":"1520","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1521","messages":"1522","suppressedMessages":"1523","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1524","messages":"1525","suppressedMessages":"1526","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1527","messages":"1528","suppressedMessages":"1529","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1530","messages":"1531","suppressedMessages":"1532","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1533","messages":"1534","suppressedMessages":"1535","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1536","messages":"1537","suppressedMessages":"1538","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1539","messages":"1540","suppressedMessages":"1541","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1542","messages":"1543","suppressedMessages":"1544","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1545","messages":"1546","suppressedMessages":"1547","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1548","messages":"1549","suppressedMessages":"1550","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1551","messages":"1552","suppressedMessages":"1553","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1554","messages":"1555","suppressedMessages":"1556","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1557","messages":"1558","suppressedMessages":"1559","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1560","messages":"1561","suppressedMessages":"1562","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1563","messages":"1564","suppressedMessages":"1565","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1566","messages":"1567","suppressedMessages":"1568","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1569","messages":"1570","suppressedMessages":"1571","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1572","messages":"1573","suppressedMessages":"1574","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1575","messages":"1576","suppressedMessages":"1577","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1578","messages":"1579","suppressedMessages":"1580","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1581","messages":"1582","suppressedMessages":"1583","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1584","messages":"1585","suppressedMessages":"1586","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1587","messages":"1588","suppressedMessages":"1589","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1590","messages":"1591","suppressedMessages":"1592","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1593","messages":"1594","suppressedMessages":"1595","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1596","messages":"1597","suppressedMessages":"1598","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1599","messages":"1600","suppressedMessages":"1601","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1602","messages":"1603","suppressedMessages":"1604","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1605","messages":"1606","suppressedMessages":"1607","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1608","messages":"1609","suppressedMessages":"1610","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1611","messages":"1612","suppressedMessages":"1613","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1614","messages":"1615","suppressedMessages":"1616","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1617","messages":"1618","suppressedMessages":"1619","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1620","messages":"1621","suppressedMessages":"1622","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1623","messages":"1624","suppressedMessages":"1625","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1626","messages":"1627","suppressedMessages":"1628","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1629","messages":"1630","suppressedMessages":"1631","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1632","messages":"1633","suppressedMessages":"1634","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1635","messages":"1636","suppressedMessages":"1637","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1638","messages":"1639","suppressedMessages":"1640","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1641","messages":"1642","suppressedMessages":"1643","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1644","messages":"1645","suppressedMessages":"1646","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1647","messages":"1648","suppressedMessages":"1649","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1650","messages":"1651","suppressedMessages":"1652","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1653","messages":"1654","suppressedMessages":"1655","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1656","messages":"1657","suppressedMessages":"1658","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1659","messages":"1660","suppressedMessages":"1661","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1662","messages":"1663","suppressedMessages":"1664","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1665","messages":"1666","suppressedMessages":"1667","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1668","messages":"1669","suppressedMessages":"1670","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1671","messages":"1672","suppressedMessages":"1673","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1674","messages":"1675","suppressedMessages":"1676","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1677","messages":"1678","suppressedMessages":"1679","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1680","messages":"1681","suppressedMessages":"1682","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1683","messages":"1684","suppressedMessages":"1685","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1686","messages":"1687","suppressedMessages":"1688","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1689","messages":"1690","suppressedMessages":"1691","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1692","messages":"1693","suppressedMessages":"1694","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1695","messages":"1696","suppressedMessages":"1697","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1698","messages":"1699","suppressedMessages":"1700","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1701","messages":"1702","suppressedMessages":"1703","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1704","messages":"1705","suppressedMessages":"1706","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1707","messages":"1708","suppressedMessages":"1709","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1710","messages":"1711","suppressedMessages":"1712","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1713","messages":"1714","suppressedMessages":"1715","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1716","messages":"1717","suppressedMessages":"1718","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1719","messages":"1720","suppressedMessages":"1721","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1722","messages":"1723","suppressedMessages":"1724","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1725","messages":"1726","suppressedMessages":"1727","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1728","messages":"1729","suppressedMessages":"1730","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1731","messages":"1732","suppressedMessages":"1733","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1734","messages":"1735","suppressedMessages":"1736","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1737","messages":"1738","suppressedMessages":"1739","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1740","messages":"1741","suppressedMessages":"1742","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1743","messages":"1744","suppressedMessages":"1745","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1746","messages":"1747","suppressedMessages":"1748","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1749","messages":"1750","suppressedMessages":"1751","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1752","messages":"1753","suppressedMessages":"1754","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1755","messages":"1756","suppressedMessages":"1757","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1758","messages":"1759","suppressedMessages":"1760","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1761","messages":"1762","suppressedMessages":"1763","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1764","messages":"1765","suppressedMessages":"1766","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1767","messages":"1768","suppressedMessages":"1769","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1770","messages":"1771","suppressedMessages":"1772","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1773","messages":"1774","suppressedMessages":"1775","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1776","messages":"1777","suppressedMessages":"1778","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1779","messages":"1780","suppressedMessages":"1781","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1782","messages":"1783","suppressedMessages":"1784","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1785","messages":"1786","suppressedMessages":"1787","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1788","messages":"1789","suppressedMessages":"1790","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1791","messages":"1792","suppressedMessages":"1793","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1794","messages":"1795","suppressedMessages":"1796","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1797","messages":"1798","suppressedMessages":"1799","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1800","messages":"1801","suppressedMessages":"1802","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1803","messages":"1804","suppressedMessages":"1805","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1806","messages":"1807","suppressedMessages":"1808","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1809","messages":"1810","suppressedMessages":"1811","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1812","messages":"1813","suppressedMessages":"1814","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1815","messages":"1816","suppressedMessages":"1817","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1818","messages":"1819","suppressedMessages":"1820","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1821","messages":"1822","suppressedMessages":"1823","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1824","messages":"1825","suppressedMessages":"1826","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1827","messages":"1828","suppressedMessages":"1829","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1830","messages":"1831","suppressedMessages":"1832","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1833","messages":"1834","suppressedMessages":"1835","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1836","messages":"1837","suppressedMessages":"1838","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1839","messages":"1840","suppressedMessages":"1841","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1842","messages":"1843","suppressedMessages":"1844","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1845","messages":"1846","suppressedMessages":"1847","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1848","messages":"1849","suppressedMessages":"1850","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1851","messages":"1852","suppressedMessages":"1853","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1854","messages":"1855","suppressedMessages":"1856","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1857","messages":"1858","suppressedMessages":"1859","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1860","messages":"1861","suppressedMessages":"1862","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1863","messages":"1864","suppressedMessages":"1865","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1866","messages":"1867","suppressedMessages":"1868","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1869","messages":"1870","suppressedMessages":"1871","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1872","messages":"1873","suppressedMessages":"1874","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1875","messages":"1876","suppressedMessages":"1877","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1878","messages":"1879","suppressedMessages":"1880","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1881","messages":"1882","suppressedMessages":"1883","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1884","messages":"1885","suppressedMessages":"1886","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1887","messages":"1888","suppressedMessages":"1889","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1890","messages":"1891","suppressedMessages":"1892","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1893","messages":"1894","suppressedMessages":"1895","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1896","messages":"1897","suppressedMessages":"1898","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1899","messages":"1900","suppressedMessages":"1901","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1902","messages":"1903","suppressedMessages":"1904","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1905","messages":"1906","suppressedMessages":"1907","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1908","messages":"1909","suppressedMessages":"1910","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1911","messages":"1912","suppressedMessages":"1913","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1914","messages":"1915","suppressedMessages":"1916","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1917","messages":"1918","suppressedMessages":"1919","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1920","messages":"1921","suppressedMessages":"1922","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1923","messages":"1924","suppressedMessages":"1925","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1926","messages":"1927","suppressedMessages":"1928","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1929","messages":"1930","suppressedMessages":"1931","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1932","messages":"1933","suppressedMessages":"1934","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1935","messages":"1936","suppressedMessages":"1937","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1938","messages":"1939","suppressedMessages":"1940","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1941","messages":"1942","suppressedMessages":"1943","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1944","messages":"1945","suppressedMessages":"1946","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1947","messages":"1948","suppressedMessages":"1949","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1950","messages":"1951","suppressedMessages":"1952","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1953","messages":"1954","suppressedMessages":"1955","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1956","messages":"1957","suppressedMessages":"1958","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1959","messages":"1960","suppressedMessages":"1961","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1962","messages":"1963","suppressedMessages":"1964","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1965","messages":"1966","suppressedMessages":"1967","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1968","messages":"1969","suppressedMessages":"1970","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1971","messages":"1972","suppressedMessages":"1973","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1974","messages":"1975","suppressedMessages":"1976","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1977","messages":"1978","suppressedMessages":"1979","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1980","messages":"1981","suppressedMessages":"1982","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1983","messages":"1984","suppressedMessages":"1985","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1986","messages":"1987","suppressedMessages":"1988","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1989","messages":"1990","suppressedMessages":"1991","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1992","messages":"1993","suppressedMessages":"1994","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1995","messages":"1996","suppressedMessages":"1997","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"1998","messages":"1999","suppressedMessages":"2000","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2001","messages":"2002","suppressedMessages":"2003","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2004","messages":"2005","suppressedMessages":"2006","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2007","messages":"2008","suppressedMessages":"2009","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2010","messages":"2011","suppressedMessages":"2012","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2013","messages":"2014","suppressedMessages":"2015","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2016","messages":"2017","suppressedMessages":"2018","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2019","messages":"2020","suppressedMessages":"2021","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2022","messages":"2023","suppressedMessages":"2024","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2025","messages":"2026","suppressedMessages":"2027","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2028","messages":"2029","suppressedMessages":"2030","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2031","messages":"2032","suppressedMessages":"2033","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2034","messages":"2035","suppressedMessages":"2036","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2037","messages":"2038","suppressedMessages":"2039","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2040","messages":"2041","suppressedMessages":"2042","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2043","messages":"2044","suppressedMessages":"2045","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2046","messages":"2047","suppressedMessages":"2048","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2049","messages":"2050","suppressedMessages":"2051","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2052","messages":"2053","suppressedMessages":"2054","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2055","messages":"2056","suppressedMessages":"2057","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2058","messages":"2059","suppressedMessages":"2060","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2061","messages":"2062","suppressedMessages":"2063","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2064","messages":"2065","suppressedMessages":"2066","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2067","messages":"2068","suppressedMessages":"2069","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2070","messages":"2071","suppressedMessages":"2072","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2073","messages":"2074","suppressedMessages":"2075","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2076","messages":"2077","suppressedMessages":"2078","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2079","messages":"2080","suppressedMessages":"2081","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2082","messages":"2083","suppressedMessages":"2084","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2085","messages":"2086","suppressedMessages":"2087","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2088","messages":"2089","suppressedMessages":"2090","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2091","messages":"2092","suppressedMessages":"2093","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2094","messages":"2095","suppressedMessages":"2096","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2097","messages":"2098","suppressedMessages":"2099","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2100","messages":"2101","suppressedMessages":"2102","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2103","messages":"2104","suppressedMessages":"2105","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2106","messages":"2107","suppressedMessages":"2108","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2109","messages":"2110","suppressedMessages":"2111","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2112","messages":"2113","suppressedMessages":"2114","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2115","messages":"2116","suppressedMessages":"2117","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2118","messages":"2119","suppressedMessages":"2120","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2121","messages":"2122","suppressedMessages":"2123","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2124","messages":"2125","suppressedMessages":"2126","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2127","messages":"2128","suppressedMessages":"2129","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2130","messages":"2131","suppressedMessages":"2132","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2133","messages":"2134","suppressedMessages":"2135","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2136","messages":"2137","suppressedMessages":"2138","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2139","messages":"2140","suppressedMessages":"2141","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2142","messages":"2143","suppressedMessages":"2144","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2145","messages":"2146","suppressedMessages":"2147","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2148","messages":"2149","suppressedMessages":"2150","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2151","messages":"2152","suppressedMessages":"2153","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2154","messages":"2155","suppressedMessages":"2156","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2157","messages":"2158","suppressedMessages":"2159","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2160","messages":"2161","suppressedMessages":"2162","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2163","messages":"2164","suppressedMessages":"2165","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2166","messages":"2167","suppressedMessages":"2168","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2169","messages":"2170","suppressedMessages":"2171","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2172","messages":"2173","suppressedMessages":"2174","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2175","messages":"2176","suppressedMessages":"2177","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2178","messages":"2179","suppressedMessages":"2180","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2181","messages":"2182","suppressedMessages":"2183","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2184","messages":"2185","suppressedMessages":"2186","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2187","messages":"2188","suppressedMessages":"2189","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2190","messages":"2191","suppressedMessages":"2192","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2193","messages":"2194","suppressedMessages":"2195","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2196","messages":"2197","suppressedMessages":"2198","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2199","messages":"2200","suppressedMessages":"2201","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2202","messages":"2203","suppressedMessages":"2204","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2205","messages":"2206","suppressedMessages":"2207","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2208","messages":"2209","suppressedMessages":"2210","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2211","messages":"2212","suppressedMessages":"2213","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2214","messages":"2215","suppressedMessages":"2216","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2217","messages":"2218","suppressedMessages":"2219","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2220","messages":"2221","suppressedMessages":"2222","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2223","messages":"2224","suppressedMessages":"2225","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2226","messages":"2227","suppressedMessages":"2228","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2229","messages":"2230","suppressedMessages":"2231","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2232","messages":"2233","suppressedMessages":"2234","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2235","messages":"2236","suppressedMessages":"2237","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2238","messages":"2239","suppressedMessages":"2240","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2241","messages":"2242","suppressedMessages":"2243","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2244","messages":"2245","suppressedMessages":"2246","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2247","messages":"2248","suppressedMessages":"2249","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2250","messages":"2251","suppressedMessages":"2252","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2253","messages":"2254","suppressedMessages":"2255","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2256","messages":"2257","suppressedMessages":"2258","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2259","messages":"2260","suppressedMessages":"2261","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2262","messages":"2263","suppressedMessages":"2264","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2265","messages":"2266","suppressedMessages":"2267","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2268","messages":"2269","suppressedMessages":"2270","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2271","messages":"2272","suppressedMessages":"2273","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2274","messages":"2275","suppressedMessages":"2276","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2277","messages":"2278","suppressedMessages":"2279","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2280","messages":"2281","suppressedMessages":"2282","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2283","messages":"2284","suppressedMessages":"2285","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2286","messages":"2287","suppressedMessages":"2288","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2289","messages":"2290","suppressedMessages":"2291","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2292","messages":"2293","suppressedMessages":"2294","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2295","messages":"2296","suppressedMessages":"2297","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2298","messages":"2299","suppressedMessages":"2300","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2301","messages":"2302","suppressedMessages":"2303","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2304","messages":"2305","suppressedMessages":"2306","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2307","messages":"2308","suppressedMessages":"2309","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2310","messages":"2311","suppressedMessages":"2312","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2313","messages":"2314","suppressedMessages":"2315","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2316","messages":"2317","suppressedMessages":"2318","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2319","messages":"2320","suppressedMessages":"2321","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2322","messages":"2323","suppressedMessages":"2324","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2325","messages":"2326","suppressedMessages":"2327","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2328","messages":"2329","suppressedMessages":"2330","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2331","messages":"2332","suppressedMessages":"2333","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2334","messages":"2335","suppressedMessages":"2336","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2337","messages":"2338","suppressedMessages":"2339","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2340","messages":"2341","suppressedMessages":"2342","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2343","messages":"2344","suppressedMessages":"2345","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2346","messages":"2347","suppressedMessages":"2348","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2349","messages":"2350","suppressedMessages":"2351","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2352","messages":"2353","suppressedMessages":"2354","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2355","messages":"2356","suppressedMessages":"2357","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2358","messages":"2359","suppressedMessages":"2360","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2361","messages":"2362","suppressedMessages":"2363","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2364","messages":"2365","suppressedMessages":"2366","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2367","messages":"2368","suppressedMessages":"2369","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2370","messages":"2371","suppressedMessages":"2372","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2373","messages":"2374","suppressedMessages":"2375","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2376","messages":"2377","suppressedMessages":"2378","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2379","messages":"2380","suppressedMessages":"2381","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2382","messages":"2383","suppressedMessages":"2384","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2385","messages":"2386","suppressedMessages":"2387","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2388","messages":"2389","suppressedMessages":"2390","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2391","messages":"2392","suppressedMessages":"2393","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2394","messages":"2395","suppressedMessages":"2396","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2397","messages":"2398","suppressedMessages":"2399","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2400","messages":"2401","suppressedMessages":"2402","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2403","messages":"2404","suppressedMessages":"2405","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"2406","messages":"2407","suppressedMessages":"2408","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"/home/pooya/Code/nitro/AGENTS.md",[],[],"/home/pooya/Code/nitro/CHANGELOG.md",[],[],"/home/pooya/Code/nitro/CLAUDE.md",[],[],"/home/pooya/Code/nitro/CODE_OF_CONDUCT.md",[],[],"/home/pooya/Code/nitro/CONTRIBUTING.md",[],[],"/home/pooya/Code/nitro/README.md",[],[],"/home/pooya/Code/nitro/SECURITY.md",[],[],"/home/pooya/Code/nitro/automd.config.ts",[],[],"/home/pooya/Code/nitro/build.config.ts",[],[],"/home/pooya/Code/nitro/changelog.config.ts",[],[],"/home/pooya/Code/nitro/docs/.config/automd.config.ts",[],[],"/home/pooya/Code/nitro/eslint.config.mjs",[],[],"/home/pooya/Code/nitro/examples/api-routes/api/hello/[name].ts",[],[],"/home/pooya/Code/nitro/examples/api-routes/api/hello.ts",[],[],"/home/pooya/Code/nitro/examples/api-routes/api/test.get.ts",[],[],"/home/pooya/Code/nitro/examples/api-routes/api/test.post.ts",[],[],"/home/pooya/Code/nitro/examples/api-routes/nitro.config.ts",[],[],"/home/pooya/Code/nitro/examples/api-routes/vite.config.ts",[],[],"/home/pooya/Code/nitro/examples/auto-imports/nitro.config.ts",[],[],"/home/pooya/Code/nitro/examples/auto-imports/server/utils/hello.ts",[],[],"/home/pooya/Code/nitro/examples/auto-imports/server.ts",[],[],"/home/pooya/Code/nitro/examples/auto-imports/vite.config.ts",[],[],"/home/pooya/Code/nitro/examples/cached-handler/nitro.config.ts",[],[],"/home/pooya/Code/nitro/examples/cached-handler/server.ts",[],[],"/home/pooya/Code/nitro/examples/cached-handler/vite.config.ts",[],[],"/home/pooya/Code/nitro/examples/custom-error-handler/error.ts",[],[],"/home/pooya/Code/nitro/examples/custom-error-handler/nitro.config.ts",[],[],"/home/pooya/Code/nitro/examples/custom-error-handler/server.ts",[],[],"/home/pooya/Code/nitro/examples/custom-error-handler/vite.config.ts",[],[],"/home/pooya/Code/nitro/examples/database/nitro.config.ts",[],[],"/home/pooya/Code/nitro/examples/database/server.ts",[],[],"/home/pooya/Code/nitro/examples/database/tasks/db/migrate.ts",[],[],"/home/pooya/Code/nitro/examples/database/vite.config.ts",[],[],"/home/pooya/Code/nitro/examples/elysia/nitro.config.ts",[],[],"/home/pooya/Code/nitro/examples/elysia/server.ts",[],[],"/home/pooya/Code/nitro/examples/elysia/vite.config.ts",[],[],"/home/pooya/Code/nitro/examples/express/nitro.config.ts",[],[],"/home/pooya/Code/nitro/examples/express/server.node.ts",[],[],"/home/pooya/Code/nitro/examples/express/vite.config.ts",[],[],"/home/pooya/Code/nitro/examples/fastify/nitro.config.ts",[],[],"/home/pooya/Code/nitro/examples/fastify/server.node.ts",[],[],"/home/pooya/Code/nitro/examples/fastify/vite.config.ts",[],[],"/home/pooya/Code/nitro/examples/hello-world/nitro.config.ts",[],[],"/home/pooya/Code/nitro/examples/hello-world/server.ts",[],[],"/home/pooya/Code/nitro/examples/hello-world/vite.config.ts",[],[],"/home/pooya/Code/nitro/examples/hono/nitro.config.ts",[],[],"/home/pooya/Code/nitro/examples/hono/server.ts",[],[],"/home/pooya/Code/nitro/examples/hono/vite.config.ts",[],[],"/home/pooya/Code/nitro/examples/import-alias/nitro.config.ts",[],[],"/home/pooya/Code/nitro/examples/import-alias/server/routes/index.ts",[],[],"/home/pooya/Code/nitro/examples/import-alias/server/utils/math.ts",[],[],"/home/pooya/Code/nitro/examples/import-alias/vite.config.ts",[],[],"/home/pooya/Code/nitro/examples/middleware/nitro.config.ts",[],[],"/home/pooya/Code/nitro/examples/middleware/server/middleware/auth.ts",[],[],"/home/pooya/Code/nitro/examples/middleware/server.ts",[],[],"/home/pooya/Code/nitro/examples/middleware/vite.config.ts",[],[],"/home/pooya/Code/nitro/examples/mono-jsx/nitro.config.ts",[],[],"/home/pooya/Code/nitro/examples/mono-jsx/server.tsx",[],[],"/home/pooya/Code/nitro/examples/mono-jsx/vite.config.ts",[],[],"/home/pooya/Code/nitro/examples/nano-jsx/nitro.config.ts",[],[],"/home/pooya/Code/nitro/examples/nano-jsx/server.tsx",[],[],"/home/pooya/Code/nitro/examples/nano-jsx/vite.config.ts",[],[],"/home/pooya/Code/nitro/examples/plugins/nitro.config.ts",[],[],"/home/pooya/Code/nitro/examples/plugins/server/plugins/test.ts",[],[],"/home/pooya/Code/nitro/examples/plugins/server.ts",[],[],"/home/pooya/Code/nitro/examples/plugins/vite.config.ts",[],[],"/home/pooya/Code/nitro/examples/renderer/api/hello.ts",[],[],"/home/pooya/Code/nitro/examples/renderer/nitro.config.ts",[],[],"/home/pooya/Code/nitro/examples/renderer/renderer.ts",[],[],"/home/pooya/Code/nitro/examples/renderer/vite.config.ts",[],[],"/home/pooya/Code/nitro/examples/runtime-config/nitro.config.ts",[],[],"/home/pooya/Code/nitro/examples/runtime-config/server.ts",[],[],"/home/pooya/Code/nitro/examples/runtime-config/vite.config.ts",[],[],"/home/pooya/Code/nitro/examples/server-fetch/nitro.config.ts",[],[],"/home/pooya/Code/nitro/examples/server-fetch/routes/hello.ts",[],[],"/home/pooya/Code/nitro/examples/server-fetch/routes/index.ts",[],[],"/home/pooya/Code/nitro/examples/server-fetch/vite.config.ts",[],[],"/home/pooya/Code/nitro/examples/shiki/api/highlight.ts",[],[],"/home/pooya/Code/nitro/examples/shiki/nitro.config.ts",[],[],"/home/pooya/Code/nitro/examples/shiki/vite.config.ts",[],[],"/home/pooya/Code/nitro/examples/virtual-routes/nitro.config.ts",[],[],"/home/pooya/Code/nitro/examples/virtual-routes/vite.config.ts",[],[],"/home/pooya/Code/nitro/examples/vite-nitro-plugin/vite.config.mjs",[],[],"/home/pooya/Code/nitro/examples/vite-rsc/app/action.tsx",[],[],"/home/pooya/Code/nitro/examples/vite-rsc/app/client.tsx",[],[],"/home/pooya/Code/nitro/examples/vite-rsc/app/framework/entry.browser.tsx",[],["2409"],"/home/pooya/Code/nitro/examples/vite-rsc/app/framework/entry.rsc.tsx",[],["2410"],"/home/pooya/Code/nitro/examples/vite-rsc/app/framework/entry.ssr.tsx",[],[],"/home/pooya/Code/nitro/examples/vite-rsc/app/framework/error-boundary.tsx",[],[],"/home/pooya/Code/nitro/examples/vite-rsc/app/framework/request.tsx",[],[],"/home/pooya/Code/nitro/examples/vite-rsc/app/root.tsx",[],["2411"],"/home/pooya/Code/nitro/examples/vite-rsc/vite.config.ts",[],[],"/home/pooya/Code/nitro/examples/vite-ssr-html/app/entry-server.ts",[],[],"/home/pooya/Code/nitro/examples/vite-ssr-html/routes/quote.ts",[],[],"/home/pooya/Code/nitro/examples/vite-ssr-html/vite.config.ts",[],[],"/home/pooya/Code/nitro/examples/vite-ssr-preact/src/app.tsx",[],[],"/home/pooya/Code/nitro/examples/vite-ssr-preact/src/entry-client.tsx",[],[],"/home/pooya/Code/nitro/examples/vite-ssr-preact/src/entry-server.tsx",[],[],"/home/pooya/Code/nitro/examples/vite-ssr-preact/vite.config.mjs",[],[],"/home/pooya/Code/nitro/examples/vite-ssr-react/src/app.tsx",[],[],"/home/pooya/Code/nitro/examples/vite-ssr-react/src/entry-client.tsx",[],[],"/home/pooya/Code/nitro/examples/vite-ssr-react/src/entry-server.tsx",[],[],"/home/pooya/Code/nitro/examples/vite-ssr-react/vite.config.mjs",[],[],"/home/pooya/Code/nitro/examples/vite-ssr-solid/src/app.tsx",[],[],"/home/pooya/Code/nitro/examples/vite-ssr-solid/src/entry-client.tsx",[],[],"/home/pooya/Code/nitro/examples/vite-ssr-solid/src/entry-server.tsx",[],[],"/home/pooya/Code/nitro/examples/vite-ssr-solid/vite.config.mjs",[],[],"/home/pooya/Code/nitro/examples/vite-ssr-tsr-react/src/main.tsx",[],[],"/home/pooya/Code/nitro/examples/vite-ssr-tsr-react/src/routes/__root.tsx",[],[],"/home/pooya/Code/nitro/examples/vite-ssr-tsr-react/src/routes/index.tsx",[],[],"/home/pooya/Code/nitro/examples/vite-ssr-tsr-react/vite.config.mjs",[],[],"/home/pooya/Code/nitro/examples/vite-ssr-tss-react/server.ts",[],[],"/home/pooya/Code/nitro/examples/vite-ssr-tss-react/src/router.tsx",[],[],"/home/pooya/Code/nitro/examples/vite-ssr-tss-react/src/routes/__root.tsx",[],[],"/home/pooya/Code/nitro/examples/vite-ssr-tss-react/src/routes/api/test.ts",[],[],"/home/pooya/Code/nitro/examples/vite-ssr-tss-react/src/routes/index.tsx",[],[],"/home/pooya/Code/nitro/examples/vite-ssr-tss-react/vite.config.mjs",[],[],"/home/pooya/Code/nitro/examples/vite-ssr-vue-router/app/entry-client.ts",[],["2412"],"/home/pooya/Code/nitro/examples/vite-ssr-vue-router/app/entry-server.ts",[],[],"/home/pooya/Code/nitro/examples/vite-ssr-vue-router/app/routes.ts",[],[],"/home/pooya/Code/nitro/examples/vite-ssr-vue-router/app/shims.d.ts",[],[],"/home/pooya/Code/nitro/examples/vite-ssr-vue-router/vite.config.mjs",[],[],"/home/pooya/Code/nitro/examples/vite-trpc/server/trpc.ts",[],[],"/home/pooya/Code/nitro/examples/vite-trpc/vite.config.ts",[],[],"/home/pooya/Code/nitro/examples/websocket/nitro.config.ts",[],[],"/home/pooya/Code/nitro/examples/websocket/routes/_ws.ts",[],[],"/home/pooya/Code/nitro/examples/websocket/vite.config.ts",[],[],"/home/pooya/Code/nitro/lib/h3.d.mts",[],[],"/home/pooya/Code/nitro/lib/h3.mjs",[],[],"/home/pooya/Code/nitro/lib/vite.types.d.mts",[],[],"/home/pooya/Code/nitro/lib/vite.types.mjs",[],["2413"],"/home/pooya/Code/nitro/playground/nitro.config.ts",[],[],"/home/pooya/Code/nitro/playground/server.ts",[],[],"/home/pooya/Code/nitro/playground/vite.config.ts",[],[],"/home/pooya/Code/nitro/scripts/bump-nightly.ts",[],["2414","2415"],"/home/pooya/Code/nitro/scripts/gen-node-compat.ts",[],[],"/home/pooya/Code/nitro/scripts/gen-presets.ts",[],[],"/home/pooya/Code/nitro/src/build/assets.ts",[],[],"/home/pooya/Code/nitro/src/build/build.ts",[],[],"/home/pooya/Code/nitro/src/build/chunks.ts",[],[],"/home/pooya/Code/nitro/src/build/config.ts",[],[],"/home/pooya/Code/nitro/src/build/info.ts",[],[],"/home/pooya/Code/nitro/src/build/plugins/externals.ts",[],[],"/home/pooya/Code/nitro/src/build/plugins/oxc.ts",[],[],"/home/pooya/Code/nitro/src/build/plugins/raw.ts",[],[],"/home/pooya/Code/nitro/src/build/plugins/route-meta.ts",[],["2416"],"/home/pooya/Code/nitro/src/build/plugins/server-main.ts",[],[],"/home/pooya/Code/nitro/src/build/plugins/sourcemap-min.ts",[],[],"/home/pooya/Code/nitro/src/build/plugins/virtual.ts",[],[],"/home/pooya/Code/nitro/src/build/plugins.ts",[],[],"/home/pooya/Code/nitro/src/build/prepare.ts",[],[],"/home/pooya/Code/nitro/src/build/rolldown/build.ts",[],[],"/home/pooya/Code/nitro/src/build/rolldown/config.ts",[],[],"/home/pooya/Code/nitro/src/build/rolldown/dev.ts",[],[],"/home/pooya/Code/nitro/src/build/rolldown/prod.ts",[],[],"/home/pooya/Code/nitro/src/build/rollup/build.ts",[],[],"/home/pooya/Code/nitro/src/build/rollup/config.ts",[],[],"/home/pooya/Code/nitro/src/build/rollup/dev.ts",[],[],"/home/pooya/Code/nitro/src/build/rollup/error.ts",[],[],"/home/pooya/Code/nitro/src/build/rollup/prod.ts",[],[],"/home/pooya/Code/nitro/src/build/types.ts",[],[],"/home/pooya/Code/nitro/src/build/virtual/_all.ts",[],[],"/home/pooya/Code/nitro/src/build/virtual/database.ts",[],[],"/home/pooya/Code/nitro/src/build/virtual/error-handler.ts",[],[],"/home/pooya/Code/nitro/src/build/virtual/feature-flags.ts",[],[],"/home/pooya/Code/nitro/src/build/virtual/plugins.ts",[],[],"/home/pooya/Code/nitro/src/build/virtual/polyfills.ts",[],[],"/home/pooya/Code/nitro/src/build/virtual/public-assets.ts",[],[],"/home/pooya/Code/nitro/src/build/virtual/renderer-template.ts",[],[],"/home/pooya/Code/nitro/src/build/virtual/routing-meta.ts",[],[],"/home/pooya/Code/nitro/src/build/virtual/routing.ts",[],[],"/home/pooya/Code/nitro/src/build/virtual/runtime-config.ts",[],[],"/home/pooya/Code/nitro/src/build/virtual/server-assets.ts",[],[],"/home/pooya/Code/nitro/src/build/virtual/storage.ts",[],[],"/home/pooya/Code/nitro/src/build/virtual/tasks.ts",[],[],"/home/pooya/Code/nitro/src/build/vite/build.ts",[],[],"/home/pooya/Code/nitro/src/build/vite/bundler.ts",[],[],"/home/pooya/Code/nitro/src/build/vite/dev.ts",[],[],"/home/pooya/Code/nitro/src/build/vite/env.ts",[],[],"/home/pooya/Code/nitro/src/build/vite/plugin.ts",[],[],"/home/pooya/Code/nitro/src/build/vite/preview.ts",[],[],"/home/pooya/Code/nitro/src/build/vite/prod.ts",[],[],"/home/pooya/Code/nitro/src/build/vite/types.ts",[],[],"/home/pooya/Code/nitro/src/builder.ts",[],[],"/home/pooya/Code/nitro/src/cli/commands/build.ts",[],[],"/home/pooya/Code/nitro/src/cli/commands/dev.ts",[],[],"/home/pooya/Code/nitro/src/cli/commands/prepare.ts",[],[],"/home/pooya/Code/nitro/src/cli/commands/task/index.ts",[],[],"/home/pooya/Code/nitro/src/cli/commands/task/list.ts",[],[],"/home/pooya/Code/nitro/src/cli/commands/task/run.ts",[],["2417"],"/home/pooya/Code/nitro/src/cli/common.ts",[],[],"/home/pooya/Code/nitro/src/cli/index.ts",[],[],"/home/pooya/Code/nitro/src/config/defaults.ts",[],[],"/home/pooya/Code/nitro/src/config/loader.ts",[],[],"/home/pooya/Code/nitro/src/config/resolvers/assets.ts",[],[],"/home/pooya/Code/nitro/src/config/resolvers/builder.ts",[],[],"/home/pooya/Code/nitro/src/config/resolvers/compatibility.ts",[],[],"/home/pooya/Code/nitro/src/config/resolvers/database.ts",[],[],"/home/pooya/Code/nitro/src/config/resolvers/error.ts",[],[],"/home/pooya/Code/nitro/src/config/resolvers/export-conditions.ts",[],[],"/home/pooya/Code/nitro/src/config/resolvers/imports.ts",[],[],"/home/pooya/Code/nitro/src/config/resolvers/open-api.ts",[],[],"/home/pooya/Code/nitro/src/config/resolvers/paths.ts",[],[],"/home/pooya/Code/nitro/src/config/resolvers/route-rules.ts",[],[],"/home/pooya/Code/nitro/src/config/resolvers/runtime-config.ts",[],[],"/home/pooya/Code/nitro/src/config/resolvers/storage.ts",[],[],"/home/pooya/Code/nitro/src/config/resolvers/tsconfig.ts",[],[],"/home/pooya/Code/nitro/src/config/resolvers/unenv.ts",[],[],"/home/pooya/Code/nitro/src/config/resolvers/url.ts",[],[],"/home/pooya/Code/nitro/src/config/update.ts",[],[],"/home/pooya/Code/nitro/src/dev/app.ts",[],[],"/home/pooya/Code/nitro/src/dev/server.ts",[],[],"/home/pooya/Code/nitro/src/dev/vfs.ts",[],[],"/home/pooya/Code/nitro/src/global.ts",[],[],"/home/pooya/Code/nitro/src/module.ts",[],[],"/home/pooya/Code/nitro/src/nitro.ts",[],[],"/home/pooya/Code/nitro/src/prerender/prerender.ts",[],[],"/home/pooya/Code/nitro/src/prerender/utils.ts",[],[],"/home/pooya/Code/nitro/src/presets/_nitro/base-worker.ts",[],[],"/home/pooya/Code/nitro/src/presets/_nitro/nitro-dev.ts",[],[],"/home/pooya/Code/nitro/src/presets/_nitro/nitro-prerender.ts",[],[],"/home/pooya/Code/nitro/src/presets/_nitro/preset.ts",[],[],"/home/pooya/Code/nitro/src/presets/_nitro/runtime/nitro-dev.ts",[],["2418"],"/home/pooya/Code/nitro/src/presets/_nitro/runtime/nitro-prerenderer.ts",[],[],"/home/pooya/Code/nitro/src/presets/_nitro/runtime/service-worker.ts",[],[],"/home/pooya/Code/nitro/src/presets/_resolve.ts",[],[],"/home/pooya/Code/nitro/src/presets/_static/preset.ts",[],[],"/home/pooya/Code/nitro/src/presets/_utils/fs.ts",[],[],"/home/pooya/Code/nitro/src/presets/_utils/preset.ts",[],[],"/home/pooya/Code/nitro/src/presets/alwaysdata/preset.ts",[],[],"/home/pooya/Code/nitro/src/presets/aws-amplify/preset.ts",[],[],"/home/pooya/Code/nitro/src/presets/aws-amplify/runtime/aws-amplify.ts",[],[],"/home/pooya/Code/nitro/src/presets/aws-amplify/types.ts",[],[],"/home/pooya/Code/nitro/src/presets/aws-amplify/utils.ts",[],[],"/home/pooya/Code/nitro/src/presets/aws-lambda/preset.ts",[],[],"/home/pooya/Code/nitro/src/presets/aws-lambda/runtime/_utils.ts",[],[],"/home/pooya/Code/nitro/src/presets/aws-lambda/runtime/aws-lambda-streaming.ts",[],[],"/home/pooya/Code/nitro/src/presets/aws-lambda/runtime/aws-lambda.ts",[],[],"/home/pooya/Code/nitro/src/presets/aws-lambda/types.ts",[],[],"/home/pooya/Code/nitro/src/presets/azure/preset.ts",[],[],"/home/pooya/Code/nitro/src/presets/azure/runtime/_utils.ts",[],[],"/home/pooya/Code/nitro/src/presets/azure/runtime/azure-swa.ts",[],[],"/home/pooya/Code/nitro/src/presets/azure/types.ts",[],[],"/home/pooya/Code/nitro/src/presets/azure/utils.ts",[],[],"/home/pooya/Code/nitro/src/presets/bun/preset.ts",[],[],"/home/pooya/Code/nitro/src/presets/bun/runtime/bun.ts",[],[],"/home/pooya/Code/nitro/src/presets/cleavr/preset.ts",[],[],"/home/pooya/Code/nitro/src/presets/cloudflare/dev.ts",[],[],"/home/pooya/Code/nitro/src/presets/cloudflare/entry-exports.ts",[],[],"/home/pooya/Code/nitro/src/presets/cloudflare/preset.ts",[],[],"/home/pooya/Code/nitro/src/presets/cloudflare/runtime/_module-handler.ts",[],[],"/home/pooya/Code/nitro/src/presets/cloudflare/runtime/cloudflare-durable.ts",[],[],"/home/pooya/Code/nitro/src/presets/cloudflare/runtime/cloudflare-module.ts",[],[],"/home/pooya/Code/nitro/src/presets/cloudflare/runtime/cloudflare-pages.ts",[],[],"/home/pooya/Code/nitro/src/presets/cloudflare/runtime/plugin.dev.ts",[],[],"/home/pooya/Code/nitro/src/presets/cloudflare/runtime/shims/workers.dev.mjs",[],[],"/home/pooya/Code/nitro/src/presets/cloudflare/types.ts",[],[],"/home/pooya/Code/nitro/src/presets/cloudflare/unenv/node-compat.ts",[],[],"/home/pooya/Code/nitro/src/presets/cloudflare/unenv/preset.ts",[],[],"/home/pooya/Code/nitro/src/presets/cloudflare/utils.ts",[],[],"/home/pooya/Code/nitro/src/presets/cloudflare/wrangler/_utils.ts",[],[],"/home/pooya/Code/nitro/src/presets/cloudflare/wrangler/config.ts",[],[],"/home/pooya/Code/nitro/src/presets/cloudflare/wrangler/environment.ts",[],[],"/home/pooya/Code/nitro/src/presets/deno/preset.ts",[],[],"/home/pooya/Code/nitro/src/presets/deno/runtime/deno-deploy.ts",[],[],"/home/pooya/Code/nitro/src/presets/deno/runtime/deno-server.ts",[],[],"/home/pooya/Code/nitro/src/presets/deno/unenv/node-compat.ts",[],[],"/home/pooya/Code/nitro/src/presets/deno/unenv/preset.ts",[],[],"/home/pooya/Code/nitro/src/presets/digitalocean/preset.ts",[],[],"/home/pooya/Code/nitro/src/presets/firebase/preset.ts",[],[],"/home/pooya/Code/nitro/src/presets/firebase/types.ts",[],[],"/home/pooya/Code/nitro/src/presets/flightcontrol/preset.ts",[],[],"/home/pooya/Code/nitro/src/presets/genezio/preset.ts",[],[],"/home/pooya/Code/nitro/src/presets/heroku/preset.ts",[],[],"/home/pooya/Code/nitro/src/presets/iis/preset.ts",[],[],"/home/pooya/Code/nitro/src/presets/iis/utils.ts",[],[],"/home/pooya/Code/nitro/src/presets/index.ts",[],[],"/home/pooya/Code/nitro/src/presets/koyeb/preset.ts",[],[],"/home/pooya/Code/nitro/src/presets/netlify/preset.ts",[],[],"/home/pooya/Code/nitro/src/presets/netlify/runtime/netlify-edge.ts",[],[],"/home/pooya/Code/nitro/src/presets/netlify/runtime/netlify.ts",[],[],"/home/pooya/Code/nitro/src/presets/netlify/types.ts",[],[],"/home/pooya/Code/nitro/src/presets/netlify/utils.ts",[],[],"/home/pooya/Code/nitro/src/presets/node/cluster.ts",[],[],"/home/pooya/Code/nitro/src/presets/node/preset.ts",[],[],"/home/pooya/Code/nitro/src/presets/node/runtime/node-cluster.ts",[],[],"/home/pooya/Code/nitro/src/presets/node/runtime/node-middleware.ts",[],[],"/home/pooya/Code/nitro/src/presets/node/runtime/node-server.ts",[],[],"/home/pooya/Code/nitro/src/presets/platform.sh/preset.ts",[],[],"/home/pooya/Code/nitro/src/presets/render.com/preset.ts",[],[],"/home/pooya/Code/nitro/src/presets/standard/preset.ts",[],[],"/home/pooya/Code/nitro/src/presets/standard/runtime/server.ts",[],[],"/home/pooya/Code/nitro/src/presets/stormkit/preset.ts",[],[],"/home/pooya/Code/nitro/src/presets/stormkit/runtime/stormkit.ts",[],[],"/home/pooya/Code/nitro/src/presets/vercel/preset.ts",[],[],"/home/pooya/Code/nitro/src/presets/vercel/runtime/isr.ts",[],[],"/home/pooya/Code/nitro/src/presets/vercel/runtime/vercel.node.ts",[],[],"/home/pooya/Code/nitro/src/presets/vercel/runtime/vercel.web.ts",[],[],"/home/pooya/Code/nitro/src/presets/vercel/types.ts",[],[],"/home/pooya/Code/nitro/src/presets/vercel/utils.ts",[],[],"/home/pooya/Code/nitro/src/presets/winterjs/preset.ts",[],[],"/home/pooya/Code/nitro/src/presets/winterjs/runtime/winterjs.ts",[],[],"/home/pooya/Code/nitro/src/presets/zeabur/preset.ts",[],[],"/home/pooya/Code/nitro/src/presets/zeabur/runtime/zeabur.ts",[],[],"/home/pooya/Code/nitro/src/presets/zerops/preset.ts",[],[],"/home/pooya/Code/nitro/src/routing.ts",[],[],"/home/pooya/Code/nitro/src/runner/node.ts",[],["2419"],"/home/pooya/Code/nitro/src/runner/proxy.ts",[],[],"/home/pooya/Code/nitro/src/runtime/app.ts",[],[],"/home/pooya/Code/nitro/src/runtime/cache.ts",[],[],"/home/pooya/Code/nitro/src/runtime/config.ts",[],[],"/home/pooya/Code/nitro/src/runtime/context.ts",[],[],"/home/pooya/Code/nitro/src/runtime/database.ts",[],[],"/home/pooya/Code/nitro/src/runtime/internal/app.ts",[],[],"/home/pooya/Code/nitro/src/runtime/internal/cache.ts",[],[],"/home/pooya/Code/nitro/src/runtime/internal/context.ts",[],[],"/home/pooya/Code/nitro/src/runtime/internal/database.ts",[],[],"/home/pooya/Code/nitro/src/runtime/internal/empty.ts",[],[],"/home/pooya/Code/nitro/src/runtime/internal/error/dev.ts",[],[],"/home/pooya/Code/nitro/src/runtime/internal/error/hooks.ts",[],[],"/home/pooya/Code/nitro/src/runtime/internal/error/prod.ts",[],[],"/home/pooya/Code/nitro/src/runtime/internal/error/utils.ts",[],[],"/home/pooya/Code/nitro/src/runtime/internal/meta.ts",[],[],"/home/pooya/Code/nitro/src/runtime/internal/plugin.ts",[],[],"/home/pooya/Code/nitro/src/runtime/internal/route-rules.ts",[],[],"/home/pooya/Code/nitro/src/runtime/internal/routes/dev-tasks.ts",[],[],"/home/pooya/Code/nitro/src/runtime/internal/routes/openapi.ts",[],[],"/home/pooya/Code/nitro/src/runtime/internal/routes/renderer-template.dev.ts",[],[],"/home/pooya/Code/nitro/src/runtime/internal/routes/renderer-template.ts",[],[],"/home/pooya/Code/nitro/src/runtime/internal/routes/scalar.ts",[],[],"/home/pooya/Code/nitro/src/runtime/internal/routes/swagger.ts",[],[],"/home/pooya/Code/nitro/src/runtime/internal/runtime-config.ts",[],[],"/home/pooya/Code/nitro/src/runtime/internal/static.ts",[],[],"/home/pooya/Code/nitro/src/runtime/internal/storage.ts",[],[],"/home/pooya/Code/nitro/src/runtime/internal/task.ts",[],[],"/home/pooya/Code/nitro/src/runtime/internal/vite/dev-entry.mjs",[],[],"/home/pooya/Code/nitro/src/runtime/internal/vite/node-runner.mjs",[],["2420"],"/home/pooya/Code/nitro/src/runtime/internal/vite/ssr-renderer.mjs",[],[],"/home/pooya/Code/nitro/src/runtime/meta.ts",[],[],"/home/pooya/Code/nitro/src/runtime/nitro.ts",[],[],"/home/pooya/Code/nitro/src/runtime/runtime-config.ts",[],[],"/home/pooya/Code/nitro/src/runtime/storage.ts",[],[],"/home/pooya/Code/nitro/src/runtime/task.ts",[],[],"/home/pooya/Code/nitro/src/runtime/virtual/_runtime_warn.ts",[],[],"/home/pooya/Code/nitro/src/runtime/virtual/database.ts",[],[],"/home/pooya/Code/nitro/src/runtime/virtual/error-handler.ts",[],[],"/home/pooya/Code/nitro/src/runtime/virtual/feature-flags.ts",[],[],"/home/pooya/Code/nitro/src/runtime/virtual/plugins.ts",[],[],"/home/pooya/Code/nitro/src/runtime/virtual/polyfills.ts",[],[],"/home/pooya/Code/nitro/src/runtime/virtual/public-assets.ts",[],[],"/home/pooya/Code/nitro/src/runtime/virtual/renderer-template.ts",[],[],"/home/pooya/Code/nitro/src/runtime/virtual/routing-meta.ts",[],[],"/home/pooya/Code/nitro/src/runtime/virtual/routing.ts",[],[],"/home/pooya/Code/nitro/src/runtime/virtual/runtime-config.ts",[],[],"/home/pooya/Code/nitro/src/runtime/virtual/server-assets.ts",[],[],"/home/pooya/Code/nitro/src/runtime/virtual/storage.ts",[],[],"/home/pooya/Code/nitro/src/runtime/virtual/tasks.ts",[],[],"/home/pooya/Code/nitro/src/runtime/vite.ts",[],[],"/home/pooya/Code/nitro/src/scan.ts",[],[],"/home/pooya/Code/nitro/src/task.ts",[],[],"/home/pooya/Code/nitro/src/types/_utils.ts",[],["2421"],"/home/pooya/Code/nitro/src/types/build.ts",[],[],"/home/pooya/Code/nitro/src/types/config.ts",[],[],"/home/pooya/Code/nitro/src/types/fetch/_match.ts",[],[],"/home/pooya/Code/nitro/src/types/fetch/_serialize.ts",[],["2422"],"/home/pooya/Code/nitro/src/types/fetch/fetch.ts",[],["2423"],"/home/pooya/Code/nitro/src/types/fetch/index.ts",[],[],"/home/pooya/Code/nitro/src/types/global.ts",[],["2424"],"/home/pooya/Code/nitro/src/types/h3.ts",[],["2425"],"/home/pooya/Code/nitro/src/types/handler.ts",[],[],"/home/pooya/Code/nitro/src/types/hooks.ts",[],[],"/home/pooya/Code/nitro/src/types/index.ts",[],[],"/home/pooya/Code/nitro/src/types/module.ts",[],[],"/home/pooya/Code/nitro/src/types/nitro.ts",[],[],"/home/pooya/Code/nitro/src/types/openapi-ts.ts",[],[],"/home/pooya/Code/nitro/src/types/openapi.ts",[],[],"/home/pooya/Code/nitro/src/types/prerender.ts",[],[],"/home/pooya/Code/nitro/src/types/preset.ts",[],[],"/home/pooya/Code/nitro/src/types/route-rules.ts",[],[],"/home/pooya/Code/nitro/src/types/runner.ts",[],[],"/home/pooya/Code/nitro/src/types/runtime/asset.ts",[],[],"/home/pooya/Code/nitro/src/types/runtime/cache.ts",[],[],"/home/pooya/Code/nitro/src/types/runtime/index.ts",[],[],"/home/pooya/Code/nitro/src/types/runtime/nitro.ts",[],[],"/home/pooya/Code/nitro/src/types/runtime/task.ts",[],[],"/home/pooya/Code/nitro/src/types/srvx.ts",[],[],"/home/pooya/Code/nitro/src/utils/compress.ts",[],[],"/home/pooya/Code/nitro/src/utils/dep.ts",[],[],"/home/pooya/Code/nitro/src/utils/fs-tree.ts",[],[],"/home/pooya/Code/nitro/src/utils/fs.ts",[],["2426"],"/home/pooya/Code/nitro/src/utils/parallel.ts",[],[],"/home/pooya/Code/nitro/src/utils/regex.ts",[],[],"/home/pooya/Code/nitro/src/vite.ts",[],[],"/home/pooya/Code/nitro/test/examples.test.ts",[],[],"/home/pooya/Code/nitro/test/fixture/error.ts",[],[],"/home/pooya/Code/nitro/test/fixture/exports.cloudflare.ts",[],[],"/home/pooya/Code/nitro/test/fixture/nitro.config.ts",[],[],"/home/pooya/Code/nitro/test/fixture/public/foo.js",[],[],"/home/pooya/Code/nitro/test/fixture/server/files/sqlts.sql.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/middleware/_ignored.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/plugins/errors.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/plugins/vary.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/(route-group)/route-group.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/500.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/api/_ignored.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/api/cached.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/api/db.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/api/echo.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/api/error.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/api/errors.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/api/headers.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/api/hello.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/api/hey/index.get.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/api/kebab.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/api/meta/test.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/api/methods/foo.get.get.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/api/methods/get.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/api/param/[test-id].ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/api/storage/item.get.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/api/storage/item.put.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/api/upload.post.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/api/wildcard/[...param].ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/assets/[id].ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/assets/all.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/assets/md.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/config.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/context.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/env/index.dev.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/env/index.get.prod.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/error-stack.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/fetch.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/file.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/icon.png.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/imports.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/json-string.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/jsx.tsx",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/modules.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/node-compat.ts",[],["2427","2428","2429"],"/home/pooya/Code/nitro/test/fixture/server/routes/prerender-custom.html.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/prerender.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/raw.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/replace.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/rules/[...slug].ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/static-flags.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/stream.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/tasks/[...name].ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/wait-until.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/wasm/dynamic-import.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/routes/wasm/static-import.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/tasks/db/migrate.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/tasks/test.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/utils/foo/bar/test.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/utils/foo/test.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server/utils/test.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server.config.ts",[],[],"/home/pooya/Code/nitro/test/fixture/server.ts",[],[],"/home/pooya/Code/nitro/test/fixture/vite.config.ts",[],[],"/home/pooya/Code/nitro/test/minimal/minimal.test.ts",[],[],"/home/pooya/Code/nitro/test/minimal/nitro.config.ts",[],[],"/home/pooya/Code/nitro/test/minimal/server.ts",[],[],"/home/pooya/Code/nitro/test/minimal/vite.config.mjs",[],[],"/home/pooya/Code/nitro/test/presets/aws-lambda.test.ts",[],[],"/home/pooya/Code/nitro/test/presets/azure-swa.test.ts",[],[],"/home/pooya/Code/nitro/test/presets/bun.test.ts",[],[],"/home/pooya/Code/nitro/test/presets/cloudflare-module.test.ts",[],[],"/home/pooya/Code/nitro/test/presets/cloudflare-pages.test.ts",[],[],"/home/pooya/Code/nitro/test/presets/deno-server.test.ts",[],[],"/home/pooya/Code/nitro/test/presets/netlify.test.ts",[],[],"/home/pooya/Code/nitro/test/presets/nitro-dev.test.ts",[],[],"/home/pooya/Code/nitro/test/presets/node.test.ts",[],[],"/home/pooya/Code/nitro/test/presets/standard.test.ts",[],[],"/home/pooya/Code/nitro/test/presets/static.test.ts",[],[],"/home/pooya/Code/nitro/test/presets/vercel.test.ts",[],[],"/home/pooya/Code/nitro/test/presets/winterjs.test.ts",[],[],"/home/pooya/Code/nitro/test/scripts/gen-fixture-types.ts",[],[],"/home/pooya/Code/nitro/test/tests.ts",[],[],"/home/pooya/Code/nitro/test/unit/azure.utils.test.ts",[],[],"/home/pooya/Code/nitro/test/unit/runtime-config.env.test.ts",[],[],"/home/pooya/Code/nitro/test/unit/runtime-config.test.ts",[],[],"/home/pooya/Code/nitro/test/unit/virtual.test.ts",[],[],"/home/pooya/Code/nitro/vitest.config.ts",[],[],{"ruleId":"2430","severity":2,"message":"2431","line":139,"column":1,"nodeType":"2432","messageId":"2433","endLine":139,"endColumn":7,"suggestions":"2434","suppressions":"2435"},{"ruleId":"2436","severity":2,"message":"2437","line":53,"column":28,"nodeType":"2432","messageId":"2438","endLine":53,"endColumn":52,"suppressions":"2439"},{"ruleId":"2440","severity":2,"message":"2441","line":13,"column":23,"nodeType":"2442","messageId":"2443","endLine":13,"endColumn":30,"suggestions":"2444","suppressions":"2445"},{"ruleId":"2430","severity":2,"message":"2431","line":15,"column":1,"nodeType":"2432","messageId":"2433","endLine":15,"endColumn":7,"suggestions":"2446","suppressions":"2447"},{"ruleId":"2448","severity":2,"message":"2449","line":2,"column":8,"nodeType":"2450","messageId":"2451","endLine":2,"endColumn":10,"fix":"2452","suppressions":"2453"},{"ruleId":"2430","severity":2,"message":"2454","line":143,"column":8,"nodeType":"2455","messageId":"2456","endLine":143,"endColumn":13,"suppressions":"2457"},{"ruleId":"2458","severity":2,"message":"2459","line":146,"column":3,"nodeType":"2432","messageId":"2460","endLine":146,"endColumn":18,"suppressions":"2461"},{"ruleId":"2462","severity":2,"message":"2463","line":16,"column":21,"nodeType":"2442","messageId":"2464","endLine":16,"endColumn":45,"suppressions":"2465"},{"ruleId":"2458","severity":2,"message":"2459","line":55,"column":7,"nodeType":"2432","messageId":"2460","endLine":55,"endColumn":22,"suppressions":"2466"},{"ruleId":"2430","severity":2,"message":"2454","line":32,"column":4,"nodeType":"2455","messageId":"2456","endLine":32,"endColumn":9,"suppressions":"2467"},{"ruleId":"2468","severity":2,"message":"2469","line":124,"column":45,"nodeType":"2470","messageId":"2471","endLine":124,"endColumn":77,"fix":"2472","suppressions":"2473"},{"ruleId":"2430","severity":2,"message":"2474","line":185,"column":1,"nodeType":"2432","messageId":"2433","endLine":185,"endColumn":9,"suggestions":"2475","suppressions":"2476"},{"ruleId":"2477","severity":2,"message":"2478","line":16,"column":47,"nodeType":"2455","messageId":"2479","endLine":16,"endColumn":55,"suppressions":"2480"},{"ruleId":"2477","severity":2,"message":"2478","line":13,"column":37,"nodeType":"2455","messageId":"2479","endLine":13,"endColumn":45,"suppressions":"2481"},{"ruleId":"2448","severity":2,"message":"2449","line":116,"column":13,"nodeType":"2450","messageId":"2451","endLine":116,"endColumn":15,"fix":"2482","suppressions":"2483"},{"ruleId":"2448","severity":2,"message":"2449","line":22,"column":13,"nodeType":"2450","messageId":"2451","endLine":22,"endColumn":15,"fix":"2484","suppressions":"2485"},{"ruleId":"2448","severity":2,"message":"2449","line":28,"column":13,"nodeType":"2450","messageId":"2451","endLine":28,"endColumn":15,"fix":"2486","suppressions":"2487"},{"ruleId":"2462","severity":2,"message":"2463","line":32,"column":7,"nodeType":"2442","messageId":"2464","endLine":32,"endColumn":19,"suppressions":"2488"},{"ruleId":"2489","severity":2,"message":"2490","line":8,"column":41,"nodeType":"2455","messageId":"2491","endLine":8,"endColumn":47,"fix":"2492","suppressions":"2493"},{"ruleId":"2489","severity":2,"message":"2490","line":10,"column":50,"nodeType":"2455","messageId":"2491","endLine":10,"endColumn":56,"fix":"2494","suppressions":"2495"},{"ruleId":"2489","severity":2,"message":"2490","line":12,"column":53,"nodeType":"2455","messageId":"2491","endLine":12,"endColumn":59,"fix":"2496","suppressions":"2497"},"unicorn/prefer-top-level-await","Prefer top-level await over an async function `main` call.","CallExpression","identifier",["2498"],["2499"],"prefer-spread","Use the spread operator instead of '.apply()'.","preferSpread",["2500"],"unicorn/text-encoding-identifier-case","Prefer `utf-8` over `UTF-8`.","Literal","text-encoding-identifier/error",["2501"],["2502"],["2503"],["2504"],"unicorn/require-module-specifiers","export statement without specifiers is not allowed.","ExportNamedDeclaration","error",{"range":"2505","text":"2506"},["2507"],"Prefer top-level await over using a promise chain.","Identifier","promise",["2508"],"unicorn/no-process-exit","Only use `process.exit()` in CLI apps. Throw an error instead.","no-process-exit",["2509"],"no-control-regex","Unexpected control character(s) in regular expression: \\x00.","unexpected",["2510"],["2511"],["2512"],"unicorn/no-nested-ternary","Nested ternary expression should be parenthesized.","ConditionalExpression","should-parenthesized",{"range":"2513","text":"2514"},["2515"],"Prefer top-level await over an async function `reload` call.",["2516"],["2517"],"@typescript-eslint/no-unsafe-function-type","The `Function` type accepts any function-like value.\nPrefer explicitly defining any function parameters and return type.","bannedFunctionType",["2518"],["2519"],{"range":"2520","text":"2506"},["2521"],{"range":"2522","text":"2506"},["2523"],{"range":"2524","text":"2506"},["2525"],["2526"],"unicorn/prefer-global-this","Prefer `globalThis` over `global`.","prefer-global-this/error",{"range":"2527","text":"2528"},["2529"],{"range":"2530","text":"2528"},["2531"],{"range":"2532","text":"2528"},["2533"],{"messageId":"2534","fix":"2535","data":"2536","desc":"2537"},{"kind":"2538","justification":"2506"},{"kind":"2538","justification":"2506"},{"messageId":"2539","fix":"2540","data":"2541","desc":"2542"},{"kind":"2538","justification":"2506"},{"messageId":"2534","fix":"2543","data":"2544","desc":"2537"},{"kind":"2538","justification":"2506"},[62,72],"",{"kind":"2538","justification":"2506"},{"kind":"2538","justification":"2506"},{"kind":"2538","justification":"2506"},{"kind":"2538","justification":"2506"},{"kind":"2538","justification":"2506"},{"kind":"2538","justification":"2506"},[3173,3205],"(this.ready ? \"ready\" : \"pending\")",{"kind":"2538","justification":"2506"},{"messageId":"2534","fix":"2545","data":"2546","desc":"2537"},{"kind":"2538","justification":"2506"},{"kind":"2538","justification":"2506"},{"kind":"2538","justification":"2506"},[3539,3554],{"kind":"2538","justification":"2506"},[484,499],{"kind":"2538","justification":"2506"},[830,845],{"kind":"2538","justification":"2506"},{"kind":"2538","justification":"2506"},[256,262],"globalThis",{"kind":"2538","justification":"2506"},[372,378],{"kind":"2538","justification":"2506"},[498,504],{"kind":"2538","justification":"2506"},"add-await",{"range":"2547","text":"2548"},{"name":"2549"},"Insert `await`.","directive","text-encoding-identifier/suggestion",{"range":"2550","text":"2551"},{"value":"2552","replacement":"2551"},"Replace `UTF-8` with `utf-8`.",{"range":"2553","text":"2548"},{"name":"2549"},{"range":"2554","text":"2548"},{"name":"2555"},[4351,4351],"await ","main",[530,535],"utf-8","UTF-8",[414,414],[4829,4829],"reload"] ================================================ FILE: .gitattributes ================================================ *.txt text eol=lf ================================================ FILE: .github/CODEOWNERS ================================================ * @pi0 ================================================ FILE: .github/ISSUE_TEMPLATE/bug-report.yml ================================================ name: "\U0001F41E Bug report" description: Report an issue or bug labels: ["pending triage"] body: - type: markdown attributes: value: | If bug-report is related to Nuxt framework, please open an issue directly there: https://github.com/nuxt/nuxt/issues/new/choose Please use one of the templates below to create a minimal reproduction : 👉 https://stackblitz.com/github/nitrojs/nitro/tree/starter 👉 https://codesandbox.io/p/github/nitrojs/nitro/starter - type: textarea id: bug-env attributes: label: Environment description: Nitro and Node.js version possibly nitro config can be helpful to investigate issues placeholder: Environment validations: required: true - type: textarea id: reproduction attributes: label: Reproduction description: Please provide a link to a repo that can reproduce the problem you ran into. A **minimal reproduction** is required unless you are absolutely sure that the issue is obvious and the provided information is enough to understand the problem. If a report is vague (e.g. just a generic error message) and has no reproduction, it will receive a "need reproduction" label. If no reproduction is provided we might close it. placeholder: Reproduction validations: required: true - type: textarea id: bug-description attributes: label: Describe the bug description: A clear and concise description of what the bug is. If you intend to submit a PR for this issue, tell us in the description. Thanks! placeholder: Bug description validations: required: true - type: textarea id: additional attributes: label: Additional context description: If applicable, add any other context about the problem here - type: textarea id: logs attributes: label: Logs description: | Optional if provided reproduction. Please try not to insert an image but copy paste the log text. render: sh ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: true contact_links: - name: 📚 Nitro Documentation url: https://nitro.build/ about: Check the documentation for usage of Nuxt 3 - name: 💬 Discussions url: https://github.com/nitrojs/nitro/discussions about: Use discussions if you have an idea for improvement and asking questions - name: 🔗 Nuxt related Issues url: https://github.com/nuxt/nuxt/issues/new/choose about: Please open an issue in nuxt/nuxt to discuss ================================================ FILE: .github/ISSUE_TEMPLATE/feature-request.yml ================================================ name: "🚀 Feature request" description: Suggest a feature or improvement labels: ["pending triage"] body: - type: markdown attributes: value: | Thank you for taking the time to fill out this feature request! - type: textarea id: feature-description attributes: label: Describe the feature description: A clear and concise description of what you think would be a helpful addition, including the possible use cases and alternatives you have considered. If you have a working prototype or module that implements it, please include a link. placeholder: Feature description validations: required: true - type: checkboxes id: additional-info attributes: label: Additional information description: Additional information that helps us decide how to proceed. options: - label: Would you be willing to help implement this feature? ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ ### 🔗 Linked issue ### ❓ Type of change - [ ] 📖 Documentation (updates to the documentation, readme, or JSdoc annotations) - [ ] 🐞 Bug fix (a non-breaking change that fixes an issue) - [ ] 👌 Enhancement (improving an existing functionality like performance) - [ ] ✨ New feature (a non-breaking change that adds functionality) - [ ] 🧹 Chore (updates to the build process or auxiliary tools and libraries) - [ ] ⚠️ Breaking change (fix or feature that would cause existing functionality to change) ### 📚 Description ### 📝 Checklist - [ ] I have linked an issue or discussion. - [ ] I have updated the documentation accordingly. ================================================ FILE: .github/agents/maintainer.agent.md ================================================ --- name: Maintainer description: > Nitro project maintainer agent. Handles bug fixes, feature implementation, code review, and contributions following project conventions. Understands the build system, runtime constraints, deployment presets, and testing strategy. tools: - "*" --- You are a maintainer of the Nitro project. Follow all instructions in [AGENTS.md](../../AGENTS.md) strictly. For deeper architectural context, refer to the `.agents/` directory: - `.agents/architecture.md` — Core architecture, build system, config resolution, virtual modules, runtime internals. - `.agents/presets.md` — Deployment presets, preset structure, resolution logic. - `.agents/testing.md` — Test structure, adding regression tests, running tests. - `.agents/vite.md` — Vite build system, plugin architecture, dev server, HMR. - `.agents/docs.md` — Documentation conventions, H3 v2 API patterns. H3 v2 docs are at `node_modules/h3/skills/h3/docs/TOC.md`. ## Key principles - Prefer minimal, targeted changes over large refactors. - Code in `src/runtime/` must be runtime-agnostic (Web APIs, no Node.js-specific APIs). - Use `pathe` instead of `node:path`. - Use existing UnJS utilities (`defu`, `consola`, `unstorage`) before adding new packages. - Bug fixes MUST include a failing regression test first. - Always run `pnpm format` and `pnpm typecheck` after changes. - Use semantic commit messages with scope (e.g., `fix(runtime): ...`). ================================================ FILE: .github/codecov.yml ================================================ coverage: status: project: default: threshold: 50% ================================================ FILE: .github/copilot-instructions.md ================================================ @../AGENTS.md Refer to [AGENTS.md](../AGENTS.md) for project instructions. ================================================ FILE: .github/workflows/autofix.yml ================================================ name: autofix.ci # needed to securely identify the workflow on: pull_request: push: branches: - main permissions: contents: read jobs: autofix: runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v6 - run: npm i -g --force corepack && corepack enable - uses: actions/setup-node@v6 with: node-version: lts/* cache: "pnpm" - run: pnpm install - run: pnpm stub - run: pnpm gen-presets - name: Fix lint issues run: npm run format - uses: autofix-ci/action@7a166d7532b277f34e16238930461bf77f9d7ed8 with: commit-message: "chore: apply automated updates" ================================================ FILE: .github/workflows/ci.yml ================================================ name: ci on: push: { branches: [main] } pull_request: { branches: [main] } jobs: tests-checks: runs-on: ${{ matrix.os }} timeout-minutes: 10 strategy: matrix: os: [ubuntu-latest] steps: - uses: actions/checkout@v6 - run: npm i -g --force corepack && corepack enable - uses: actions/setup-node@v6 with: { node-version: lts/*, cache: pnpm } - run: pnpm install - run: pnpm stub && pnpm lint - run: pnpm typecheck - run: pnpm vitest run test/unit - run: pnpm vitest run test/minimal tests-rollup: runs-on: ${{ matrix.os }} timeout-minutes: 10 strategy: matrix: os: [ubuntu-latest, windows-latest] steps: - uses: actions/checkout@v6 - run: npm i -g --force corepack && corepack enable - uses: actions/setup-node@v6 with: { node-version: lts/*, cache: pnpm } - uses: oven-sh/setup-bun@v2 if: ${{ matrix.os != 'windows-latest' }} with: { bun-version: latest } - uses: denoland/setup-deno@v1 if: ${{ matrix.os != 'windows-latest' }} with: { deno-version: 2.7.4 } - run: node scripts/vite7.ts - run: pnpm install - run: pnpm stub && pnpm lint if: ${{ matrix.os != 'windows-latest' }} - run: pnpm build - run: pnpm vitest run test/examples env: { NITRO_BUILDER: rollup, NITRO_VITE_PKG: vite7 } - run: pnpm vitest run test/presets env: { NITRO_BUILDER: rollup, NITRO_VITE_PKG: vite7 } tests-rolldown: runs-on: ${{ matrix.os }} timeout-minutes: 10 strategy: matrix: os: [ubuntu-latest, windows-latest] steps: - uses: actions/checkout@v6 - run: npm i -g --force corepack && corepack enable - uses: actions/setup-node@v6 with: { node-version: lts/*, cache: pnpm } - uses: oven-sh/setup-bun@v2 if: ${{ matrix.os != 'windows-latest' }} with: { bun-version: latest } - uses: denoland/setup-deno@v1 if: ${{ matrix.os != 'windows-latest' }} with: { deno-version: 2.7.4 } - run: pnpm install - run: pnpm build - run: pnpm vitest run test/examples env: { NITRO_BUILDER: rolldown, NITRO_VITE_PKG: vite } - run: pnpm vitest run test/presets env: { NITRO_BUILDER: rolldown, NITRO_VITE_PKG: vite } publish-pkg-pr-new: runs-on: ubuntu-latest timeout-minutes: 10 needs: [tests-checks, tests-rollup, tests-rolldown] steps: - uses: actions/checkout@v6 with: { fetch-depth: 0 } - run: npm i -fg corepack && corepack enable - uses: actions/setup-node@v6 with: { node-version: lts/*, cache: "pnpm" } - run: pnpm install - run: pnpm build - run: pnpm dlx pkg-pr-new publish || true publish-nitro-nightly: runs-on: ubuntu-latest timeout-minutes: 10 environment: npm-publish-nightly permissions: { id-token: write, contents: read } needs: [tests-checks, tests-rollup, tests-rolldown] if: contains('refs/heads/main', github.ref) && github.event_name == 'push' steps: - uses: actions/checkout@v6 with: { fetch-depth: 0 } - run: npm i -fg corepack && corepack enable - uses: actions/setup-node@v6 with: { node-version: lts/*, cache: "pnpm" } - run: pnpm install - run: pnpm build - run: pnpm changelogen --bump -r 3.0.1 --canary nightly - run: npm i -g npm@latest && npm publish --tag latest ================================================ FILE: .github/workflows/copilot-setup-steps.yml ================================================ name: "Copilot Setup Steps" on: workflow_dispatch jobs: copilot-setup-steps: runs-on: ubuntu-latest permissions: contents: read steps: - uses: actions/checkout@v6 - run: npm i -g --force corepack && corepack enable - uses: actions/setup-node@v6 with: { node-version: lts/*, cache: pnpm } - run: pnpm install - run: pnpm build --stub ================================================ FILE: .github/workflows/npm-publish.yml ================================================ name: npm-publish on: push: tags: - "v*" permissions: {} jobs: publish: name: Publish to npm runs-on: ubuntu-latest if: github.repository == 'nitrojs/nitro' timeout-minutes: 30 environment: npm-publish permissions: contents: write id-token: write steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - run: npm i -g --force corepack && corepack enable - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: lts/* cache: pnpm registry-url: https://registry.npmjs.org - run: pnpm install - run: pnpm build - run: pnpm lint - run: pnpm typecheck - run: pnpm test:rolldown - name: Publish to npm with provenance # Uses OIDC trusted publishing — no NPM_TOKEN needed. run: npm publish --provenance --access public --tag latest - name: Create GitHub Release env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAG: ${{ github.ref_name }} run: | gh release create "$TAG" \ --notes "$(pnpm changelogen --from=$(git describe --tags --abbrev=0 "$TAG^"))" \ --title "$TAG" ================================================ FILE: .gitignore ================================================ # Dependencies node_modules jspm_packages # Nitro nitro.d.ts .nitro .data package-lock.json # Logs *.log # Temp directories .temp .tmp .cache # Generated dirs dist .nuxt .output .gen .tmp nuxt.d.ts # Junit reports reports # Coverage reports coverage *.lcov .nyc_output # VSCode .vscode # Intellij idea *.iml .idea # OSX .DS_Store .AppleDouble .LSOverride # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk .vercel_build_output .build-* .env .netlify .vercel .amplify-hosting staticwebapp.config.json playground/firebase.json .zeabur .apphosting test/fixture/functions .data .pnpm-store .wrangler CHANGELOG.md RELEASE_NOTES.md skills/nitro/docs ================================================ FILE: .nvmrc ================================================ lts/* ================================================ FILE: .oxfmtrc.json ================================================ { "$schema": "./node_modules/oxfmt/configuration_schema.json", "trailingComma": "es5", "ignorePatterns": ["*.md", "pnpm*.yaml", "**/*.gen.ts", "**/.docs", "**/dist/**"] } ================================================ FILE: .oxlintrc.json ================================================ { "$schema": "./node_modules/oxlint/configuration_schema.json", "plugins": ["unicorn", "typescript", "oxc"], "rules": { "no-control-regex": "off", "no-unused-expressions": "off", "no-unused-vars": "off", "typescript/no-useless-empty-export": "off", "unicorn/no-empty-file": "off", "unicorn/no-invalid-fetch-options": "off", "unicorn/no-useless-spread": "off" } } ================================================ FILE: AGENTS.md ================================================ ## Project Identity Nitro is a framework-agnostic and deployment-agnostic server framework powered by [H3](https://github.com/h3js/h3) (v2), [UnJS] (https://github.com/unjs), and Vite | Rolldown | Rollup. ## First-time Setup for Development - Run `corepack enable` to ensure `pnpm` is available. - Run `pnpm install` to install dependencies. - Run `pnpm build --stub` to prepare development mode. ## Key Scripts - `pnpm build --stub` — Fast stub build for development. - `pnpm lint` — Lint and format code. - `pnpm format` — Automatically fix lint and formatting issues. - `pnpm test` — Run all tests. - `pnpm typecheck` — Run type tests. **Always run** `pnpm format` and `pnpm typecheck` after making changes. ## Repository Structure - `.github/` — GitHub Actions workflows. - `docs/` — Documentation site built with [UnDocs](https://github.com/unjs/undocs). - `examples/` — Example projects and integrations. - `src/` — Project source code. - `test/` — Unit, minimal, and end-to-end tests. ### Code Structure Project source is centralized under `src/`: - `src/build` — Build logic (Vite | Rolldown | Rollup config, virtual templates, plugins). - `src/cli` — `nitro` CLI subcommands (each file in `src/cli/commands` is a command). - `src/config/` — Config defaults (`src/config/defaults.ts`) and resolvers/normalizers (`src/config/resolvers`). - `src/dev` and `src/runner` — Development server logic. - `src/prerender` — Prerender logic. - `src/presets` — Deployment presets and runtime entry. - `src/types` — Shared types. - `src/utils` — Internal utilities. - `src/runtime` — Runtime code that goes into the bundle (runtime and platform agnostic). ### Why Changes in `src/` Are High-Impact Code in `src/` affects all Nitro users: - Changes in `src/runtime` are bundled and run across all deployment targets. - Changes in `src/build` affect build output and performance. - Changes in `src/presets` affect specific deployment platforms. - Changes in `src/config` affect default behavior. Review these changes carefully for backwards compatibility, bundle size, and cross-runtime support. ## Code Patterns & Conventions - `pathe` — Cross-platform path operations (always prefer over `node:path`). - `defu` — Deep object merging and config defaults. - `consola` — Logging in build/dev code (use `nitro.logger` when available). - `unstorage` — Storage abstraction. ### Runtime Constraints Code in `src/runtime/` must be runtime-agnostic: - **Don't use Node.js-specific APIs** (unless behind runtime checks). - Prefer **Web APIs** (fetch, Request, Response, URL, etc.). - Only use `console` for logging (no `consola` in runtime). - Keep bundle size minimal and side-effect free. ## Testing Strategy ### Test Structure Main tests are defined in `test/tests.ts` and setup per each deployment provider in `test/presets` and run against `test/fixture` nitro app. Add new regression tests to `test/fixture`. Other tests: - **Unit** (`test/unit/`) — Isolated unit tests. - **Minimal** (`test/minimal/`) — Smallest bundle output. ### Testing Requirements - Run `pnpm run test` before submitting. - **Bug fixes MUST include a failing test first** — add regression tests to `test/fixture/` and make sure test script fails before attempting the fix and resolves after. - Keep tests deterministic and environment-independent. ## Working with Presets Each preset in `src/presets/` defines deployment target behavior: - Runtime logic and entry is in `src/presets//runtime` - Preset config and utils (build time) are in `src/presets//*.ts`. ## Development Workflow ### Making Changes 1. Make changes in `src/`. 2. Run `pnpm build --stub` if you changed build logic. 3. Test with `pnpm test`. 4. Run `pnpm format`. 5. Run `pnpm typecheck`. 6. Run `pnpm vitest run`. ## Contribution Principles - Prefer **minimal, targeted changes** over large refactors. - Avoid introducing new dependencies unless strictly necessary. Add them to `devDependencies` unless they are required in runtime logic. - Be mindful of **bundle size**, startup cost, and runtime overhead. - Maintain **backwards compatibility** unless explicitly instructed otherwise. - Batch multiple related edits together. Avoid sequential micro-changes. - Never modify files outside the scope of the requested change. ## Common Gotchas - **Don't use Node.js-specific APIs in `src/runtime/`** — Code runs in multiple runtimes (Node, workers, edge). - **Virtual modules must be registered** in `src/build/virtual.ts`. - **CLI commands** are in `src/cli/commands/` — Each file exports a command definition. - **Runtime size matters** — Check bundle impact with `pnpm build`. - **Use `pathe` not `node:path`** — Ensures cross-platform compatibility. ## Error & Logging Guidelines - Prefer explicit errors over silent failures. - Use `nitro.logger` in build/dev code, `consola` as fallback. - Use `console` only in `src/runtime/` code. - Use warnings for recoverable situations; throw for invalid states. - Include actionable context in error messages. ## Documentation Requirements - Update `docs/` for user-facing changes. - Update types and JSDoc for API changes. - Examples in `examples/` should reflect best practices and be added for new integrations. - Add migration notes for breaking changes. ## Code Conventions - Use **ESM** and modern JavaScript; use explicit extensions (`.ts`, `.mjs`) in imports. - For `.json` imports, use `with { "type": "json" }`. - Avoid barrel files (`index.ts` re-exports); import directly from specific modules. - Place non-exported/internal helpers at the end of the file. - For multi-arg functions, use an options object as the second parameter. - Split logic across files; avoid long single-file modules (>200 LoC). Use `_*` prefix for internal files. - Prefer **Web APIs** over Node.js APIs where possible. - Do not add comments explaining what the line does unless prompted. - Before adding new code, study surrounding patterns, naming conventions, and architectural decisions. - Use existing UnJS utilities and dependencies before adding new packages. - Keep runtime code minimal and fast. ## Commit Conventions - Use **semantic commit messages**, lower-case (e.g., `fix(cli): resolve path issue`). - Prefer to include scope (e.g., `feat(runtime):`, `fix(build):`). - Add a short description on the second line when helpful. ## Detailed References For deeper context, see `.agents/`: - [`.agents/architecture.md`](.agents/architecture.md) — Full architecture: core instance, build system, config resolution, virtual modules, runtime internals, dev server, routing, key libraries. - [`.agents/presets.md`](.agents/presets.md) — All 31 presets, preset structure, how to create presets, resolution logic. - [`.agents/testing.md`](.agents/testing.md) — Test structure, how tests work, adding regression tests, running tests. - [`.agents/vite.md`](.agents/vite.md) — Vite build system: plugin architecture (6 sub-plugins), environments API, dev server integration, production build stages, bundler config, HMR, runtime worker. - [`.agents/docs.md`](.agents/docs.md) — Documentation conventions: structure, preset naming (underscore), H3 v2 API patterns, import paths, common mistakes. - **Important:** H3 v2 updated docs is at `node_modules/h3/skills/h3/docs/TOC.md` ================================================ FILE: CLAUDE.md ================================================ @AGENTS.md ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at CODE_OF_CONDUCT.md. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. ================================================ FILE: CONTRIBUTING.md ================================================ # Contribution Guide > All contributors lead the growth of Nitro - including you! ## Discussions You can involve in discussions using: - [GitHub Discussions](discussions) - [Nitro Discord](https://discord.nitro.build) ## Contribute to the Code > [!IMPORTANT] > Please discuss your ideas with the maintainers before opening a pull request. ### Local Development - Clone the [`nitrojs/nitro`](https://github.com/nitrojs/nitro) git repository. - Install the latest LTS version of [Node.js](https://nodejs.org/en/) (v22+). - Enable [corepack](https://github.com/nodejs/corepack) using `corepack enable` (run `npm i -g corepack` if it's not available). - Install dependencies using `pnpm install`. - Build the project in stub mode using `pnpm build --stub`. - Run the playground with `pnpm nitro dev ./playground` to verify changes. - Add, modify, and run tests using `pnpm test`. - Tip: Run `pnpm vitest test/presets/node.test.ts` for quick testing. ## Reporting Issues You might encounter a bug while using Nitro. Although we aim to resolve all known issues, new bugs can emerge over time. Your bug report helps us find and fix them faster — even if you're unable to fix the underlying code yourself. Here’s how to report a bug effectively: ### Ensure It's a Bug Sometimes what seems like a bug may actually be expected behavior or a missing feature. Make sure you’re reporting an actual bug by creating a minimal nitro project and reducing scope. ### Create a Minimal Reproduction Please create a minimal reproduction using the Nitro starter templates. Sometimes, bugs originate from another layer — not Nitro itself. A minimal reproduction helps identify the source and speeds up debugging. Use one of the following templates to reproduce the issue: - [Stackblitz Template](https://stackblitz.com/fork/github/nitrojs/starter) - [Nitro Starter Repo](https://github.com/nitrojs/starter) If your bug involves a higher-level framework like [Nuxt](https://nuxt.com), please report it there. Maintainers will help narrow it down to a Nitro-level issue if needed. ### Search Existing Issues and Discussions Before creating a new issue, search existing [issues](https://github.com/nitrojs/nitro/issues) and [discussions](https://github.com/nitrojs/nitro/discussions) to see if your bug has already been reported. If it has already been reported: - Add a 👍 reaction to the original post (instead of commenting "me too" or "when will it be fixed"). - If you can provide additional context or a better/smaller reproduction, please share it. > [!NOTE] > If the issue seems related but different or old or already closed, it's **better to open a new issue**. Maintainers will merge similar issues if needed. ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) Pooya Parsa and Nitro contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## Third-Party Licenses This software includes bundled third-party dependencies. The licenses and copyright notices for these dependencies are available in `dist/THIRD-PARTY-LICENSES.md` within the distributed package. ================================================ FILE: README.md ================================================ [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/nitrojs/nitro) # Nitro > [!NOTE] > You’re viewing the **v3** branch. > For the current stable release, see [Nitro v2](https://github.com/nitrojs/nitro/tree/v2). **Nitro** extends your Vite app with a **production-ready server**, designed to run **anywhere**. Add server routes, deploy across multiple platforms, and enjoy a **zero-config** experience. 📘 **Docs:** [https://nitro.build](https://nitro.build) ## Contributing See Check out the [Contribution Guide](./CONTRIBUTING.md) to get started. ## License Released under the [MIT License](LICENSE). ================================================ FILE: automd.config.ts ================================================ import type { Config } from "automd"; import { readdir, stat, readFile } from "node:fs/promises"; import { join, extname, relative } from "pathe"; interface FileEntry { path: string; relativePath: string; content: string; language: string; } const DEFAULT_IGNORE = [ "node_modules", ".git", ".DS_Store", ".nuxt", ".output", ".nitro", "dist", "coverage", ".cache", ".turbo", "pnpm-lock.yaml", "package-lock.json", "yarn.lock", ]; const EXTENSION_LANGUAGE_MAP: Record = { ".ts": "ts", ".tsx": "tsx", ".js": "js", ".jsx": "jsx", ".mjs": "js", ".cjs": "js", ".vue": "vue", ".json": "json", ".html": "html", ".css": "css", ".scss": "scss", ".md": "md", ".yaml": "yaml", ".yml": "yaml", ".toml": "toml", ".sh": "bash", ".bash": "bash", ".zsh": "bash", }; async function parseGitignore(dir: string): Promise { try { const gitignorePath = join(dir, ".gitignore"); const content = await readFile(gitignorePath, "utf8"); return content .split("\n") .map((line) => line.trim()) .filter((line) => line && !line.startsWith("#")); } catch { return []; } } function shouldIgnore(name: string, ignorePatterns: string[], defaultIgnore: string[]): boolean { const allPatterns = [...defaultIgnore, ...ignorePatterns]; for (const pattern of allPatterns) { const cleanPattern = pattern.replace(/^\//, "").replace(/\/$/, ""); if (name === cleanPattern) { return true; } if (pattern.startsWith("*") && name.endsWith(pattern.slice(1))) { return true; } if (pattern.endsWith("*") && name.startsWith(pattern.slice(0, -1))) { return true; } } return false; } function getLanguage(filePath: string): string { const ext = extname(filePath).toLowerCase(); return EXTENSION_LANGUAGE_MAP[ext] || "text"; } async function collectFiles( dir: string, baseDir: string, ignorePatterns: string[], maxDepth: number, currentDepth: number = 0 ): Promise { if (maxDepth > 0 && currentDepth >= maxDepth) { return []; } const entries = await readdir(dir); const files: FileEntry[] = []; for (const entry of entries) { if (shouldIgnore(entry, ignorePatterns, DEFAULT_IGNORE)) { continue; } const fullPath = join(dir, entry); const stats = await stat(fullPath); if (stats.isDirectory()) { const nestedFiles = await collectFiles( fullPath, baseDir, ignorePatterns, maxDepth, currentDepth + 1 ); files.push(...nestedFiles); } else { try { const content = await readFile(fullPath, "utf8"); const relativePath = relative(baseDir, fullPath); files.push({ path: fullPath, relativePath, content: content.trim(), language: getLanguage(fullPath), }); } catch { // Skip binary or unreadable files } } } return files; } function sortFiles(files: FileEntry[]): FileEntry[] { return files.sort((a, b) => { const aParts = a.relativePath.split("/"); const bParts = b.relativePath.split("/"); // Sort by depth first (shallower files first) if (aParts.length !== bParts.length) { return aParts.length - bParts.length; } // Then alphabetically return a.relativePath.localeCompare(b.relativePath); }); } function generateCodeTree( files: FileEntry[], options: { defaultValue?: string; expandAll?: boolean } = {} ): string { const sortedFiles = sortFiles(files); const codeBlocks: string[] = []; for (const file of sortedFiles) { const lang = file.language; const filename = file.relativePath; // Use 4 backticks for markdown files to avoid conflicts const fence = lang === "md" ? "````" : "```"; codeBlocks.push(`${fence}${lang} [${filename}]`); codeBlocks.push(file.content); codeBlocks.push(fence); codeBlocks.push(""); } const attrs: string[] = []; if (options.defaultValue) { attrs.push(`defaultValue="${options.defaultValue}"`); } if (options.expandAll) { attrs.push(`expandAll`); } const propsStr = attrs.length > 0 ? `{${attrs.join(" ")}}` : ""; const contents = `::code-tree${propsStr}\n\n${codeBlocks.join("\n").trim()}\n\n::`; return contents; } function resolvePath(srcPath: string, options: { url?: string; dir?: string }): string { if (srcPath.startsWith("/")) { return srcPath; } const base = options.url ? new URL(".", options.url).pathname : options.dir || process.cwd(); return join(base, srcPath); } export default { input: ["README.md", "docs/**/*.md"], generators: { compatDate: { name: "compatDate", async generate(ctx) { // const { compatibilityChanges } = await import("./lib/meta.mjs"); // const table = [ // "| Compatibility date | Platform | Description |", // "|------|----------|-------------|", // ...compatibilityChanges.map( // (change) => // `| **≥ ${change.from}** | ${change.platform} | ${change.description} |` // ), // ]; return { // contents: table.join("\n"), contents: "", }; }, }, "ui-code-tree": { name: "ui-code-tree", async generate({ args, config, url, }: { args: Record; config: { dir?: string }; url?: string; }) { const srcPath = (args.src as string) || "."; const fullPath = resolvePath(srcPath, { url, dir: config.dir }); const stats = await stat(fullPath); if (!stats.isDirectory()) { throw new Error(`Path "${srcPath}" is not a directory`); } const userIgnore: string[] = args.ignore ? String(args.ignore) .split(",") .map((s: string) => s.trim()) : []; const gitignorePatterns = await parseGitignore(fullPath); const ignorePatterns = [...gitignorePatterns, ...userIgnore, "README.md", ".*"]; const maxDepth = args.maxDepth ? Number(args.maxDepth) : 0; const defaultValue = (args.defaultValue || args.default) as string | undefined; const expandAll = args.expandAll !== undefined && args.expandAll !== "false"; const files = await collectFiles(fullPath, fullPath, ignorePatterns, maxDepth); if (files.length === 0) { return { contents: "", issues: ["No files found in the specified directory"], }; } const contents = generateCodeTree(files, { defaultValue, expandAll }); return { contents }; }, }, }, } satisfies Config; ================================================ FILE: build.config.ts ================================================ import { defineBuildConfig } from "obuild/config"; import { resolveModulePath } from "exsolve"; import { traceNodeModules } from "nf3"; import { mkdir, readFile, rmdir, writeFile } from "node:fs/promises"; import type { CodeSplittingOptions } from "rolldown"; const isStub = process.argv.includes("--stub"); const pkg = await import("./package.json", { with: { type: "json" } }).then((r) => r.default || r); const tracePkgs = [ "cookie-es", // used by azure runtime "croner", // used by internal/task "defu", // used by open-api runtime "destr", // used by node-server and deno-server "get-port-please", // used by dev server "rendu", // used by HTML renderer template "scule", // used by runtime config "source-map", // used by dev error runtime "ufo", // used by presets and runtime "unctx", // used by internal/context "youch", // used by error handler "youch-core", // used by error handler ]; export default defineBuildConfig({ entries: [ { type: "bundle", input: ["src/builder.ts", "src/cli/index.ts", "src/types/index.ts", "src/vite.ts"], }, { type: "transform", input: "src/runtime/", outDir: "dist/runtime", }, { type: "transform", input: "src/presets/", outDir: "dist/presets", filter: (id) => id.includes("runtime/"), dts: false, }, ], hooks: { rolldownConfig(config) { config.platform = "node"; config.resolve ??= {}; config.resolve.alias ??= {}; Object.assign(config.resolve.alias, { "node-fetch-native/proxy": "node-fetch-native/native", "node-fetch-native": "node-fetch-native/native", }); config.external ??= []; (config.external as string[]).push( "nitro", ...Object.keys(pkg.exports || {}).map((key) => key.replace(/^./, "nitro")), ...Object.keys(pkg.dependencies), ...Object.keys(pkg.peerDependencies), ...tracePkgs, "typescript", "firebase-functions", "@scalar/api-reference", "get-port-please", "cloudflare:workers", "@cloudflare/workers-types", // unplugin deps "@rspack/core", "@farmfe/core", "webpack", "unloader" ); }, rolldownOutput(config) { (config.codeSplitting as CodeSplittingOptions).groups?.unshift( { test: /src[/\\]build[/\\](plugins|virtual|\w+\.ts)/, name: "_build/common", }, { test: /src[/\\](utils)[/\\]/, name: "_chunks/utils" } ); config.chunkFileNames = (chunk) => { if (chunk.name.startsWith("_")) { return `[name].mjs`; } if (chunk.name === "rolldown-runtime") { return `_common.mjs`; } if (chunk.moduleIds.every((id) => id.includes("node_modules"))) { const pkgNames = [ ...new Set( chunk.moduleIds .map( (id) => id.match(/.*[/\\]node_modules[/\\](?@[^/\\]+[/\\][^/\\]+|[^/\\]+)/) ?.groups?.package ) .filter(Boolean) .map((name) => name!.split(/[/\\]/).pop()!) .filter(Boolean) ), ].sort((a, b) => a.length - b.length); let chunkName = ""; for (const name of pkgNames) { const separator = chunkName ? "+" : ""; if ((chunkName + separator + name).length > 30) { break; } chunkName += separator + name; } return `_libs/${chunkName || "_"}.mjs`; } if (chunk.moduleIds.every((id) => /src[/\\]cli[/\\]/.test(id))) { return `cli/_chunks/[name].mjs`; } if (chunk.moduleIds.every((id) => /build[/\\]vite[/\\]/.test(id))) { return `_build/vite.[name].mjs`; } if (chunk.moduleIds.every((id) => /build[/\\]rolldown[/\\]/.test(id))) { return `_build/rolldown.mjs`; } if (chunk.moduleIds.every((id) => /build[/\\]rollup[/\\]|build[/\\]plugins/.test(id))) { return `_build/rollup.mjs`; } if (chunk.moduleIds.every((id) => /src[/\\]dev[/\\]|src[/\\]runtime/.test(id))) { return `_dev.mjs`; } if (chunk.moduleIds.every((id) => /src[/\\]presets/.test(id))) { return `_presets.mjs`; } if ( chunk.moduleIds.every((id) => /src[/\\]build[/\\]|src[/\\]presets|src[/\\]utils/.test(id)) ) { return `_build/shared.mjs`; } if (chunk.moduleIds.every((id) => /src[/\\](runner|dev|runtime)/.test(id))) { return `_chunks/dev.mjs`; } return "_chunks/nitro.mjs"; }; }, async end() { if (isStub) { return; } // Bundle docs const { DocsManager, DocsSourceFS, exportDocsToFS } = await import("mdzilla"); const man = new DocsManager(new DocsSourceFS("./docs")); await man.load(); await rmdir("./skills/nitro/docs").catch(() => {}); await mkdir("./skills/nitro/docs", { recursive: true }); await exportDocsToFS(man, "./skills/nitro/docs", { title: "Nitro Documentation", tocFile: "TOC.md", filter: (e: { entry: { path: string } }) => !e.entry.path.startsWith("/blog"), }); // Trace included dependencies await traceNodeModules( tracePkgs.map((pkg) => resolveModulePath(pkg)), { hooks: { tracedPackages(packages) { // Avoid tracing direct dependencies const deps = new Set([ ...Object.keys(pkg.dependencies), ...Object.keys(pkg.peerDependencies), ]); for (const dep of deps) { delete packages[dep]; } }, }, } ); // Vite types await writeFile( "dist/vite.d.mts", `import "vite/client";\nimport "nitro/vite/types";\n${await readFile("dist/vite.d.mts", "utf8")}` ); }, }, }); ================================================ FILE: changelog.config.ts ================================================ import type { ChangelogConfig } from "changelogen"; export default { output: false, types: { presets: { title: "Preset Changes", semver: "patch" }, }, } satisfies Partial; ================================================ FILE: docs/.config/automd.config.ts ================================================ export { default } from "../../automd.config"; ================================================ FILE: docs/.config/docs.yaml ================================================ # yaml-language-server: $schema=https://unpkg.com/undocs/schema/config.json name: Nitro shortDescription: Ship Full-stack Vite Apps description: Add server API routes to any Vite apps and deploy with zero configuration on your favorite hosting platform. github: nitrojs/nitro url: https://nitro.build socials: x: "https://x.com/nitrojsdev" bluesky: "https://bsky.app/profile/nitro.build" discord: https://discord.nitro.build sponsors: api: https://sponsors.pi0.io/sponsors.json redirects: # v2 guide → v3 docs "/guide": "/docs" "/guide/getting-started": "/docs" "/guide/utils": "/docs/routing" "/guide/routing": "/docs/routing" "/guide/websocket": "/docs/routing" "/guide/cache": "/docs/cache" "/guide/storage": "/docs/storage" "/guide/database": "/docs/database" "/guide/fetch": "/docs/routing" "/guide/assets": "/docs/assets" "/guide/plugins": "/docs/plugins" "/guide/configuration": "/docs/configuration" "/guide/typescript": "/docs/configuration" "/guide/tasks": "/docs/tasks" "/guide/nightly": "/docs/nightly" # v2 deploy "/deploy/workers": "/deploy" "/deploy/runtimes/winterjs": "/deploy" "/deploy/custom-presets": "/deploy" "/deploy/providers/edgio": "/deploy" "/deploy/node": "/deploy/runtimes/node" themeColor: "rose" automd: true branch: main versions: - label: "v3 (beta)" active: true - label: "v2 (legacy)" to: "https://v2.nitro.build" ================================================ FILE: docs/.docs/app.config.ts ================================================ import { defineAppConfig } from "#imports" export default defineAppConfig({ ui: { button: { slots: { base: 'active:translate-y-px transition-transform duration-300', }, }, }, }) ================================================ FILE: docs/.docs/assets/css/main.css ================================================ :root { --font-sans: "Geist", sans-serif !important; --font-mono: "Geist Mono", monospace !important; } h1[data-slot="title"] { font-family: "Geist Pixels", sans-serif !important; } .landing-code { --ui-bg-muted: #0c0c0e; --ui-border-muted: #27272a; --ui-bg: #0c0c0e; } .landing-code pre[style] { background-color: #0c0c0e !important; } .landing-code > div > div { margin-top: 0; margin-bottom: 0; } ================================================ FILE: docs/.docs/components/AppHero.vue ================================================ ================================================ FILE: docs/.docs/components/AppHeroLinks.vue ================================================ ================================================ FILE: docs/.docs/components/FeatureCard.vue ================================================ ================================================ FILE: docs/.docs/components/HeroBackground.client.vue ================================================ ================================================ FILE: docs/.docs/components/HeroFeatures.vue ================================================ ================================================ FILE: docs/.docs/components/LandingFeatures.vue ================================================ ================================================ FILE: docs/.docs/components/PerformanceShowcase.vue ================================================ ================================================ FILE: docs/.docs/layouts/examples.vue ================================================ ================================================ FILE: docs/.docs/nuxt.config.ts ================================================ import { defineNuxtConfig } from "nuxt/config" export default defineNuxtConfig({ modules: ['motion-v/nuxt'], css: ['~/assets/css/main.css'], fonts: { families: [ { name: 'Geist', weights: [400, 600, 700], global: true }, { name: 'Geist Mono', weights: [400, 600], global: true }, { name: "Geist Pixels", src: "/assets/fonts/GeistPixel-Square.woff2", weight: 500, global: true }, ], }, }) ================================================ FILE: docs/.docs/pages/examples/[...slug].vue ================================================ ================================================ FILE: docs/.docs/pages/examples/index.vue ================================================ ================================================ FILE: docs/.docs/pages/index.vue ================================================ ================================================ FILE: docs/.docs/utils/examples.ts ================================================ // Category order for examples - used in sidebar and examples page export const categoryOrder = [ 'features', 'config', 'server side rendering', 'backend frameworks', 'integrations', 'vite', ] export const categoryIcons: Record = { vite: 'i-logos-vitejs', 'backend frameworks': 'i-lucide-puzzle', features: 'i-lucide-sparkles', config: 'i-lucide-settings', integrations: 'i-lucide-plug', 'server side rendering': 'i-lucide-server', other: 'i-lucide-folder', } ================================================ FILE: docs/.npmrc ================================================ shamefully-hoist=true ================================================ FILE: docs/1.docs/.navigation.yml ================================================ icon: i-lucide-book-open ================================================ FILE: docs/1.docs/1.index.md ================================================ --- icon: i-lucide-compass --- # Introduction > Nitro is a full-stack server framework, compatible with any runtime and any deployment target. Nitro gives you a production-ready server with filesystem routing, code-splitting, and built-in support for storage, caching, and databases — all runtime-agnostic and deployable anywhere. ## What is Nitro? Create server and API routes inside the `routes/` directory. Each file maps directly to a URL path, and Nitro handles the rest — routing, code-splitting, and optimized builds. You can also take full control of the server entry by creating a `server.ts` file. Nitro’s high-level, runtime-agnostic approach lets you use any HTTP library, such as [Elysia](https://elysiajs.com/), [h3](https://h3.dev), or [Hono](https://hono.dev). ### Performance Nitro compiles your routes at build time, removing the need for a runtime router. Only the code required to handle each incoming request is loaded and executed. This makes it ideal for serverless hosting, with near-0ms boot time regardless of project size. ### Deploy Anywhere Build your server into an optimized `.output/` folder compatible with Node.js, Bun, Deno, and many hosting platforms without any configuration — Cloudflare Workers, Netlify, Vercel, and more. Take advantage of platform features like ESR, ISR, and SWR without changing a single line of code. ### Server-Side Rendering Render HTML with your favorite templating engine, or use component libraries such as React, Vue, or Svelte directly on the server. Go full universal rendering with client-side hydration. Nitro provides the foundation and a progressive approach to reach your goals. ### Storage Nitro includes a runtime-agnostic key-value storage layer out of the box. It uses in-memory storage by default, but you can connect more than 20 different drivers (FS, Redis, S3, etc.), attach them to different namespaces, and swap them without changing your code. ### Caching Nitro supports caching for both server routes and server functions, backed directly by the server storage (via the `cache` namespace). ### Database Nitro also includes a built-in SQL database. It defaults to SQLite, but you can connect to and query more than 10 databases (Postgres, MySQL, PGLite, etc.) using the same API. ### Meta-Framework Foundation Nitro can be used as the foundation for building your own meta-framework. Popular frameworks such as Nuxt, SolidStart, and TanStack Start fully or partially leverage Nitro. ## Vite Integration Nitro integrates seamlessly with [Vite](https://vite.dev) as a plugin. If you’re building a frontend application with Vite, adding Nitro gives you API routes, server-side rendering, and a full production server — all built together with `vite build`. ```ts [vite.config.ts] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()], }); ``` With Nitro, `vite build` produces an optimized `.output/` folder containing both your frontend and backend — ready to deploy anywhere. Ready to give it a try? Jump into the [quick start](/docs/quick-start). ================================================ FILE: docs/1.docs/2.quick-start.md ================================================ --- icon: i-lucide-zap --- # Quick Start > Start with a fresh Nitro project or adopt it in your current Vite project. ## Try Nitro online Get a taste of Nitro in your browser using our playground. [Play with Nitro in StackBlitz](https://stackblitz.com/github/nitrojs/starter/tree/v3-vite?file=index.html,server.ts){target="_blank"} ## Create a Nitro project The fastest way to create a Nitro application is using the `create-nitro-app`. > [!NOTE] > Make sure to have installed the latest LTS version of either [Node.js](https://nodejs.org/en), [Bun](https://bun.sh/), or [Deno](https://deno.com/). :pm-x{command="create-nitro-app"}
Preview
Preview
Follow the instructions from the CLI and you will be ready to start your development server. ## Add to a Vite project You can add Nitro to any existing Vite project to get API routes, server-side rendering, and more. ::steps{level="3"} ### Install `nitro` and `vite` :pm-install{name="nitro vite"} ### Add Nitro plugin to Vite Add the Nitro plugin to your `vite.config.ts`: ```ts [vite.config.ts] {2,6} import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [ nitro() ], }); ``` ### Configure Nitro Create a `nitro.config.ts` to configure the server directory: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ serverDir: "./server", }); ``` The `serverDir` option tells Nitro where to look for your server routes. In this example, all routes will be inside the `server/` directory. ### Create an API route Create your first API route at `server/api/test.ts`: ::code-tree{defaultValue="server/api/test.ts"} ```ts [server/api/test.ts] import { defineHandler } from "nitro"; export default defineHandler(() => { return { message: "Hello Nitro!" }; }); ``` ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ serverDir: "./server", }); ``` ```ts [vite.config.ts] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()], }); ``` :: The file path maps directly to the route URL — `server/api/test.ts` becomes `/api/test`. ::tip As an alternative to filesystem routing, you can declare routes programmatically using the `routes` config option. See [Programmatic route handlers](/docs/routing#programmatic-route-handlers) for more details. :: ::tip You can return strings, JSON objects, `Response` instances, or readable streams from your handlers. See [Routing](/docs/routing) for more about dynamic routes, methods, and middleware. :: ### Start the development server :pm-run{script="dev -- --open"} Your API route is now accessible at `http://localhost:3000/api/test` :sparkles: ================================================ FILE: docs/1.docs/4.renderer.md ================================================ --- icon: ri:layout-masonry-line navigation: title: Renderer --- # Nitro Renderer > Use a renderer to handle all unmatched routes with custom HTML or a templating system. The renderer is a special handler in Nitro that catches all routes that don't match any specific API or route handler. It's commonly used for server-side rendering (SSR), serving single-page applications (SPAs), or creating custom HTML responses. ## Configuration The renderer is configured using the `renderer` option in your Nitro config: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ renderer: { template: './index.html', // Path to HTML template file handler: './renderer.ts', // Path to custom renderer handler static: false, // Treat template as static HTML (no rendu processing) } }) ``` | Option | Type | Description | | --- | --- | --- | | `template` | `string` | Path to an HTML file used as the renderer template. | | `handler` | `string` | Path to a custom renderer handler module. | | `static` | `boolean` | When `true`, skips rendu template processing and serves the HTML as-is. Auto-detected based on template syntax when not set. | Set `renderer: false` in the config to explicitly disable the renderer entirely (including auto-detection of `index.html`). ## HTML template ### Auto-detected `index.html` By default, Nitro automatically looks for an `index.html` file in your project src dir. If found, Nitro will use it as the renderer template and serve it for all unmatched routes. ::code-group ```html [index.html] My Vite + Nitro App
``` ```ts [routes/api/hello.ts] import { defineHandler } from "nitro"; export default defineHandler((event) => { return { hello: "API" }; }); ``` :: ::tip When `index.html` is detected, Nitro will automatically log in the terminal: `Using index.html as renderer template.` :: With this setup: - `/api/hello` → Handled by your API routes - `/about`, `/contact`, etc. → Served with `index.html` ### Custom HTML file You can specify a custom HTML template file using the `renderer.template` option in your Nitro configuration. ::code-group ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ renderer: { template: './app.html' } }) ``` ```html [app.html] Custom Template
Loading...
``` :: ### Static templates By default, Nitro auto-detects whether your HTML template contains [rendu](#hypertext-preprocessor-experimental) syntax. If it does, the template is processed dynamically on each request. If it doesn't, it's served as static HTML. You can override this behavior with the `static` option: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ renderer: { template: './index.html', static: true // Force static serving, skip template processing } }) ``` In production, static templates are inlined into the server bundle and served directly for optimal performance. ### Hypertext Preprocessor (experimental) Nitro uses [rendu](https://github.com/h3js/rendu) Hypertext Preprocessor, which provides a simple and powerful way to create dynamic HTML templates with JavaScript expressions. #### Output expressions - `{{ expression }}` — HTML-escaped output - `{{{ expression }}}` or `` — raw (unescaped) output ```html

Hello {{ $URL.pathname }}

{{{ 'raw html' }}}
``` #### Control flow Use `` for JavaScript control flow: ```html

Form submitted!

  • {{ item }}
``` #### Server scripts Use `
{{ JSON.stringify(data) }}
``` #### Streaming content Use the `echo()` function for streaming content. It accepts strings, functions, Promises, Response objects, or ReadableStreams: ```html ``` #### Global variables Access request context within templates: | Variable | Description | | --- | --- | | `$REQUEST` | The incoming `Request` object | | `$METHOD` | HTTP method (`GET`, `POST`, etc.) | | `$URL` | Request `URL` object | | `$HEADERS` | Request headers | | `$RESPONSE` | Response configuration object | | `$COOKIES` | Read-only object containing request cookies | #### Built-in functions | Function | Description | | --- | --- | | `htmlspecialchars(str)` | Escape HTML characters (automatically applied in `{{ }}` syntax) | | `setCookie(name, value, options?)` | Set a cookie in the response | | `redirect(url)` | Redirect the user to another URL | | `echo(content)` | Stream content to the response | ```html [index.html] Dynamic template

Hello {{ $REQUEST.url }}

Welcome, !

``` :read-more{to="https://github.com/h3js/rendu" title="Rendu Documentation"} ## Custom renderer handler For more complex scenarios, you can create a custom renderer handler that programmatically generates responses. The handler is a default export function that receives an H3 event object. You can access the incoming `Request` via `event.req`: ```ts [renderer.ts] export default function renderer({ req }: { req: Request }) { const url = new URL(req.url); return new Response( /* html */ ` Custom Renderer

Hello from custom renderer!

Current path: ${url.pathname}

`, { headers: { "content-type": "text/html; charset=utf-8" } } ); } ``` Then, specify the renderer entry in the Nitro config: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ renderer: { handler: './renderer.ts' } }) ``` ::note When `renderer.handler` is set, it takes full control of rendering. The `renderer.template` option is ignored. :: ## Renderer priority The renderer always acts as a catch-all route (`/**`) and has the **lowest priority**. This means: 1. Specific API routes are matched first (e.g., `/api/users`) 2. Specific server routes are matched next (e.g., `/about`) 3. The renderer catches everything else ```md api/ users.ts → /api/users (matched first) routes/ about.ts → /about (matched second) renderer.ts → /** (catches all other routes) ``` ::warning If you define a catch-all route (`[...].ts`) in your routes, Nitro will warn you that the renderer will override it. Use more specific routes or different HTTP methods to avoid conflicts. :: :read-more{to="/docs/lifecycle" title="Lifecycle"} ## Vite integration When using Nitro with Vite, the renderer integrates with Vite's build pipeline and dev server. ### Development mode In development, the renderer template is read from disk on each request, so changes to `index.html` are reflected immediately without restarting the server. Vite's `transformIndexHtml` hook is applied to inject HMR client scripts and other dev-time transforms. ### SSR with `` When using Vite environments with an `ssr` service, you can add an `` comment to your `index.html`. Nitro will replace it with the output from your SSR entry during rendering: ```html [index.html] SSR App
``` ### Production build During production builds, Vite processes the `index.html` through its build pipeline (resolving scripts, CSS, and other assets), then Nitro inlines the transformed HTML into the server bundle. ## Use Cases ### Single-Page Application (SPA) Serve your SPA's `index.html` for all routes to enable client-side routing: > [!TIP] > This is the default behavior of Nitro when used with Vite. ================================================ FILE: docs/1.docs/5.routing.md ================================================ --- icon: ri:direction-line --- # Routing > Nitro supports filesystem routing to automatically map files to routes. By combining code-splitting with compiled routes, it removes the need for a runtime router, leaving only minimal compiled logic. ## Request handler Nitro request handler is a function accepting an `event` object, which is a [H3Event](https://h3.dev/guide/api/h3event#h3event-properties) object. ::code-group ```ts [Single function] import type { H3Event } from "nitro"; export default (event: H3Event) => { return "world"; } ``` ```ts [defineHandler] import { defineHandler } from "nitro"; // For better type inference export default defineHandler((event) => { return "world"; }); ``` :: ## Filesystem routing Nitro supports file-based routing for your API routes (files are automatically mapped to [h3 routes](https://h3.dev/guide/basics/routing)). Defining a route is as simple as creating a file inside the `api/` or `routes/` directory. You can only define one handler per files and you can [append the HTTP method](#specific-request-method) to the filename to define a specific request method. ``` routes/ api/ test.ts <-- /api/test hello.get.ts <-- /hello (GET only) hello.post.ts <-- /hello (POST only) vite.config.ts ``` You can nest routes by creating subdirectories. ```txt routes/ api/ [org]/ [repo]/ index.ts <-- /api/:org/:repo issues.ts <-- /api/:org/:repo/issues index.ts <-- /api/:org package.json ``` #### Route Groups In some cases, you may want to group a set of routes together in a way which doesn't affect file-based routing. For this purpose, you can put files in a folder which is wrapped in parentheses `(` and `)`. For example: ```txt routes/ api/ (admin)/ users.ts <-- /api/users reports.ts <-- /api/reports (public)/ index.ts <-- /api package.json ``` > [!NOTE] The route groups are not part of the route definition and are only used for organization purposes. ### Static routes First, create a file in `routes/` or `routes/api/` directory. The filename will be the route path. Then, export a fetch-compatible function: ```ts [routes/api/test.ts] import { defineHandler } from "nitro"; export default defineHandler(() => { return { hello: "API" }; }); ``` ### Dynamic routes #### Single param To define a route with params, use the `[]` syntax where `` is the name of the param. The param will be available in the `event.context.params` object or using the [`getRouterParam`](https://h3.dev/utils/request#getrouterparamevent-name-opts-decode) utility. ```ts [routes/hello/[name\\].ts] import { defineHandler } from "nitro"; export default defineHandler((event) => { const { name } = event.context.params; return `Hello ${name}!`; }); ``` Call the route with the param `/hello/nitro`, you will get: ```txt [Response] Hello nitro! ``` #### Multiple params You can define multiple params in a route using `[]/[]` syntax where each param is a folder. You **cannot** define multiple params in a single filename of folder. ```ts [routes/hello/[name\\]/[age\\].ts] import { defineHandler } from "nitro"; export default defineHandler((event) => { const { name, age } = event.context.params; return `Hello ${name}! You are ${age} years old.`; }); ``` #### Catch-all params You can capture all the remaining parts of a URL using `[...]` syntax. This will include the `/` in the param. ```ts [routes/hello/[...name\\].ts] import { defineHandler } from "nitro"; export default defineHandler((event) => { const { name } = event.context.params; return `Hello ${name}!`; }); ``` Call the route with the param `/hello/nitro/is/hot`, you will get: ```txt [Response] Hello nitro/is/hot! ``` ### Specific request method You can append the HTTP method to the filename to force the route to be matched only for a specific HTTP request method, for example `hello.get.ts` will only match for `GET` requests. You can use any HTTP method you want. Supported methods: `get`, `post`, `put`, `delete`, `patch`, `head`, `options`, `connect`, `trace`. ::code-group ```js [GET] // routes/users/[id].get.ts import { defineHandler } from "nitro"; export default defineHandler(async (event) => { const { id } = event.context.params; // Do something with id return `User profile!`; }); ``` ```js [POST] // routes/users.post.ts import { defineHandler } from "nitro"; export default defineHandler(async (event) => { const body = await event.req.json(); // Do something with body like saving it to a database return { updated: true }; }); ``` :: ### Catch-all route You can create a special route that will match all routes that are not matched by any other route. This is useful for creating a default route. To create a catch-all route, create a file named `[...].ts`. ```ts [routes/[...\\].ts] import { defineHandler } from "nitro"; export default defineHandler((event) => { return `Hello ${event.url}!`; }); ``` ### Environment specific handlers You can specify for a route that will only be included in specific builds by adding a `.dev`, `.prod` or `.prerender` suffix to the file name, for example: `routes/test.get.dev.ts` or `routes/test.get.prod.ts`. The suffix is placed after the method suffix (if any): ```txt routes/ env/ index.dev.ts <-- /env (dev only) index.get.prod.ts <-- /env (GET, prod only) ``` > [!TIP] > You can specify multiple environments or specify a preset name as environment using programmatic registration of routes via [`routes`](#routes-config) config. ### Ignoring files You can use the `ignore` config option to exclude files from route scanning. It accepts an array of glob patterns relative to the server directory. ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ ignore: [ "routes/api/**/_*", // Ignore files starting with _ in api/ "middleware/_*.ts", // Ignore middleware starting with _ "routes/_*.ts", // Ignore root routes starting with _ ], }); ``` ## Programmatic route handlers In addition to filesystem routing, you can register route handlers programmatically using the `routes` config option. ### `routes` config The `routes` option allows you to map route patterns to handlers: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ routes: { "/api/hello": "./server/routes/api/hello.ts", "/api/custom": { handler: "./server/routes/api/hello.ts", method: "POST", lazy: true, }, "/virtual": { handler: "#virtual-route", }, }, }); ``` Each route entry can be a simple string (handler path) or an object with the following options: | Option | Type | Description | |--------|------|-------------| | `handler` | `string` | Path to event handler file or virtual module ID | | `method` | `string` | HTTP method to match (`get`, `post`, etc.) | | `lazy` | `boolean` | Use lazy loading to import handler | | `format` | `"web" \| "node"` | Handler type. `"node"` handlers are converted to web-compatible | | `env` | `string \| string[]` | Environments to include this handler (`"dev"`, `"prod"`, `"prerender"`, or a preset name) | ### `handlers` config The `handlers` array is useful for registering middleware with control over route matching: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ handlers: [ { route: "/api/**", handler: "./server/middleware/api-auth.ts", middleware: true, }, ], }); ``` Each handler entry supports the following options: | Option | Type | Description | |--------|------|-------------| | `route` | `string` | HTTP pathname pattern (e.g., `/test`, `/api/:id`, `/blog/**`) | | `handler` | `string` | Path to event handler file or virtual module ID | | `method` | `string` | HTTP method to match (`get`, `post`, etc.) | | `middleware` | `boolean` | Run handler as middleware before route handlers | | `lazy` | `boolean` | Use lazy loading to import handler | | `format` | `"web" \| "node"` | Handler type. `"node"` handlers are converted to web-compatible | | `env` | `string \| string[]` | Environments to include this handler (`"dev"`, `"prod"`, `"prerender"`, or a preset name) | ## Middleware Nitro route middleware can hook into the request lifecycle. ::tip A middleware can modify the request before it is processed, not after. :: Middleware are auto-registered within the `middleware/` directory. ```md middleware/ auth.ts logger.ts ... routes/ hello.ts ``` ### Simple middleware Middleware are defined exactly like route handlers with the only exception that they should not return anything. Returning from middleware behaves like returning from a request - the value will be returned as a response and further code will not be ran. ```ts [middleware/auth.ts] import { defineHandler } from "nitro"; export default defineHandler((event) => { // Extends or modify the event event.context.user = { name: "Nitro" }; }); ``` Middleware in `middleware/` directory are automatically registered for all routes. If you want to register a middleware for a specific route, see [Object Syntax Event Handler](https://h3.dev/guide/basics/handler#object-syntax). ::note Returning anything from a middleware will close the request and should be avoided! Any returned value from middleware will be the response and further code will not be executed however **this is not recommended to do!** :: ### Route Meta You can define route handler meta at build-time using `defineRouteMeta` macro in the event handler files. > [!IMPORTANT] > This feature is currently experimental. ```ts [routes/api/test.ts] import { defineRouteMeta } from "nitro"; import { defineHandler } from "nitro"; defineRouteMeta({ openAPI: { tags: ["test"], description: "Test route description", parameters: [{ in: "query", name: "test", required: true }], }, }); export default defineHandler(() => "OK"); ``` ::read-more{to="https://swagger.io/specification/v3/"} This feature is currently usable to specify OpenAPI meta. See swagger specification for available OpenAPI options. :: ### Execution order Middleware are executed in directory listing order. ```md middleware/ auth.ts <-- First logger.ts <-- Second ... <-- Third ``` Prefix middleware with a number to control their execution order. ```md middleware/ 1.logger.ts <-- First 2.auth.ts <-- Second 3.... <-- Third ``` ::note Remember that file names are sorted as strings, thus for example if you have 3 files `1.filename.ts`, `2.filename.ts` and `10.filename.ts`, the `10.filename.ts` will come after the `1.filename.ts`. To avoid this, prefix `1-9` with a `0` like `01`, if you have more than 10 middleware in the same directory. :: ### Request filtering Middleware are executed on every request. Apply custom logic to scope them to specific conditions. For example, you can use the URL to apply a middleware to a specific route: ```ts [middleware/auth.ts] import { defineHandler } from "nitro"; export default defineHandler((event) => { // Will only execute for /auth route if (event.url.pathname.startsWith('/auth')) { event.context.user = { name: "Nitro" }; } }); ``` ### Route-scoped middleware You can register middleware for specific route patterns using the [`handlers`](#handlers-config) config with the `middleware` option and a specific `route`: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ handlers: [ { route: "/api/**", handler: "./server/middleware/api-auth.ts", middleware: true, }, ], }); ``` Unlike global middleware (registered in the `middleware/` directory which match `/**`), route-scoped middleware only run for requests matching the specified pattern. ## Error handling You can use the [utilities available in H3](https://h3.dev/guide/basics/error) to handle errors in both routes and middlewares. The way errors are sent back to the client depends on the environment. In development, requests with an `Accept` header of `text/html` (such as browsers) will receive a HTML error page. In production, errors are always sent in JSON. This behaviour can be overridden by some request properties (e.g.: `Accept` or `User-Agent` headers). ## Code splitting Nitro creates a separate chunk for each route handler. Chunks load on-demand when first requested, so `/api/users` doesn't load code for `/api/posts`. See [`inlineDynamicImports`](/config#inlinedynamicimports) to bundle everything into a single file. ## Route rules Nitro allows you to add logic at the top-level for each route of your configuration. It can be used for redirecting, proxying, caching, authentication, and adding headers to routes. It is a map from route pattern (following [rou3](https://github.com/h3js/rou3)) to route options. When `cache` option is set, handlers matching pattern will be automatically wrapped with `defineCachedEventHandler`. See the [cache guide](/docs/cache) to learn more about this function. ::note `swr: true|number` is shortcut for `cache: { swr: true, maxAge: number }` :: You can set route rules in the `nitro.routeRules` options. ```ts [nitro.config.ts] import { defineConfig } from "nitro"; export default defineNitroConfig({ routeRules: { '/blog/**': { swr: true }, '/blog2/**': { swr: 600 }, '/blog3/**': { static: true }, '/blog4/**': { cache: { /* cache options*/ } }, '/assets/**': { headers: { 'cache-control': 's-maxage=0' } }, '/api/v1/**': { cors: true, headers: { 'access-control-allow-methods': 'GET' } }, '/old-page': { redirect: '/new-page' }, '/old-page/**': { redirect: '/new-page/**' }, '/proxy/example': { proxy: 'https://example.com' }, '/proxy/**': { proxy: '/api/**' }, '/admin/**': { basicAuth: { username: 'admin', password: 'supersecret' } }, } }); ``` ### Rule merging and overrides Route rules are matched from least specific to most specific. When multiple rules match a request, their options are merged, with more specific rules taking precedence. You can use `false` to disable a rule that was set by a more general pattern: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ routeRules: { '/api/cached/**': { swr: true }, '/api/cached/no-cache': { cache: false, swr: false }, '/admin/**': { basicAuth: { username: 'admin', password: 'secret' } }, '/admin/public/**': { basicAuth: false }, } }); ``` ### Headers Set custom response headers for matching routes: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ routeRules: { '/api/**': { headers: { 'cache-control': 's-maxage=60' } }, '**': { headers: { 'x-powered-by': 'Nitro' } }, } }); ``` ### CORS Enable CORS headers with the `cors: true` shortcut. This sets `access-control-allow-origin: *`, `access-control-allow-methods: *`, `access-control-allow-headers: *`, and `access-control-max-age: 0`. You can override individual CORS headers using `headers`: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ routeRules: { '/api/v1/**': { cors: true, headers: { 'access-control-allow-methods': 'GET' }, }, } }); ``` ### Redirect Redirect matching routes to another URL. Use a string for a simple redirect (defaults to `307` status), or an object for more control: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ routeRules: { // Simple redirect (307 status) '/old-page': { redirect: '/new-page' }, // Redirect with custom status '/legacy': { redirect: { to: 'https://example.com/', status: 308 } }, // Wildcard redirect — preserves the path after the pattern '/old-blog/**': { redirect: 'https://blog.example.com/**' }, } }); ``` ### Proxy Proxy requests to another URL. Supports both internal and external targets: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ routeRules: { // Proxy to exact URL '/api/proxy/example': { proxy: 'https://example.com' }, // Proxy to internal route '/api/proxy/**': { proxy: '/api/echo' }, // Wildcard proxy — preserves the path after the pattern '/cdn/**': { proxy: 'https://cdn.jsdelivr.net/**' }, // Proxy with options '/external/**': { proxy: { to: 'https://api.example.com/**', // Additional H3 proxy options... }, }, } }); ``` ### Basic auth Protect routes with HTTP Basic Authentication: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ routeRules: { '/admin/**': { basicAuth: { username: 'admin', password: 'supersecret', realm: 'Admin Area', // Optional, shown in the browser prompt }, }, // Disable basic auth for a sub-path '/admin/public/**': { basicAuth: false }, } }); ``` ### Caching (SWR / Static) Control caching behavior with `cache`, `swr`, or `static` options: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ routeRules: { // Enable stale-while-revalidate caching '/blog/**': { swr: true }, // SWR with maxAge in seconds '/blog/posts/**': { swr: 600 }, // Full cache options '/api/data/**': { cache: { maxAge: 60, swr: true, // ...other cache options }, }, // Disable caching '/api/realtime/**': { cache: false }, } }); ``` ::tip `swr: true` is a shortcut for `cache: { swr: true }` and `swr: ` is a shortcut for `cache: { swr: true, maxAge: }`. :: ### Prerender Mark routes for prerendering at build time: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ routeRules: { '/about': { prerender: true }, '/dynamic/**': { prerender: false }, } }); ``` ### ISR (Vercel) Configure Incremental Static Regeneration for Vercel deployments: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ routeRules: { '/isr/**': { isr: true }, '/isr-ttl/**': { isr: 60 }, '/isr-custom/**': { isr: { expiration: 60, allowQuery: ['q'], group: 1, }, }, } }); ``` ### Route rules reference | Option | Type | Description | |--------|------|-------------| | `headers` | `Record` | Custom response headers | | `redirect` | `string \| { to: string, status?: number }` | Redirect to another URL (default status: `307`) | | `proxy` | `string \| { to: string, ...proxyOptions }` | Proxy requests to another URL | | `cors` | `boolean` | Enable permissive CORS headers | | `cache` | `object \| false` | Cache options (see [cache guide](/docs/cache)) | | `swr` | `boolean \| number` | Shortcut for `cache: { swr: true, maxAge: number }` | | `static` | `boolean \| number` | Shortcut for static caching | | `basicAuth` | `{ username, password, realm? } \| false` | HTTP Basic Authentication | | `prerender` | `boolean` | Enable/disable prerendering | | `isr` | `boolean \| number \| object` | Incremental Static Regeneration (Vercel) | ### Runtime route rules Route rules can be provided through `runtimeConfig`, allowing overrides via environment variables without rebuilding: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ runtimeConfig: { nitro: { routeRules: { '/api/**': { headers: { 'x-env': 'production' } }, }, }, }, }); ``` ## Config reference These config options control routing behavior: | Option | Type | Default | Description | |--------|------|---------|-------------| | `baseURL` | `string` | `"/"` | Base URL for all routes | | `apiBaseURL` | `string` | `"/api"` | Base URL for routes in the `api/` directory | | `apiDir` | `string` | `"api"` | Directory name for API routes | | `routesDir` | `string` | `"routes"` | Directory name for file-based routes | | `serverDir` | `string \| false` | `false` | Server directory for scanning routes, middleware, plugins, etc. | | `scanDirs` | `string[]` | `[]` | Additional directories to scan for routes | | `routes` | `Record` | `{}` | Route-to-handler mapping | | `handlers` | `NitroEventHandler[]` | `[]` | Programmatic handler registration (mainly for middleware) | | `routeRules` | `Record` | `{}` | Route rules for matching patterns | | `ignore` | `string[]` | `[]` | Glob patterns to ignore during file scanning | ================================================ FILE: docs/1.docs/50.assets.md ================================================ --- icon: ri:image-2-line --- # Assets Nitro supports two types of assets: **public assets** served directly to clients and **server assets** bundled into the server for programmatic access. ## Public Assets Nitro handles assets via the `public/` directory. All assets in `public/` directory will be automatically served. This means that you can access them directly from the browser without any special configuration. ```md public/ image.png <-- /image.png video.mp4 <-- /video.mp4 robots.txt <-- /robots.txt ``` ### Caching and Headers Public assets are served with automatic `ETag` and `Last-Modified` headers for conditional requests. When the client sends `If-None-Match` or `If-Modified-Since` headers, Nitro returns a `304 Not Modified` response. For assets served from a non-root `baseURL` (such as `/build/`), Nitro prevents fallthrough to application handlers. If a request matches a public asset base but the file is not found, a `404` is returned immediately. ### Production Public Assets When building your Nitro app, the `public/` directory will be copied to `.output/public/` and a manifest with metadata will be created and embedded in the server bundle. ```json { "/image.png": { "type": "image/png", "etag": "\"4a0c-6utWq0Kbk5OqDmksYCa9XV8irnM\"", "mtime": "2023-03-04T21:39:45.086Z", "size": 18956 }, "/robots.txt": { "type": "text/plain; charset=utf-8", "etag": "\"8-hMqyDrA8fJ0R904zgEPs3L55Jls\"", "mtime": "2023-03-04T21:39:45.086Z", "size": 8 }, "/video.mp4": { "type": "video/mp4", "etag": "\"9b943-4UwfQXKUjPCesGPr6J5j7GzNYGU\"", "mtime": "2023-03-04T21:39:45.085Z", "size": 637251 } } ``` This allows Nitro to know the public assets without scanning the directory, giving high performance with caching headers. ### Custom Public Asset Directories You can configure additional public asset directories using the `publicAssets` config option. Each entry supports the following properties: - `dir` -- Path to the directory (resolved relative to `rootDir`). - `baseURL` -- URL prefix for serving assets (default: `"/"`). - `maxAge` -- Cache `max-age` in seconds. When set, a `Cache-Control: public, max-age=, immutable` header is applied via route rules. - `fallthrough` -- Whether requests should fall through to application handlers when the asset is not found. Top-level (`baseURL: "/"`) directories default to `true`; non-root directories default to `false`. - `ignore` -- Pass `false` to disable ignore patterns, or an array of glob patterns to override the global `ignore` option. ```js [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ publicAssets: [ { baseURL: "build", dir: "public/build", maxAge: 3600, }, ], }); ``` In this example, files in `public/build/` are served under `/build/` with a one-hour cache and no fallthrough to application handlers. ### Compressed Public Assets Nitro can generate pre-compressed versions of your public assets during the build. When a client sends an `Accept-Encoding` header, the server will serve the compressed version if available. Supported encodings are gzip (`.gz`), brotli (`.br`), and zstd (`.zst`). Set `compressPublicAssets: true` to enable all encodings: ```js [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ compressPublicAssets: true, }); ``` Or pick specific encodings: ```js [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ compressPublicAssets: { gzip: true, brotli: true, zstd: false, }, }); ``` > [!NOTE] > Only compressible MIME types (text, JavaScript, JSON, XML, WASM, fonts, SVG, etc.) with a file size of at least 1 KB are compressed. Source map files (`.map`) are excluded. ## Server Assets All assets in `assets/` directory will be added to the server bundle. After building your application, you can find them in the `.output/server/chunks/raw/` directory. Be careful with the size of your assets, as they will be bundled with the server bundle. > [!TIP] > Unless using `useStorage()`, assets won't be included in the server bundle. They can be addressed by the `assets:server` mount point using the [storage layer](/docs/storage). For example, you could store a json file in `assets/data.json` and retrieve it in your handler: ```js import { defineHandler } from "nitro"; export default defineHandler(async () => { const data = await useStorage("assets:server").get("data.json"); return data; }); ``` ### Custom Server Assets In order to add assets from a custom directory, you will need to define a path in your nitro config. This allows you to add assets from a directory outside of the `assets/` directory. Each entry in `serverAssets` supports the following properties: - `baseName` -- Name used as the storage mount point (accessed via `assets:`). - `dir` -- Path to the directory (resolved relative to `rootDir`). - `pattern` -- Glob pattern for file inclusion (default: `"**/*"`). - `ignore` -- Array of glob patterns to exclude files. ```js [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ serverAssets: [ { baseName: "templates", dir: "./templates", }, ], }); ``` Then you can use the `assets:templates` base to retrieve your assets. ```ts [handlers/success.ts] import { defineHandler } from "nitro"; export default defineHandler(async (event) => { const html = await useStorage("assets:templates").get("success.html"); return html; }); ``` > [!TIP] > During development, server assets are read directly from the filesystem using the `fs` unstorage driver. In production, they are bundled into the server as lazy imports with pre-computed metadata (MIME type, ETag, modification time). ================================================ FILE: docs/1.docs/50.configuration.md ================================================ --- icon: ri:settings-3-line --- # Configuration > Customize and extend Nitro defaults. ::read-more{to="/config"} See [config reference](/config) for available options. :: ## Config file You can customize your Nitro builder with a configuration file. ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ // Nitro options }) ``` ```ts [vite.config.ts] import { defineConfig } from 'vite' import { nitro } from 'nitro/vite' export default defineConfig({ plugins: [ nitro() ], nitro: { // Nitro options } }) ``` > [!TIP] > Nitro loads the configuration using [c12](https://github.com/unjs/c12), giving more possibilities such as using `.nitrorc` file in current working directory or in the user's home directory. ### Environment-specific config Using [c12](https://github.com/unjs/c12) conventions, you can provide environment-specific overrides using `$development` and `$production` keys: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ logLevel: 3, $development: { // Options applied only in development mode debug: true, }, $production: { // Options applied only in production builds minify: true, }, }) ``` The environment name is `"development"` during `nitro dev` and `"production"` during `nitro build`. ### Extending configs You can extend from other configs or presets using the `extends` key: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ extends: "./base.config", }) ``` ### Config from `package.json` You can also provide Nitro configuration under the `nitro` key in your `package.json` file. ## Directory options Nitro provides several options for controlling directory structure: | Option | Default | Description | | --- | --- | --- | | `rootDir` | `.` (current directory) | The root directory of the project. | | `serverDir` | `false` | Server source directory (set to `"server"` or `"./"` to enable). | | `buildDir` | `node_modules/.nitro` | Directory for build artifacts. | | `output.dir` | `.output` | Production output directory. | | `output.serverDir` | `.output/server` | Server output directory. | | `output.publicDir` | `.output/public` | Public assets output directory. | ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ serverDir: "server", buildDir: "node_modules/.nitro", output: { dir: ".output", }, }) ``` > [!NOTE] > The `srcDir` option is deprecated. Use `serverDir` instead. ## Environment variables Certain Nitro behaviors can be configured using environment variables: | Variable | Description | | --- | --- | | `NITRO_PRESET` | Override the deployment preset. | | `NITRO_COMPATIBILITY_DATE` | Set the compatibility date. | | `NITRO_APP_BASE_URL` | Override the base URL (default: `/`). | ## Runtime configuration Nitro provides a runtime config API to expose configuration within your application, with the ability to update it at runtime by setting environment variables. This is useful when you want to expose different configuration values for different environments (e.g. development, staging, production). For example, you can use this to expose different API endpoints for different environments or to expose different feature flags. First, you need to define the runtime config in your configuration file. ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ runtimeConfig: { apiToken: "dev_token", // `dev_token` is the default value } }); ``` You can now access the runtime config using `useRuntimeConfig()`. ```ts [api/example.get.ts] import { defineHandler } from "nitro"; import { useRuntimeConfig } from "nitro/runtime-config"; export default defineHandler((event) => { return useRuntimeConfig().apiToken; // Returns `dev_token` }); ``` ### Nested objects Runtime config supports nested objects. Keys at any depth are mapped to environment variables using the `NITRO_` prefix and `UPPER_SNAKE_CASE` conversion: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ runtimeConfig: { database: { host: "localhost", port: 5432, }, }, }); ``` ```bash [.env] NITRO_DATABASE_HOST="db.example.com" NITRO_DATABASE_PORT="5433" ``` > [!NOTE] > Only keys defined in `runtimeConfig` in your config file will be considered. You cannot introduce new keys using environment variables alone. ### Serialization Runtime config values must be serializable (strings, numbers, booleans, plain objects, and arrays). Non-serializable values (class instances, functions, etc.) will trigger a warning at build time. Values that are `undefined` or `null` in the config are replaced with empty strings (`""`) as a fallback. ### Local development You can update the runtime config using environment variables. You can use a `.env` or `.env.local` file in development and use platform variables in production (see below). Create an `.env` file in your project root: ```bash [.env] NITRO_API_TOKEN="123" ``` Re-start the development server, fetch the `/api/example` endpoint and you should see `123` as the response instead of `dev_token`. > [!NOTE] > The `.env` and `.env.local` files are only loaded during development (`nitro dev`). In production, use your platform's native environment variable mechanism. Do not forget that you can still universally access environment variables using `import.meta.env` or `process.env` but avoid using them in ambient global contexts to prevent unexpected behavior. ### Production You can define variables in your production environment to update the runtime config. ::warning All variables must be prefixed with `NITRO_` to be applied to the runtime config. They will override the runtime config variables defined within your `nitro.config.ts` file. :: ```bash [.env] NITRO_API_TOKEN="123" ``` In runtime config, define key using camelCase. In environment variables, define key using snake_case and uppercase. ```ts { helloWorld: "foo" } ``` ```bash NITRO_HELLO_WORLD="foo" ``` ### Custom env prefix You can configure a secondary environment variable prefix using the `nitro.envPrefix` runtime config key. This prefix is checked in addition to the default `NITRO_` prefix: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ runtimeConfig: { nitro: { envPrefix: "APP_", }, apiToken: "", }, }); ``` With this configuration, both `NITRO_API_TOKEN` and `APP_API_TOKEN` will be checked as overrides. ### Env expansion When enabled, environment variable references using `{{VAR_NAME}}` syntax in runtime config string values are expanded at runtime: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ experimental: { envExpansion: true, }, runtimeConfig: { url: "https://{{APP_DOMAIN}}/api", }, }); ``` ```bash APP_DOMAIN="example.com" ``` At runtime, `useRuntimeConfig().url` will resolve to `"https://example.com/api"`. ================================================ FILE: docs/1.docs/50.database.md ================================================ --- icon: ri:database-2-line title: Database --- > Nitro provides a built-in and lightweight SQL database layer. The default database connection is **preconfigured** with [SQLite](https://db0.unjs.io/connectors/sqlite) and works out of the box for development mode and any Node.js compatible production deployments. By default, data will be stored in `.data/db.sqlite`. :read-more{to="https://db0.unjs.io" title="DB0 Documentation"} > [!IMPORTANT] > Database support is currently experimental. > Refer to the [db0 issues](https://github.com/unjs/db0/issues) for status and bug report. In order to enable database layer you need to enable experimental feature flag. ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ experimental: { database: true } }) ``` > [!TIP] > You can change default connection or define more connections to any of the [supported databases](https://db0.unjs.io/connectors/sqlite). > [!TIP] > You can integrate database instance to any of the [supported ORMs](https://db0.unjs.io/integrations). ## Usage ```ts [server.ts] import { defineHandler } from "nitro"; import { useDatabase } from "nitro/database"; export default defineHandler(async () => { const db = useDatabase(); // Create users table await db.sql`DROP TABLE IF EXISTS users`; await db.sql`CREATE TABLE IF NOT EXISTS users ("id" TEXT PRIMARY KEY, "firstName" TEXT, "lastName" TEXT, "email" TEXT)`; // Add a new user const userId = String(Math.round(Math.random() * 10_000)); await db.sql`INSERT INTO users VALUES (${userId}, 'John', 'Doe', '')`; // Query for users const { rows } = await db.sql`SELECT * FROM users WHERE id = ${userId}`; return { rows, }; }); ``` ### `useDatabase` Use `useDatabase` to get a database instance. It accepts an optional connection name (defaults to `"default"`). ```ts import { useDatabase } from "nitro/database"; // Use the default connection const db = useDatabase(); // Use a named connection const usersDb = useDatabase("users"); ``` > [!NOTE] > When `experimental.database` is enabled, `useDatabase` is auto-imported and available without an explicit import statement. Database instances are created lazily on first use and cached for subsequent calls with the same connection name. If a connection name is not configured, an error will be thrown. ### `db.sql` Execute SQL queries using tagged template literals with automatic parameter binding: ```ts const db = useDatabase(); // Insert with parameterized values (safe from SQL injection) const id = "1001"; await db.sql`INSERT INTO users VALUES (${id}, 'John', 'Doe', 'john@example.com')`; // Query with parameters const { rows } = await db.sql`SELECT * FROM users WHERE id = ${id}`; // The result includes rows, changes count, and last insert ID const result = await db.sql`INSERT INTO posts (title) VALUES (${"Hello"})`; // result.rows, result.changes, result.lastInsertRowid ``` ### `db.exec` Execute a raw SQL string directly: ```ts const db = useDatabase(); await db.exec("CREATE TABLE IF NOT EXISTS users (id TEXT PRIMARY KEY, name TEXT)"); ``` ### `db.prepare` Prepare an SQL statement for repeated execution: ```ts const db = useDatabase(); const stmt = db.prepare("SELECT * FROM users WHERE id = ?"); const result = await stmt.bind("1001").all(); ``` ## Configuration You can configure database connections using `database` config: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ database: { default: { connector: "sqlite", options: { name: "db" } }, users: { connector: "postgresql", options: { url: "postgresql://username:password@hostname:port/database_name" }, }, }, }); ``` ### Development Database Use the `devDatabase` config to override the database configuration **only for development mode**. This is useful for using a local SQLite database during development while targeting a different database in production. ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ database: { default: { connector: "postgresql", options: { url: "postgresql://username:password@hostname:port/database_name" } } }, devDatabase: { default: { connector: "sqlite", options: { name: "dev-db" } } } }); ``` > [!TIP] > When `experimental.database` is enabled and no `database` or `devDatabase` config is provided, Nitro automatically configures a default SQLite connection. In development mode, data is stored relative to the project root directory. In Node.js production, it uses the default SQLite path. ## Connectors Nitro supports all [db0 connectors](https://db0.unjs.io/connectors). The `connector` field in the database config accepts any of the following values: | Connector | Description | |---|---| | `sqlite` | Node.js built-in SQLite (alias for `node-sqlite`) | | `node-sqlite` | Node.js built-in SQLite | | `better-sqlite3` | [better-sqlite3](https://github.com/WiseLibs/better-sqlite3) | | `sqlite3` | [sqlite3](https://github.com/TryGhost/node-sqlite3) | | `bun` / `bun-sqlite` | Bun built-in SQLite | | `libsql` / `libsql-node` | [libSQL](https://github.com/tursodatabase/libsql) (Node.js) | | `libsql-http` | libSQL over HTTP | | `libsql-web` | libSQL for web environments | | `postgresql` | [PostgreSQL](https://github.com/porsager/postgres) | | `mysql2` | [MySQL](https://github.com/sidorares/node-mysql2) | | `pglite` | [PGlite](https://github.com/electric-sql/pglite) (embedded PostgreSQL) | | `planetscale` | [PlanetScale](https://github.com/planetscale/database-js) serverless | | `cloudflare-d1` | [Cloudflare D1](https://developers.cloudflare.com/d1/) | | `cloudflare-hyperdrive-mysql` | Cloudflare Hyperdrive with MySQL | | `cloudflare-hyperdrive-postgresql` | Cloudflare Hyperdrive with PostgreSQL | ================================================ FILE: docs/1.docs/50.lifecycle.md ================================================ --- icon: i-lucide-layers --- # Lifecycle > Understand how Nitro runs and serves incoming requests to your application. ## Request lifecycle A request can be intercepted and terminated (with or without a response) from any of these layers, in this order: ::steps ### `request` hook The `request` hook is the first code that runs for every incoming request. It is registered via a [server plugin](/docs/plugins): ```ts [plugins/request-hook.ts] import { definePlugin } from "nitro"; export default definePlugin((nitroApp) => { nitroApp.hooks.hook("request", (event) => { console.log(`Incoming request on ${event.path}`); }); }); ``` ::note Errors thrown inside the `request` hook are captured by the [`error` hook](#error-handling) and do not terminate the request pipeline. :: ### Static assets When static asset serving is enabled (the default for most presets), Nitro checks if the request matches a file in the `public/` directory **before** any other middleware or route handler runs. If a match is found, the static file is served immediately with appropriate `Content-Type`, `ETag`, `Last-Modified`, and `Cache-Control` headers. The request is terminated and no further middleware or routes are executed. Static assets also support content negotiation for pre-compressed files (gzip, brotli, zstd) via the `Accept-Encoding` header. ### Route rules The matching route rules defined in the Nitro config will execute. Route rules run as middleware so most of them alter the response without terminating it (for instance, adding a header or setting a cache policy). ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ routeRules: { '/**': { headers: { 'x-nitro': 'first' } } } }) ``` :read-more{to="/docs/routing#route-rules" title="Routing > Route rules"} ### Global middleware Any global middleware defined in the `middleware/` directory will be run: ```ts [middleware/info.ts] import { defineHandler } from "nitro"; export default defineHandler((event) => { event.context.info = { name: "Nitro" }; }); ``` ::warning Returning from a middleware will close the request and should be avoided when possible. :: ::read-more{to="/docs/routing#middleware"} Learn more about Nitro middleware. :: ### Routed middleware Middleware that targets a specific route pattern (defined with a `route` in `middleware/`) runs after global middleware but before the matched route handler. ### Routes Nitro will look at defined routes in the `routes/` folder to match the incoming request. ```ts [routes/api/hello.ts] export default (event) => ({ world: true }) ``` ::read-more{to="/docs/routing#filesystem-routing"} Learn more about Nitro file-system routing. :: If serverEntry is defined it will catch all requests not matching any other route acting as `/**` route handler. ```ts [server.ts] import { defineHandler } from "nitro"; export default defineHandler((event) => { if (event.path === "/") { return "Home page"; } }); ``` ::read-more{to="/docs/server-entry"} Learn more about Nitro server entry. :: ### Renderer If no route is matched, Nitro will look for a renderer handler (defined or auto-detected) to handle the request. ::read-more{to="/docs/renderer"} Learn more about Nitro renderer. :: ### `response` hook After the response is created (from any of the layers above), the `response` hook runs. This hook receives the final `Response` object and the event, and can be used to inspect or modify response headers: ```ts [plugins/response-hook.ts] import { definePlugin } from "nitro"; export default definePlugin((nitroApp) => { nitroApp.hooks.hook("response", (res, event) => { console.log(`Response ${res.status} for ${event.path}`); }); }); ``` ::note The `response` hook runs for every response, including static assets, middleware-terminated requests, and error responses. :: :: ## Error handling When an error occurs at any point in the request lifecycle, Nitro: 1. Calls the `error` hook with the error and context (including the event and source tags). 2. Passes the error to the **error handler** which converts it into an HTTP response. ```ts [plugins/errors.ts] import { definePlugin } from "nitro"; export default definePlugin((nitroApp) => { nitroApp.hooks.hook("error", (error, context) => { console.error("Captured error:", error); // context.event - the H3 event (if available) // context.tags - error source tags like "request", "response", "plugin" }); }); ``` Errors are also tracked per-request in `event.req.context.nitro.errors` for inspection in later hooks. You can provide a custom error handler in the Nitro config to control error response formatting: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ errorHandler: "~/error", }) ``` Additionally, unhandled promise rejections and uncaught exceptions at the process level are automatically captured into the `error` hook with the tags `"unhandledRejection"` and `"uncaughtException"`. ## Server shutdown When the Nitro server is shutting down, the `close` hook is called. Use this to clean up resources such as database connections, timers, or external service handles: ```ts [plugins/cleanup.ts] import { definePlugin } from "nitro"; export default definePlugin((nitroApp) => { nitroApp.hooks.hook("close", async () => { // Clean up resources }); }); ``` ## Hooks reference All runtime hooks are registered through [server plugins](/docs/plugins) using `nitroApp.hooks.hook()`. | Hook | Signature | When it runs | | --- | --- | --- | | `request` | `(event: HTTPEvent) => void \| Promise` | Start of each request, before routing. | | `response` | `(res: Response, event: HTTPEvent) => void \| Promise` | After the response is created, before it is sent. | | `error` | `(error: Error, context: { event?, tags? }) => void` | When any error is captured during the lifecycle. | | `close` | `() => void` | When the Nitro server is shutting down. | ::note The `NitroRuntimeHooks` interface is augmentable. Deployment presets (such as Cloudflare) can extend it with platform-specific hooks. :: ::read-more{to="/docs/plugins"} Learn more about Nitro plugins and hook usage examples. :: ================================================ FILE: docs/1.docs/50.plugins.md ================================================ --- icon: ri:plug-line --- # Plugins > Use plugins to extend Nitro's runtime behavior. Nitro plugins are **executed once** during server startup in order to allow extending Nitro's runtime behavior. They receive `nitroApp` context, which can be used to hook into lifecycle events. Plugins are auto-registered from the `plugins/` directory and run synchronously by file name order on the first Nitro initialization. Plugin functions themselves must be synchronous (return `void`), but the hooks they register can be async. **Example:** ```ts [plugins/test.ts] import { definePlugin } from "nitro"; export default definePlugin((nitroApp) => { console.log('Nitro plugin', nitroApp) }) ``` If you have plugins in another directory, you can use the `plugins` option: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ plugins: ['my-plugins/hello.ts'] }) ``` ## The `nitroApp` context The plugin function receives a `nitroApp` object with the following properties: | Property | Type | Description | | --- | --- | --- | | `hooks` | [`HookableCore`](https://github.com/unjs/hookable) | Hook system for registering lifecycle callbacks. | | `h3` | `H3Core` | The underlying [H3](https://github.com/h3js/h3) application instance. | | `fetch` | `(req: Request) => Response \| Promise` | The app's internal fetch handler. | | `captureError` | `(error: Error, context) => void` | Programmatically capture errors into the error hook pipeline. | ## Nitro runtime hooks You can use Nitro [hooks](https://github.com/unjs/hookable) to extend the default runtime behaviour of Nitro by registering custom functions to the lifecycle events within plugins. **Example:** ```ts import { definePlugin } from "nitro"; export default definePlugin((nitroApp) => { nitroApp.hooks.hook("close", async () => { // Will run when nitro is being closed }); }) ``` ### Available hooks | Hook | Signature | Description | | --- | --- | --- | | `request` | `(event: HTTPEvent) => void \| Promise` | Called at the start of each request. | | `response` | `(res: Response, event: HTTPEvent) => void \| Promise` | Called after the response is created. | | `error` | `(error: Error, context: { event?: HTTPEvent, tags?: string[] }) => void` | Called when an error is captured. | | `close` | `() => void` | Called when the Nitro server is shutting down. | > [!NOTE] > The `NitroRuntimeHooks` interface is augmentable. Deployment presets (such as Cloudflare) can extend it with platform-specific hooks like `cloudflare:scheduled` and `cloudflare:email`. ### Unregistering hooks The `hook()` method returns an unregister function that can be called to remove the hook: ```ts import { definePlugin } from "nitro"; export default definePlugin((nitroApp) => { const unregister = nitroApp.hooks.hook("request", (event) => { // ... }); // Later, remove the hook unregister(); }); ``` ## Examples ### Capturing errors You can use plugins to capture all application errors. ```ts import { definePlugin } from "nitro"; export default definePlugin((nitroApp) => { nitroApp.hooks.hook("error", async (error, { event }) => { console.error(`${event?.path} Application error:`, error) }); }) ``` The `context` object includes an optional `tags` array that identifies the error source (e.g., `"request"`, `"response"`, `"cache"`, `"plugin"`, `"unhandledRejection"`, `"uncaughtException"`). ### Programmatic error capture You can use `captureError` to manually feed errors into the error hook pipeline: ```ts import { definePlugin } from "nitro"; export default definePlugin((nitroApp) => { nitroApp.captureError(new Error("something went wrong"), { tags: ["startup"], }); }); ``` ### Graceful shutdown Server will gracefully shutdown and wait for any background pending tasks initiated by `event.waitUntil`. ```ts import { definePlugin } from "nitro"; export default definePlugin((nitroApp) => { nitroApp.hooks.hook("close", async () => { // Clean up resources, close connections, etc. }); }); ``` ### Request and response lifecycle You can use plugins to register hooks that run on the request lifecycle: ```ts import { definePlugin } from "nitro"; export default definePlugin((nitroApp) => { nitroApp.hooks.hook("request", (event) => { console.log("on request", event.path); }); nitroApp.hooks.hook("response", (res, event) => { // Modify or inspect the response console.log("on response", res.status); }); }); ``` ### Modifying response headers ```ts import { definePlugin } from "nitro"; export default definePlugin((nitroApp) => { nitroApp.hooks.hook("response", (res, event) => { const { pathname } = new URL(event.req.url); if (pathname.endsWith(".css") || pathname.endsWith(".js")) { res.headers.append("Vary", "Origin"); } }); }); ``` ================================================ FILE: docs/1.docs/50.tasks.md ================================================ --- icon: codicon:run-all --- # Tasks > Nitro tasks allow on-off operations in runtime. ## Opt-in to the experimental feature > [!IMPORTANT] > Tasks support is currently experimental. > See [nitrojs/nitro#1974](https://github.com/nitrojs/nitro/issues/1974) for the relevant discussion. In order to use the tasks API you need to enable experimental feature flag. ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ experimental: { tasks: true } }) ``` ## Define tasks Tasks can be defined in `tasks/[name].ts` files. Nested directories are supported. The task name will be joined with `:`. (Example: `tasks/db/migrate.ts` task name will be `db:migrate`) **Example:** ```ts [tasks/db/migrate.ts] export default defineTask({ meta: { name: "db:migrate", description: "Run database migrations", }, run({ payload, context }) { console.log("Running DB migration task..."); return { result: "Success" }; }, }); ``` ### Task interface The `defineTask` helper accepts an object with the following properties: - **`meta`** (optional): An object with optional `name` and `description` string fields used for display in the dev server and CLI. - **`run`** (required): A function that receives a [`TaskEvent`](#taskevent) and returns (or resolves to) an object with an optional `result` property. ```ts interface Task { meta?: { name?: string; description?: string }; run(event: TaskEvent): { result?: RT } | Promise<{ result?: RT }>; } ``` ### `TaskEvent` The `run` function receives a `TaskEvent` object with the following properties: - **`name`**: The name of the task being executed. - **`payload`**: An object (`Record`) containing any data passed to the task. - **`context`**: A `TaskContext` object (may include `waitUntil` depending on the runtime). ```ts interface TaskEvent { name: string; payload: TaskPayload; context: TaskContext; } ``` ### Registering tasks via config In addition to file-based scanning, tasks can be registered directly in the Nitro config. This is useful for tasks provided by modules or pointing to custom handler paths. ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ experimental: { tasks: true }, tasks: { "db:migrate": { handler: "./tasks/custom-migrate.ts", description: "Run database migrations" } } }) ``` If a task is both scanned from the `tasks/` directory and defined in the config, the config-defined `handler` takes precedence. ## Scheduled tasks You can define scheduled tasks using Nitro configuration to automatically run after each period of time. ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ scheduledTasks: { // Run `cms:update` task every minute '* * * * *': ['cms:update'], // Run a single task (string shorthand) '0 * * * *': 'db:cleanup' } }) ``` The `scheduledTasks` config maps cron expressions to either a single task name (string) or an array of task names. When multiple tasks are assigned to the same cron expression, they run in parallel. > [!TIP] > You can use [crontab.guru](https://crontab.guru/) to easily generate and understand cron tab patterns. When a scheduled task runs, it automatically receives a `payload` with `scheduledTime` set to the current timestamp (`Date.now()`). ### Platform support - **`dev`**, **`node_server`**, **`node_cluster`**, **`node_middleware`**, **`bun`** and **`deno_server`** presets are supported with the [croner](https://croner.56k.guru/) engine. - **`cloudflare_module`** and **`cloudflare_pages`** presets have native integration with [Cron Triggers](https://developers.cloudflare.com/workers/configuration/cron-triggers/). Nitro automatically generates the cron triggers in the wrangler config at build time - no manual wrangler setup required. - **`vercel`** preset has native integration with [Vercel Cron Jobs](https://vercel.com/docs/cron-jobs). Nitro automatically generates the cron job configuration at build time - no manual `vercel.json` setup required. You can secure cron endpoints by setting the `CRON_SECRET` environment variable. - More presets (with native primitives support) are planned to be supported! ## `waitUntil` When running background tasks, you might want to make sure the server or worker waits until the task is done. An optional `context.waitUntil` function _might_ be available depending on the runtime. ```ts export default defineTask({ run({ context }) { const promise = fetch(...) context.waitUntil?.(promise); await promise; return { result: "Success" }; }, }); ``` ## Programmatically run tasks To manually run tasks, you can use `runTask(name, { payload?, context? })` utility from `nitro/task`. **Example:** ```ts [api/migrate.ts] import { defineHandler } from "nitro"; export default defineHandler(async (event) => { // IMPORTANT: Authenticate user and validate payload! const payload = Object.fromEntries(event.url.searchParams); const { result } = await runTask("db:migrate", { payload }); return { result }; }); ``` ### Error handling `runTask` throws an HTTP error if: - The task does not exist (status `404`). - The task has no handler implementation (status `501`). Any errors thrown inside the task's `run` function will propagate to the caller. ## Run tasks with dev server Nitro's built-in dev server exposes tasks to be easily executed without programmatic usage. ### Using API routes #### `/_nitro/tasks` This endpoint returns a list of available task names and their meta. ```json // [GET] /_nitro/tasks { "tasks": { "db:migrate": { "description": "Run database migrations" }, "cms:update": { "description": "Update CMS content" } }, "scheduledTasks": [ { "cron": "* * * * *", "tasks": [ "cms:update" ] } ] } ``` #### `/_nitro/tasks/:name` This endpoint executes a task. You can provide a payload using both query parameters and body JSON payload. The payload sent in the JSON body payload must be under the `"payload"` property. ::code-group ```ts [tasks/echo/payload.ts] export default defineTask({ meta: { name: "echo:payload", description: "Returns the provided payload", }, run({ payload, context }) { console.log("Running echo task..."); return { result: payload }; }, }); ``` ```json [GET] // [GET] /_nitro/tasks/echo:payload?field=value&array=1&array=2 { "field": "value", "array": ["1", "2"] } ``` ```json [POST] /** * [POST] /_nitro/tasks/echo:payload?field=value * body: { * "payload": { * "answer": 42, * "nested": { * "value": true * } * } * } */ { "field": "value", "answer": 42, "nested": { "value": true } } ``` :: > [!NOTE] > The JSON payload included in the body will overwrite the keys present in the query params. ### Using CLI > [!IMPORTANT] > It is only possible to run these commands while the **dev server is running**. You should run them in a second terminal. #### List tasks ```sh nitro task list ``` #### Run a task ```sh nitro task run db:migrate --payload "{}" ``` The `--payload` flag accepts a JSON string that will be parsed and passed to the task. If the value is not a valid JSON object, the task runs without a payload. ## Notes ### Concurrency Each task can have **one running instance**. Calling a task of same name multiple times in parallel, results in calling it once and all callers will get the same return value. > [!NOTE] > Nitro tasks can be running multiple times and in parallel. ================================================ FILE: docs/1.docs/6.server-entry.md ================================================ --- icon: ri:server-line navigation: title: Server Entry --- # Nitro Server Entry > Use a server entry to create a global middleware that runs for all routes before they are matched. The server entry is a special handler in Nitro that acts as a global middleware, running for every incoming request before routes are matched. It's commonly used for cross-cutting concerns like authentication, logging, request preprocessing, or creating custom routing logic. ## Auto-detected `server.ts` By default, Nitro automatically looks for a `server.ts` (or `.js`, `.mjs`, `.mts`, `.tsx`, `.jsx`) file in your project root directory. If found, Nitro will use it as the server entry and run it for all incoming requests. ::code-group ```ts [server.ts] export default { async fetch(req: Request) { const url = new URL(req.url); // Handle specific routes if (url.pathname === "/health") { return new Response("OK", { status: 200, headers: { "content-type": "text/plain" } }); } // Add custom headers to all requests // Return nothing to continue to the next handler } } ``` ```ts [routes/api/hello.ts] import { defineHandler } from "nitro"; export default defineHandler((event) => { return { hello: "API" }; }); ``` :: ::tip When `server.ts` is detected, Nitro will log in the terminal: `Detected \`server.ts\` as server entry.` :: With this setup: - `/health` → Handled by server entry (returns a response) - `/api/hello` → Handled by the API route handler directly - `/about`, etc. → Server entry runs first, then continues to the renderer if no response is returned ## Framework compatibility The server entry is a great way to integrate with other frameworks. Any framework that exposes a standard Web `fetch(request: Request): Response` interface can be used as a server entry. ### Web-compatible frameworks Frameworks that implement the Web `fetch` API work directly with `server.ts`: ::tabs ::tabs-item{label="H3" icon="i-undocs-h3"} ```ts [server.ts] import { H3 } from "h3"; const app = new H3() app.get("/", () => "⚡️ Hello from H3!"); export default app; ``` :: ::tabs-item{label="Hono" icon="i-undocs-hono"} ```ts [server.ts] import { Hono } from "hono"; const app = new Hono(); app.get("/", (c) => c.text("🔥 Hello from Hono!")); export default app; ``` :: ::tabs-item{label="Elysia" icon="i-undocs-elysia"} ```ts [server.ts] import { Elysia } from "elysia"; const app = new Elysia(); app.get("/", () => "🦊 Hello from Elysia!"); export default app.compile(); ``` :: :: ### Node.js frameworks For Node.js frameworks that use `(req, res)` style handlers (like [Express](https://expressjs.com/) or [Fastify](https://fastify.dev/)), name your server entry file `server.node.ts` instead of `server.ts`. Nitro will automatically detect the `.node.` suffix and convert the Node.js handler to a web-compatible fetch handler using [`srvx`](https://srvx.h3.dev/). ::tabs ::tabs-item{label="Express"} ```ts [server.node.ts] import Express from "express"; const app = Express(); app.use("/", (_req, res) => { res.send("Hello from Express with Nitro!"); }); export default app; ``` :: ::tabs-item{label="Fastify"} ```ts [server.node.ts] import Fastify from "fastify"; const app = Fastify(); app.get("/", () => "Hello, Fastify with Nitro!"); await app.ready(); export default app.routing; ``` :: :: ## Configuration ### Custom server entry file You can specify a custom server entry file using the `serverEntry` option in your Nitro configuration: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ serverEntry: "./nitro.server.ts" }) ``` You can also provide an object with `handler` and `format` options: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ serverEntry: { handler: "./server.ts", format: "node" // "web" (default) or "node" } }) ``` ### Handler format The `format` option controls how Nitro treats the default export of your server entry: - **`"web"`** (default) — Expects a Web-compatible handler with a `fetch(request: Request): Response` method. - **`"node"`** — Expects a Node.js-style `(req, res)` handler. Nitro automatically converts it to a web-compatible handler. When auto-detecting, the format is determined by the filename: `server.node.ts` uses `"node"` format, while `server.ts` uses `"web"` format. ### Disabling server entry Set `serverEntry` to `false` to disable auto-detection and prevent Nitro from using any server entry: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ serverEntry: false }) ``` ## Using event handler You can also export an event handler using `defineHandler` for better type inference and access to the h3 event object: ```ts [server.ts] import { defineHandler } from "nitro"; export default defineHandler((event) => { // Add custom context event.context.requestId = crypto.randomUUID(); event.context.timestamp = Date.now(); // Log the request console.log(`[${event.context.requestId}] ${event.method} ${event.path}`); // Continue to the next handler (don't return anything) }); ``` ::important If your server entry returns `undefined` or doesn't return anything, the request will continue to be processed by routes and the renderer. If it returns a response, the request lifecycle stops there. :: ## Request lifecycle The server entry is registered as a catch-all (`/**`) route handler. When a specific route (like `/api/hello`) matches a request, that route handler takes priority. For requests that don't match any specific route, the server entry runs before the renderer: ```md 1. Server hook: `request` 2. Route rules (headers, redirects, etc.) 3. Global middleware (middleware/) 4. Route matching: a. Specific routes (routes/) ← if matched, handles the request b. Server entry ← runs for unmatched routes c. Renderer (renderer.ts or index.html) ``` When both a server entry and a renderer exist, they are chained: the server entry runs first, and if it doesn't return a response, the renderer handles the request. ## Development mode During development, Nitro watches for changes to your server entry file. When the file is created, modified, or deleted, the dev server automatically reloads to pick up the changes. ## Best practices - Use server entry for cross-cutting concerns that affect **all routes** - Return `undefined` to continue processing, return a response to terminate - Keep server entry logic lightweight for better performance - Use global middleware for modular concerns instead of one large server entry - Consider using [Nitro plugins](/docs/plugins) for initialization logic - Avoid heavy computation in server entry (it runs for every request) - Don't use server entry for route-specific logic (use route handlers instead as they are more performant) ================================================ FILE: docs/1.docs/7.cache.md ================================================ --- icon: ri:speed-line --- # Cache > Nitro provides a caching system built on top of the storage layer, powered by [ocache](https://github.com/unjs/ocache). ## Cached handlers To cache an event handler, you simply need to use the `defineCachedHandler` method. It works like `defineHandler` but with an second parameter for the [cache options](#options). ```ts [routes/cached.ts] import { defineCachedHandler } from "nitro/cache"; export default defineCachedHandler((event) => { return "I am cached for an hour"; }, { maxAge: 60 * 60 }); ``` With this example, the response will be cached for 1 hour and a stale value will be sent to the client while the cache is being updated in the background. If you want to immediately return the updated response set `swr: false`. See the [options](#options) section for more details about the available options. ::important **Request headers are dropped** when handling cached responses. Use the [`varies` option](#options) to consider specific headers when caching and serving the responses. :: ### Automatic HTTP headers When using `defineCachedHandler`, Nitro automatically manages HTTP cache headers on cached responses: - **`etag`** -- A weak ETag (`W/"..."`) is generated from the response body hash if not already set by the handler. - **`last-modified`** -- Set to the current time when the response is first cached, if not already set. - **`cache-control`** -- Automatically set based on the `swr`, `maxAge`, and `staleMaxAge` options: - With `swr: true`: `s-maxage=, stale-while-revalidate=` - With `swr: false`: `max-age=` ### Conditional requests (304 Not Modified) Cached handlers automatically support conditional requests. When a client sends `if-none-match` or `if-modified-since` headers matching the cached response, Nitro returns a `304 Not Modified` response without a body. ### Request method filtering Only `GET` and `HEAD` requests are cached. All other HTTP methods (`POST`, `PUT`, `DELETE`, etc.) automatically bypass the cache and call the handler directly. ### Request deduplication When multiple concurrent requests hit the same cache key while the cache is being resolved, only one invocation of the handler runs. All concurrent requests wait for and share the same result. ## Cached functions You can also cache a function using the `defineCachedFunction` function. This is useful for caching the result of a function that is not an event handler, but is part of one, and reusing it in multiple handlers. For example, you might want to cache the result of an API call for one hour: ```ts [routes/api/stars/[...repo\\].ts] import { defineCachedFunction } from "nitro/cache"; import { defineHandler, type H3Event } from "nitro"; export default defineHandler(async (event) => { const { repo } = event.context.params; const stars = await cachedGHStars(repo).catch(() => 0) return { repo, stars } }); const cachedGHStars = defineCachedFunction(async (repo: string) => { const data = await fetch(`https://api.github.com/repos/${repo}`).then(res => res.json()); return data.stargazers_count; }, { maxAge: 60 * 60, name: "ghStars", getKey: (repo: string) => repo }); ``` The stars will be cached in development inside `.nitro/cache/functions/ghStars//.json` with `value` being the number of stars. ```json {"expires":1677851092249,"value":43991,"mtime":1677847492540,"integrity":"ZUHcsxCWEH"} ``` ::important Because the cached data is serialized to JSON, it is important that the cached function does not return anything that cannot be serialized, such as Symbols, Maps, Sets... :: ::callout If you are using edge workers to host your application, you should follow the instructions below. ::collapsible{name="Edge workers instructions"} In edge workers, the instance is destroyed after each request. Nitro automatically uses `event.waitUntil` to keep the instance alive while the cache is being updated while the response is sent to the client. To ensure that your cached functions work as expected in edge workers, **you should always pass the `event` as the first argument to the function using `defineCachedFunction`.** ```ts [routes/api/stars/[...repo\\].ts] {5,10,17} import { defineCachedFunction } from "nitro/cache"; export default defineHandler(async (event) => { const { repo } = event.context.params; const stars = await cachedGHStars(event, repo).catch(() => 0) return { repo, stars } }); const cachedGHStars = defineCachedFunction(async (event: H3Event, repo: string) => { const data = await fetch(`https://api.github.com/repos/${repo}`).then(res => res.json()); return data.stargazers_count; }, { maxAge: 60 * 60, name: "ghStars", getKey: (event: H3Event, repo: string) => repo }); ``` This way, the function will be able to keep the instance alive while the cache is being updated without slowing down the response to the client. :: :: ## Using route rules This feature enables you to add caching routes based on a glob pattern directly in the main configuration file. This is especially useful to have a global cache strategy for a part of your application. Cache all the blog routes for 1 hour with `stale-while-revalidate` behavior: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ routeRules: { "/blog/**": { cache: { maxAge: 60 * 60 } }, }, }); ``` If we want to use a [custom cache storage](#cache-storage) mount point, we can use the `base` option. ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ storage: { redis: { driver: "redis", url: "redis://localhost:6379", }, }, routeRules: { "/blog/**": { cache: { maxAge: 60 * 60, base: "redis" } }, }, }); ``` ### Route rules shortcuts You can use the `swr` shortcut for enabling `stale-while-revalidate` caching on route rules. When set to `true`, SWR is enabled with the default `maxAge`. When set to a number, it is used as the `maxAge` value in seconds. ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ routeRules: { "/blog/**": { swr: true }, "/api/**": { swr: 3600 }, }, }); ``` To explicitly disable caching on a route, set `cache: false`: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ routeRules: { "/api/realtime/**": { cache: false }, }, }); ``` ::note When using route rules, cached handlers use the group `'nitro/route-rules'` instead of the default `'nitro/handlers'`. :: ## Cache storage Nitro stores the data in the `cache` storage mount point. - In production, it will use the [memory driver](https://unstorage.unjs.io/drivers/memory) by default. - In development, it will use the [filesystem driver](https://unstorage.unjs.io/drivers/fs), writing to a temporary dir (`.nitro/cache`). To overwrite the production storage, set the `cache` mount point using the `storage` option: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ storage: { cache: { driver: 'redis', /* redis connector options */ } } }) ``` In development, you can also overwrite the cache mount point using the `devStorage` option: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ storage: { cache: { // production cache storage }, }, devStorage: { cache: { // development cache storage } } }) ``` ## Options The `defineCachedHandler` and `defineCachedFunction` functions accept the following options: ### Shared options These options are available for both `defineCachedHandler` and `defineCachedFunction`: ::field-group ::field{name="base" type="string"} Name of the storage mountpoint to use for caching. :br Default to `cache`. :: ::field{name="name" type="string"} Guessed from function name if not provided, and falls back to `'_'` otherwise. :: ::field{name="group" type="string"} Defaults to `'nitro/handlers'` for handlers and `'nitro/functions'` for functions. :: ::field{name="getKey()" type="(...args) => string"} A function that accepts the same arguments as the original function and returns a cache key (`String`). :br If not provided, a built-in hash function will be used to generate a key based on the function arguments. For cached handlers, the key is derived from the request URL path and search params. :: ::field{name="integrity" type="string"} A value that invalidates the cache when changed. :br By default, it is computed from **function code**, used in development to invalidate the cache when the function code changes. :: ::field{name="maxAge" type="number"} Maximum age that cache is valid, in seconds. :br Default to `1` (second). :: ::field{name="staleMaxAge" type="number"} Maximum age that a stale cache is valid, in seconds. If set to `-1` a stale value will still be sent to the client while the cache updates in the background. :br Defaults to `0` (disabled). :: ::field{name="swr" type="boolean"} Enable `stale-while-revalidate` behavior to serve a stale cached response while asynchronously revalidating it. :br When enabled, stale cached values are returned immediately while revalidation happens in the background. When disabled, the caller waits for the fresh value before responding (the stale entry is cleared). :br Defaults to `true`. :: ::field{name="shouldInvalidateCache()" type="(...args) => boolean | Promise"} A function that returns a `boolean` to invalidate the current cache and create a new one. :: ::field{name="shouldBypassCache()" type="(...args) => boolean | Promise"} A function that returns a `boolean` to bypass the current cache without invalidating the existing entry. :: ::field{name="onError()" type="(error: unknown) => void"} A custom error handler called when the cached function throws. :br By default, errors are logged to the console and captured by the Nitro error handler. :: :: ### Handler-only options These options are only available for `defineCachedHandler`: ::field-group ::field{name="headersOnly" type="boolean"} When `true`, skip full response caching and only handle conditional request headers (`if-none-match`, `if-modified-since`) for `304 Not Modified` responses. The handler is called on every request but benefits from conditional caching. :: ::field{name="varies" type="string[]"} An array of request header names to vary the cache key on. Headers listed here are preserved on the request during cache resolution and included in the cache key, making the cache unique per combination of header values. :br :br Headers **not** listed in `varies` are stripped from the request before calling the handler to ensure consistent cache hits. :br :br For multi-tenant environments, you may want to pass `['host', 'x-forwarded-host']` to ensure these headers are not discarded and that the cache is unique per tenant. :: :: ### Function-only options These options are only available for `defineCachedFunction`: ::field-group ::field{name="transform()" type="(entry: CacheEntry, ...args) => any"} Transform the cache entry before returning. The return value replaces the cached value. :: ::field{name="validate()" type="(entry: CacheEntry, ...args) => boolean"} Validate a cache entry. Return `false` to treat the entry as invalid and trigger re-resolution. :: :: ## SWR behavior The `stale-while-revalidate` (SWR) pattern is enabled by default (`swr: true`). Understanding how it interacts with other options: | `swr` | `maxAge` | Behavior | |-------|----------|----------| | `true` (default) | `1` (default) | Cache for 1 second, serve stale while revalidating | | `true` | `3600` | Cache for 1 hour, serve stale while revalidating | | `false` | `3600` | Cache for 1 hour, wait for fresh value when expired | | `true` | `3600` with `staleMaxAge: 600` | Cache for 1 hour, serve stale for up to 10 minutes while revalidating | When `swr` is enabled and a cached value exists but has expired: 1. The stale cached value is returned immediately to the client. 2. The function/handler is called in the background to refresh the cache. 3. On edge workers, `event.waitUntil` is used to keep the background refresh alive. When `swr` is disabled and a cached value has expired: 1. The stale entry is cleared. 2. The client waits for the function/handler to resolve with a fresh value. ## Cache keys and invalidation When using the `defineCachedFunction` or `defineCachedHandler` functions, the cache key is generated using the following pattern: ```ts `${options.base}:${options.group}:${options.name}:${options.getKey(...args)}.json` ``` For example, the following function: ```ts import { defineCachedFunction } from "nitro/cache"; const getAccessToken = defineCachedFunction(() => { return String(Date.now()) }, { maxAge: 10, name: "getAccessToken", getKey: () => "default" }); ``` Will generate the following cache key: ```ts cache:nitro/functions:getAccessToken:default.json ``` You can invalidate the cached function entry with: ```ts import { useStorage } from "nitro/storage"; await useStorage('cache').removeItem('nitro/functions:getAccessToken:default.json') ``` ::note For cached handlers, the cache key includes a hash of the URL path and, when using the [`varies`](#handler-only-options) option, hashes of the specified header values appended to the key. :: ::note Responses with HTTP status codes `>= 400` or with an undefined body are not cached. This prevents caching error responses. :: ::read-more{to="/docs/storage"} Read more about the Nitro storage. :: ================================================ FILE: docs/1.docs/8.storage.md ================================================ --- icon: carbon:datastore --- # KV Storage > Nitro provides a built-in storage layer that can abstract filesystem or database or any other data source. Nitro has built-in integration with [unstorage](https://unstorage.unjs.io) to provide a runtime agnostic persistent layer. ## Usage To use the storage layer, you can use the `useStorage()` utility to access the storage instance. ```ts import { useStorage } from "nitro/storage"; // Default storage (in-memory) await useStorage().setItem("test:foo", { hello: "world" }); const value = await useStorage().getItem("test:foo"); // You can specify a base prefix with useStorage(base) const testStorage = useStorage("test"); await testStorage.setItem("foo", { hello: "world" }); await testStorage.getItem("foo"); // { hello: "world" } // You can use generics to type the return value await useStorage<{ hello: string }>("test").getItem("foo"); await useStorage("test").getItem<{ hello: string }>("foo"); ``` :read-more{to="https://unstorage.unjs.io"} ### Available methods The storage instance returned by `useStorage()` provides the following methods: | Method | Description | |---|---| | `getItem(key)` | Get the value of a key. Returns `null` if the key does not exist. | | `getItems(items)` | Get multiple items at once. Accepts an array of keys or `{ key, options }` objects. | | `getItemRaw(key)` | Get the raw value of a key without parsing. Useful for binary data. | | `setItem(key, value)` | Set the value of a key. | | `setItems(items)` | Set multiple items at once. Accepts an array of `{ key, value }` objects. | | `setItemRaw(key, value)` | Set the raw value of a key without serialization. | | `hasItem(key)` | Check if a key exists. Returns a boolean. | | `removeItem(key)` | Remove a key from storage. | | `getKeys(base?)` | Get all keys, optionally filtered by a base prefix. | | `clear(base?)` | Clear all keys, optionally filtered by a base prefix. | | `getMeta(key)` | Get metadata for a key (e.g., `mtime`, `atime`, `ttl`). | | `setMeta(key, meta)` | Set metadata for a key. | | `removeMeta(key)` | Remove metadata for a key. | | `mount(base, driver)` | Dynamically mount a storage driver at a base path. | | `unmount(base)` | Unmount a storage driver from a base path. | | `watch(callback)` | Watch for changes. Callback receives `(event, key)` where event is `"update"` or `"remove"`. | | `unwatch()` | Stop watching for changes. | Shorthand aliases are also available: `get`, `set`, `has`, `del`, `remove`, `keys`. ```ts import { useStorage } from "nitro/storage"; // Get all keys under a prefix const keys = await useStorage("test").getKeys(); // Check if a key exists const exists = await useStorage().hasItem("test:foo"); // Remove a key await useStorage().removeItem("test:foo"); // Get raw binary data const raw = await useStorage().getItemRaw("assets/server:image.png"); // Get metadata (type, etag, mtime, etc.) const meta = await useStorage("assets/server").getMeta("file.txt"); ``` ## Configuration You can mount one or multiple custom storage drivers using the `storage` option. The key is the mount point name, and the value is the driver name and configuration. ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ storage: { redis: { driver: "redis", /* redis connector options */ } } }) ``` Then, you can use the redis storage using the `useStorage("redis")` function. ::read-more{to="https://unstorage.unjs.io/"} You can find the driver list on [unstorage documentation](https://unstorage.unjs.io/) with their configuration. :: ### Development storage You can use the `devStorage` option to override storage configuration during development and prerendering. This is useful when your production driver is not available in development (e.g., a managed Redis instance). ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ storage: { db: { driver: "redis", host: "prod.example.com", } }, devStorage: { db: { driver: "fs", base: "./.data/db" } } }) ``` When running in development mode, `devStorage` mounts are merged on top of `storage` mounts, allowing you to use a local filesystem driver or an in-memory driver while developing. ## Built-in mount points Nitro automatically mounts the following storage paths: ### `/assets` Server assets are mounted at the `/assets` base path. This mount point provides read-only access to bundled server assets (see [Server assets](#server-assets)). ```ts import { useStorage } from "nitro/storage"; // Access server assets via the /assets mount const content = await useStorage("assets/server").getItem("my-file.txt"); ``` ### Default (in-memory) The root storage (without a base path) uses an in-memory driver by default. Data stored here is not persisted across restarts. ```ts import { useStorage } from "nitro/storage"; // In-memory by default, not persisted await useStorage().setItem("counter", 1); ``` To persist data, mount a driver with a persistent backend (e.g., `fs`, `redis`, etc.) using the `storage` configuration option. ## Server assets Nitro allows you to bundle files from an `assets/` directory at the root of your project. These files are accessible at runtime via the `assets/server` storage mount. ``` my-project/ assets/ data.json templates/ welcome.html server/ routes/ index.ts ``` ```ts [server/routes/index.ts] import { useStorage } from "nitro/storage"; export default defineHandler(async () => { const serverAssets = useStorage("assets/server"); const keys = await serverAssets.getKeys(); const data = await serverAssets.getItem("data.json"); const template = await serverAssets.getItem("templates/welcome.html"); return { keys, data, template }; }); ``` ### Custom asset directories You can register additional asset directories using the `serverAssets` config option: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ serverAssets: [ { baseName: "templates", dir: "./templates", } ] }) ``` Custom asset directories are accessible under `assets/`: ```ts import { useStorage } from "nitro/storage"; const templates = useStorage("assets/templates"); const keys = await templates.getKeys(); const html = await templates.getItem("email.html"); ``` ### Asset metadata Server assets include metadata such as content type, ETag, and modification time: ```ts import { useStorage } from "nitro/storage"; const serverAssets = useStorage("assets/server"); const meta = await serverAssets.getMeta("image.png"); // { type: "image/png", etag: "\"...\"", mtime: "2024-01-01T00:00:00.000Z" } // Useful for setting response headers const raw = await serverAssets.getItemRaw("image.png"); ``` ::note In development, server assets are read directly from the filesystem. In production, they are bundled and inlined into the build output. :: ## Runtime configuration In scenarios where the mount point configuration is not known until runtime, Nitro can dynamically add mount points during startup using [plugins](/docs/plugins). ```ts [plugins/storage.ts] import { useStorage } from "nitro/storage"; import { definePlugin } from "nitro"; import redisDriver from "unstorage/drivers/redis"; export default definePlugin(() => { const storage = useStorage() // Dynamically pass in credentials from runtime configuration, or other sources const driver = redisDriver({ base: "redis", host: process.env.REDIS_HOST, port: process.env.REDIS_PORT, /* other redis connector options */ }) // Mount driver storage.mount("redis", driver) }) ``` ::warning This is a temporary workaround, with a better solution coming in the future! Keep a lookout on the GitHub issue [here](https://github.com/nitrojs/nitro/issues/1161#issuecomment-1511444675). :: ================================================ FILE: docs/1.docs/99.migration.md ================================================ --- icon: ri:arrow-right-up-line --- # Migration Guide > [!NOTE] > This is a living document for migrating from Nitro 2 to 3. Please check it regularly while using the beta version. Nitro v3 introduces intentional backward-incompatible changes. This guide helps you migrate from Nitro v2. ## `nitropack` is renamed to `nitro` The NPM package [nitropack](https://www.npmjs.com/package/nitropack) (v2) has been renamed to [nitro](https://www.npmjs.com/package/nitro) (v3). **Migration:** Update the `nitropack` dependency to `nitro` in `package.json`: ```diff [release channel] { "dependencies": { -- "nitropack": "latest" ++ "nitro": "latest" } } ``` ```diff [nightly channel] { "dependencies": { -- "nitropack": "latest" ++ "nitro": "npm:nitro-nightly" } } ``` **Migration:** Search your codebase and rename all instances of nitropack to nitro: ```diff -- import { defineNitroConfig } from "nitropack/config" ++ import { defineNitroConfig } from "nitro/config" ``` ## nitro/runtime Runtime utils had been moved to individual `nitro/*` subpath exports. Refer to docs for usage. ```diff -- import { useStorage } from "nitropack/runtime/storage" ++ import { useStorage } from "nitro/storage" ``` ## Minimum Supported Node.js Version: 20 Nitro now requires a minimum Node.js version of 20, as Node.js 18 reaches end-of-life in [April 2025](https://nodejs.org/en/about/previous-releases). Please upgrade to the [latest LTS](https://nodejs.org/en/download) version (>= 20). **Migration:** - Check your local Node.js version using `node --version` and update if necessary. - If you use a CI/CD system for deployment, ensure that your pipeline is running Node.js 20 or higher. - If your hosting provider manages the Node.js runtime, make sure it's set to version 20, 22, or later. ## Type Imports Nitro types are now only exported from `nitro/types`. **Migration:** Import types from nitro/types instead of nitro: ```diff -- import { NitroRuntimeConfig } from "nitropack" ++ import { NitroRuntimeConfig } from "nitro/types" ``` ## App Config Support Removed Nitro v2 supported a bundled app config that allowed defining configurations in `app.config.ts` and accessing them at runtime via `useAppConfig()`. This feature had been removed. **Migration:** Use a regular `.ts` file in your server directory and import it directly. ## Preset updates Nitro presets have been updated for the latest compatibility. Some (legacy) presets have been removed or renamed. | Old Preset | New Preset | |------------------------------|-------------------------------| | `node` | `node_middleware` (export changed to `middleware`) | | `cloudflare`, `cloudflare_worker`, `cloudflare_module_legacy` | `cloudflare_module` | | `deno-server-legacy` | `deno_server` with Deno v2 | | `netlify-builder` | `netlify` or `netlify_edge` | | `vercel-edge` | `vercel` with Fluid compute enabled | | `azure`, `azure_functions` | `azure_swa` | | `firebase` | `firebase_app_hosting` | | `iis` | `iis_handler` | | `deno` | `deno_deploy` | | `edgio` | Discontinued | | `cli` | Removed due to lack of use | | `service_worker` | Removed due to instability | ## Cloudflare Bindings Access In Nitro v2, Cloudflare environment variables and bindings were accessible via `event.context.cloudflare.env`. In Nitro v3, the Cloudflare runtime context is attached to the request's runtime object instead. **Migration:** ```diff -- const { cloudflare } = event.context -- const binding = cloudflare.env.MY_BINDING ++ const { env } = event.req.runtime.cloudflare ++ const binding = env.MY_BINDING ``` ## Changed nitro subpath imports Nitro v2 introduced multiple subpath exports, some of which have been removed or updated: - `nitro/rollup`, `nitropack/core` (use `nitro/builder`) - `nitropack/runtime/*` (use `nitro/*`) - `nitropack/kit` (removed) - `nitropack/presets` (removed) An experimental `nitropack/kit` was introduced but has now been removed. A standalone Nitro Kit package may be introduced in the future with clearer objectives. **Migration:** - Use `NitroModule` from `nitro/types` instead of `defineNitroModule` from the kit. - Prefer built-in Nitro presets (external presets are only for evaluation purposes). ## H3 v2 Nitro v3 upgrades to [H3 v2](https://h3.dev), which includes API changes. All H3 utilities are imported from `nitro/h3`. ### Web Standards H3 v2 is rewritten based on web standard primitives ([`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL), [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers), [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request), and [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)). Access to `event.node.{req,res}` is only available in Node.js runtime. `event.web` is renamed to `event.req` (instance of web [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request)). ### Response Handling You should always explicitly **return** the response body or **throw** an error: ```diff -- import { send, sendRedirect, sendStream } from "nitro/h3" -- send(event, value) -- sendStream(event, stream) -- sendRedirect(event, location, code) ++ import { redirect } from "nitro/h3" ++ return value ++ return stream ++ return redirect(event, location, code) ``` Other changes: - `sendError(event, error)` → `throw createError(error)` - `sendNoContent(event)` → `return noContent(event)` - `sendProxy(event, target)` → `return proxy(event, target)` ### Request Body Most body utilities can be replaced with native `event.req` methods: ```diff -- import { readBody, readRawBody, readFormData } from "nitro/h3" ++ // Use native Request methods ++ const json = await event.req.json() ++ const text = await event.req.text() ++ const formData = await event.req.formData() ++ const stream = event.req.body ``` ### Headers H3 now uses standard web [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers). Header values are always plain `string` (no `null`, `undefined`, or `string[]`). ```diff -- import { getHeader, setHeader, getResponseStatus } from "nitro/h3" -- getHeader(event, "x-foo") -- setHeader(event, "x-foo", "bar") ++ event.req.headers.get("x-foo") ++ event.res.headers.set("x-foo", "bar") ++ event.res.status // instead of getResponseStatus(event) ``` ### Handler Utils ```diff -- import { eventHandler, defineEventHandler } from "nitro/h3" ++ import { defineHandler } from "nitro" ``` - `lazyEventHandler` → `defineLazyEventHandler` - `useBase` → `withBase` ### Error Utils ```diff -- import { createError, isError } from "nitro/h3" ++ import { HTTPError } from "nitro" ++ throw new HTTPError({ status: 404, message: "Not found" }) ++ HTTPError.isError(error) ``` ### Node.js Utils ```diff -- import { defineNodeListener, fromNodeMiddleware, toNodeListener } from "nitro/h3" ++ import { defineNodeHandler, fromNodeHandler, toNodeHandler } from "nitro/h3" ``` ## Optional Hooks If you were using `useNitroApp().hooks` outside of Nitro plugins before, it might be undefined. Use new `useNitroHooks()` to guarantee having an instance. ================================================ FILE: docs/1.docs/99.nightly.md ================================================ --- icon: ri:moon-fill --- # Nightly Channel > Nitro has a nightly release channel that automatically releases for every commit to `main` branch to try latest changes. You can opt-in to the nightly release channel by updating your `package.json`: ```json { "devDependencies": { "nitro": "npm:nitro-nightly@latest" } } ``` Remove the lockfile (`package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`, `bun.lock`, or `bun.lockb`) and reinstall the dependencies. ::important When using **Bun as package manager** in a mono-repo, you need to make sure nitro package is properly hoisted.
```toml [bunfig.toml] [install] publicHoistPattern = ["nitro*"] ``` :: ::important Avoid using ` install nitro-nightly`; it does not install correctly. If you encounter issues, delete your `node_modules` and lock files, then follow the steps above. :: ================================================ FILE: docs/2.deploy/0.index.md ================================================ --- icon: ri:upload-cloud-2-line --- # Deploy > Learn more about Nitro deploy providers. Nitro can generate different output formats suitable for different hosting providers from the same code base. Using built-in presets, you can easily configure Nitro to adjust its output format with almost no additional code or configuration! ## Default output The default production output preset is [Node.js server](/deploy/runtimes/node). When running Nitro in development mode, Nitro will always use a special preset called `nitro-dev` using Node.js with ESM in an isolated Worker environment with behavior as close as possible to the production environment. ## Zero-Config Providers When deploying to production using CI/CD, Nitro tries to automatically detect the provider environment and set the right one without any additional configuration required. Currently, the providers below can be auto-detected with zero config. - [aws amplify](/deploy/providers/aws-amplify) - [azure](/deploy/providers/azure) - [cloudflare](/deploy/providers/cloudflare) - [firebase app hosting](/deploy/providers/firebase#firebase-app-hosting) - [netlify](/deploy/providers/netlify) - [stormkit](/deploy/providers/stormkit) - [vercel](/deploy/providers/vercel) - [zeabur](/deploy/providers/zeabur) ::warning For Turborepo users, zero config detection will be interferenced by its Strict Environment Mode. You may need to allowing the variables explictly or use its Loose Environment Mode (with `--env-mode=loose` flag). :: Other built-in providers are available with an explicit preset, including [zephyr](/deploy/providers/zephyr). ## Changing the deployment preset If you need to build Nitro against a specific provider, you can target it by defining an environment variable named `NITRO_PRESET` or `SERVER_PRESET`, or by updating your Nitro [configuration](/docs/configuration) or using `--preset` argument. Using the environment variable approach is recommended for deployments depending on CI/CD. **Example:** Defining a `NITRO_PRESET` environment variable ```bash nitro build --preset cloudflare_pages ``` **Example:** Updating the `nitro.config.ts` file ```ts import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ preset: 'cloudflare_pages' }) ``` ## Compatibility date Deployment providers regularly update their runtime behavior. Nitro presets are updated to support these new features. To prevent breaking existing deployments, Nitro uses compatibility dates. These dates let you lock in behavior at the project creation time. You can also opt in to future updates when ready. When you create a new project, the `compatibilityDate` is set to the current date. This setting is saved in your project's configuration. You should update the compatibility date periodically. Always test your deployment thoroughly after updating. Below is a list of key dates and their effects. ================================================ FILE: docs/2.deploy/10.runtimes/1.node.md ================================================ --- icon: akar-icons:node-fill --- # Node.js > Run Nitro apps with Node.js runtime. **Preset:** `node_server` Node.js is the default nitro output preset for production builds and Nitro has native Node.js runtime support. Build project using nitro CLI: ```bash nitro build ``` When running `nitro build` with the Node server preset, the result will be an entry point that launches a ready-to-run Node server. To try output: ```bash $ node .output/server/index.mjs Listening on http://localhost:3000 ``` You can now deploy fully standalone `.output` directory to the hosting of your choice. ### Environment Variables You can customize server behavior using following environment variables: - `NITRO_PORT` or `PORT` (defaults to `3000`) - `NITRO_HOST` or `HOST` - `NITRO_UNIX_SOCKET` - if provided (a path to the desired socket file) the service will be served over the provided UNIX socket. - `NITRO_SSL_CERT` and `NITRO_SSL_KEY` - if both are present, this will launch the server in HTTPS mode. In the vast majority of cases, this should not be used other than for testing, and the Nitro server should be run behind a reverse proxy like nginx or Cloudflare which terminates SSL. - `NITRO_SHUTDOWN_DISABLED` - Disables the graceful shutdown feature when set to `'true'`. If it's set to `'true'`, the graceful shutdown is bypassed to speed up the development process. Defaults to `'false'`. - `NITRO_SHUTDOWN_SIGNALS` - Allows you to specify which signals should be handled. Each signal should be separated with a space. Defaults to `'SIGINT SIGTERM'`. - `NITRO_SHUTDOWN_TIMEOUT` - Sets the amount of time (in milliseconds) before a forced shutdown occurs. Defaults to `'30000'` milliseconds. - `NITRO_SHUTDOWN_FORCE` - When set to true, it triggers `process.exit()` at the end of the shutdown process. If it's set to `'false'`, the process will simply let the event loop clear. Defaults to `'true'`. ## Cluster mode **Preset:** `node_cluster` For more performance and leveraging multi-core handling, you can use cluster preset. ### Environment Variables In addition to environment variables from the `node_server` preset, you can customize behavior: - `NITRO_CLUSTER_WORKERS`: Number of cluster workers (default is Number of available cpu cores) ## Handler (advanced) **Preset:** `node_middleware` Nitro also has a more low-level preset that directly exports a middleware usable for custom servers. When running `nitro build` with the Node middleware preset, the result will be an entry point exporting a middleware handler. **Example:** ```js import { createServer } from 'node:http' import { listener } from './.output/server' const server = createServer(listener) server.listen(8080) ``` ================================================ FILE: docs/2.deploy/10.runtimes/bun.md ================================================ --- icon: simple-icons:bun --- # Bun > Run Nitro apps with Bun runtime. **Preset:** `bun` Nitro output is compatible with Bun runtime. While using default [Node.js](/deploy/runtimes/node) you can also run the output in bun, using `bun` preset has advantage of better optimizations. After building with bun preset using `bun` as preset, you can run server in production using: ```bash bun run ./.output/server/index.mjs ``` :read-more{to="https://bun.sh"} ================================================ FILE: docs/2.deploy/10.runtimes/deno.md ================================================ --- icon: simple-icons:deno --- # Deno > Run Nitro apps with [Deno](https://deno.com/) runtime. **Preset:** `deno_server` You can build your Nitro server using Node.js to run within [Deno Runtime](https://deno.com/runtime) in a custom server. ```bash # Build with the deno NITRO preset NITRO_PRESET=deno_server npm run build # Start production server deno run --unstable --allow-net --allow-read --allow-env .output/server/index.ts ``` ## Deno Deploy :read-more{to="/deploy/providers/deno-deploy"} ================================================ FILE: docs/2.deploy/20.providers/alwaysdata.md ================================================ # Alwaysdata > Deploy Nitro apps to alwaysdata. **Preset:** `alwaysdata` :read-more{to="https://alwaysdata.com"} ## Set up application ### Pre-requisites 1. [Register a new profile](https://www.alwaysdata.com/en/register/) on alwaysdata platform if you don't have one. 2. Get a free 100Mb plan to host your app. > [!NOTE] > Keep in mind your *account name* will be used to provide you a default URL in the form of `account_name.alwaysdata.net`, so choose it wisely. You can also link your existing domains to your account later or register as many accounts under your profile as you need. ### Local deployment 1. Build your project locally with `npm run build -- preset alwaysdata` 2. [Upload your app](https://help.alwaysdata.com/en/remote-access/) to your account in its own directory (e.g. `$HOME/www/my-app`). You can use any protocol you prefer (SSH/FTP/WebDAV…) to do so. 3. On your admin panel, [create a new site](https://admin.alwaysdata.com/site/add/) for your app with the following features: - *Addresses*: `[account_name].alwaysdata.net` - *Type*: Node.js - *Command*: `node .output/server/index.mjs` - *Working directory*: `www/my-app` (adapt it to your deployment path) - *Environment*: ```ini NITRO_PRESET=alwaysdata ``` - *Node.js version*: `Default version` is fine; pick no less than `20.0.0` (you can also [set your Node.js version globally](https://help.alwaysdata.com/en/languages/nodejs/configuration/#supported-versions)) - *Hot restart*: `SIGHUP` :read-more{title="Get more information about alwaysdata Node.js sites type" to="https://help.alwaysdata.com/en/languages/nodejs"} 4. Your app is now live at `http(s)://[account_name].alwaysdata.net`. ================================================ FILE: docs/2.deploy/20.providers/aws-amplify.md ================================================ # AWS Amplify > Deploy Nitro apps to AWS Amplify Hosting. **Preset:** `aws_amplify` :read-more{title="AWS Amplify Hosting" to="https://aws.amazon.com/amplify"} ## Deploy to AWS Amplify Hosting ::tip Integration with this provider is possible with [zero configuration](/deploy/#zero-config-providers). :: 1. Login to the [AWS Amplify Hosting Console](https://console.aws.amazon.com/amplify/) 2. Click on "Get Started" > Amplify Hosting (Host your web app) 3. Select and authorize access to your Git repository provider and select the main branch 4. Choose a name for your app, make sure build settings are auto-detected and optionally set requirement environment variables under the advanced section 5. Optionally, select Enable SSR logging to enable server-side logging to your Amazon CloudWatch account 6. Confirm configuration and click on "Save and Deploy" ## Advanced Configuration You can configure advanced options of this preset using `awsAmplify` option. ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ awsAmplify: { // catchAllStaticFallback: true, // imageOptimization: { path: "/_image", cacheControl: "public, max-age=3600, immutable" }, // imageSettings: { ... }, // runtime: "nodejs18.x", // default: "nodejs18.x" | "nodejs16.x" | "nodejs20.x" } }) ``` ### `amplify.yml` You might need a custom `amplify.yml` file for advanced configuration. Here are two template examples: ::code-group ```yml [amplify.yml] version: 1 frontend: phases: preBuild: commands: - nvm use 18 && node --version - corepack enable && npx --yes nypm install build: commands: - pnpm build artifacts: baseDirectory: .amplify-hosting files: - "**/*" ``` ```yml [amplify.yml (monorepo)] version: 1 applications: - frontend: phases: preBuild: commands: - nvm use 18 && node --version - corepack enable && npx --yes nypm install build: commands: - pnpm --filter website1 build artifacts: baseDirectory: apps/website1/.amplify-hosting files: - '**/*' buildPath: / appRoot: apps/website1 ``` :: ================================================ FILE: docs/2.deploy/20.providers/aws.md ================================================ # AWS Lambda > Deploy Nitro apps to AWS Lambda. **Preset:** `aws_lambda` :read-more{title="AWS Lambda" to="https://aws.amazon.com/lambda/"} Nitro provides a built-in preset to generate output format compatible with [AWS Lambda](https://aws.amazon.com/lambda/). The output entrypoint in `.output/server/index.mjs` is compatible with [AWS Lambda format](https://docs.aws.amazon.com/lex/latest/dg/lambda-input-response-format.html). It can be used programmatically or as part of a deployment. ```ts import { handler } from './.output/server' // Use programmatically const { statusCode, headers, body } = handler({ rawPath: '/' }) ``` ## Inlining chunks Nitro output, by default uses dynamic chunks for lazy loading code only when needed. However this sometimes can not be ideal for performance. (See discussions in [nitrojs/nitro#650](https://github.com/nitrojs/nitro/pull/650)). You can enabling chunk inlining behavior using [`inlineDynamicImports`](/config#inlinedynamicimports) config. ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ inlineDynamicImports: true }); ``` ## Response streaming :read-more{title="Introducing AWS Lambda response streaming" to="https://aws.amazon.com/blogs/compute/introducing-aws-lambda-response-streaming/"} In order to enable response streaming, enable `awsLambda.streaming` flag: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ awsLambda: { streaming: true } }); ``` ================================================ FILE: docs/2.deploy/20.providers/azure.md ================================================ # Azure > Deploy Nitro apps to Azure Static Web apps or functions. ## Azure static web apps **Preset:** `azure-swa` :read-more{title="Azure Static Web Apps" to="https://azure.microsoft.com/en-us/products/app-service/static"} ::note Integration with this provider is possible with [zero configuration](/deploy/#zero-config-providers). :: [Azure Static Web Apps](https://azure.microsoft.com/en-us/products/app-service/static) are designed to be deployed continuously in a [GitHub Actions workflow](https://docs.microsoft.com/en-us/azure/static-web-apps/github-actions-workflow). By default, Nitro will detect this deployment environment and enable the `azure` preset. ### Local preview Install [Azure Functions Core Tools](https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local) if you want to test locally. You can invoke a development environment to preview before deploying. ```bash NITRO_PRESET=azure npx nypm@latest build npx @azure/static-web-apps-cli start .output/public --api-location .output/server ``` ### Configuration Azure Static Web Apps are [configured](https://learn.microsoft.com/en-us/azure/static-web-apps/configuration) using the `staticwebapp.config.json` file. Nitro automatically generates this configuration file whenever the application is built with the `azure` preset. Nitro will automatically add the following properties based on the following criteria: | Property | Criteria | Default | | --- | --- | --- | | **[platform.apiRuntime](https://learn.microsoft.com/en-us/azure/static-web-apps/configuration#platform)** | Will automatically set to `node:16` or `node:14` depending on your package configuration. | `node:16` | | **[navigationFallback.rewrite](https://learn.microsoft.com/en-us/azure/static-web-apps/configuration#fallback-routes)** | Is always `/api/server` | `/api/server` | | **[routes](https://learn.microsoft.com/en-us/azure/static-web-apps/configuration#routes)** | All prerendered routes are added. Additionally, if you do not have an `index.html` file an empty one is created for you for compatibility purposes and also requests to `/index.html` are redirected to the root directory which is handled by `/api/server`. | `[]` | ### Custom configuration You can alter the Nitro generated configuration using `azure.config` option. Custom routes will be added and matched first. In the case of a conflict (determined if an object has the same route property), custom routes will override generated ones. ### Deploy from CI/CD via GitHub actions When you link your GitHub repository to Azure Static Web Apps, a workflow file is added to the repository. When you are asked to select your framework, select custom and provide the following information: | Input | Value | | --- | --- | | **app_location** | '/' | | **api_location** | '.output/server' | | **output_location** | '.output/public' | If you miss this step, you can always find the build configuration section in your workflow and update the build configuration: ```yaml [.github/workflows/azure-static-web-apps-.yml] ###### Repository/Build Configurations ###### app_location: '/' api_location: '.output/server' output_location: '.output/public' ###### End of Repository/Build Configurations ###### ``` That's it! Now Azure Static Web Apps will automatically deploy your Nitro-powered application on push. If you are using runtimeConfig, you will likely want to configure the corresponding [environment variables on Azure](https://docs.microsoft.com/en-us/azure/static-web-apps/application-settings). ================================================ FILE: docs/2.deploy/20.providers/cleavr.md ================================================ # Cleavr > Deploy Nitro apps to Cleavr. **Preset:** `cleavr` :read-more{title="cleavr.io" to="https://cleavr.io"} ::note Integration with this provider is possible with [zero configuration](/deploy/#zero-config-providers). :: ## Set up your web app In your project, set Nitro preset to `cleavr`. ```js export default { nitro: { preset: 'cleavr' } } ``` Push changes to your code repository. **In your Cleavr panel:** 1. Provision a new server 2. Add a website, selecting **Nuxt 3** as the app type 3. In web app > settings > Code Repo, point to your project's code repository You're now all set to deploy your project! ================================================ FILE: docs/2.deploy/20.providers/cloudflare.md ================================================ # Cloudflare > Deploy Nitro apps to Cloudflare. ## Cloudflare Workers **Preset:** `cloudflare_module` :read-more{title="Cloudflare Workers" to="https://developers.cloudflare.com/workers/"} ::note Integration with this provider is possible with [zero configuration](/deploy#zero-config-providers) supporting [workers builds (beta)](https://developers.cloudflare.com/workers/ci-cd/builds/). :: ::important To use Workers with Static Assets, you need a Nitro compatibility date set to `2024-09-19` or later. :: The following shows an example `nitro.config.ts` file for deploying a Nitro app to Cloudflare Workers. ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ compatibilityDate: "2024-09-19", preset: "cloudflare_module", cloudflare: { deployConfig: true, nodeCompat: true } }) ``` By setting `deployConfig: true`, Nitro will automatically generate a `wrangler.json` for you with the correct configuration. If you need to add [Cloudflare Workers configuration](https://developers.cloudflare.com/workers/wrangler/configuration/), such as [bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/), you can either: - Set these in your Nitro config under the `cloudflare: { wrangler : {} }`. This has the same type as `wrangler.json`. - Provide your own `wrangler.json`. Nitro will merge your config with the appropriate settings, including pointing to the build output. ### Local Preview You can use [Wrangler](https://github.com/cloudflare/workers-sdk/tree/main/packages/wrangler) to preview your app locally: :pm-run{script="build"} :pm-x{command="wrangler dev"} ### Manual Deploy After having built your application you can manually deploy it with Wrangler. First make sure to be logged into your Cloudflare account: :pm-x{command="wrangler login"} Then you can deploy the application with: :pm-x{command="wrangler deploy"} ### Runtime Hooks You can use [runtime hooks](/docs/plugins#nitro-runtime-hooks) below in order to extend [Worker handlers](https://developers.cloudflare.com/workers/runtime-apis/handlers/). :read-more{to="/docs/plugins#nitro-runtime-hooks"} - [`cloudflare:scheduled`](https://developers.cloudflare.com/workers/runtime-apis/handlers/scheduled/) - [`cloudflare:email`](https://developers.cloudflare.com/email-routing/email-workers/runtime-api/) - [`cloudflare:queue`](https://developers.cloudflare.com/queues/configuration/javascript-apis/#consumer) - [`cloudflare:tail`](https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/) - `cloudflare:trace` ### Additional Exports You can add a `exports.cloudflare.ts` file to your project root to export additional handlers or properties to the Cloudflare Worker entrypoint. ```ts [exports.cloudflare.ts] export class MyWorkflow extends WorkflowEntrypoint { async run(event: WorkflowEvent, step: WorkflowStep) { // ... } } ``` Nitro will automatically detect this file and include its exports in the final build. ::warning The `exports.cloudflare.ts` file must not have a default export. :: You can also customize the entrypoint file location using the `cloudflare.exports` option in your `nitro.config.ts`: ```ts [nitro.config.ts] export default defineConfig({ cloudflare: { exports: "custom-exports-entry.ts" } }) ``` ### Scheduled Tasks (Cron Triggers) When using [Nitro tasks](/docs/tasks) with `scheduledTasks`, Nitro automatically generates [Cron Triggers](https://developers.cloudflare.com/workers/configuration/cron-triggers/) in the wrangler config at build time. ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ preset: "cloudflare_module", experimental: { tasks: true, }, scheduledTasks: { "* * * * *": ["cms:update"], "0 15 1 * *": ["db:cleanup"], }, cloudflare: { deployConfig: true, }, }) ``` No manual Wrangler configuration is needed - Nitro handles it for you. ## Cloudflare Pages **Preset:** `cloudflare_pages` :read-more{title="Cloudflare Pages" to="https://pages.cloudflare.com/"} ::note Integration with this provider is possible with [zero configuration](/deploy#zero-config-providers). :: ::warning Cloudflare [Workers Module](#cloudflare-workers) is the new recommended preset for deployments. Please consider using the pages only if you need specific features. :: The following shows an example `nitro.config.ts` file for deploying a Nitro app to Cloudflare Pages. ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ preset: "cloudflare_pages", cloudflare: { deployConfig: true, nodeCompat:true } }) ``` Nitro automatically generates a `_routes.json` file that controls which routes get served from files and which are served from the Worker script. The auto-generated routes file can be overridden with the config option `cloudflare.pages.routes` ([read more](https://developers.cloudflare.com/pages/platform/functions/routing/#functions-invocation-routes)). ### Local Preview You can use [Wrangler](https://github.com/cloudflare/workers-sdk/tree/main/packages/wrangler) to preview your app locally: :pm-run{script="build"} :pm-x{command="wrangler pages dev"} ### Manual Deploy After having built your application you can manually deploy it with Wrangler, in order to do so first make sure to be logged into your Cloudflare account: :pm-x{command="wrangler login"} Then you can deploy the application with: :pm-x{command="wrangler pages deploy"} ## Deploy within CI/CD using GitHub Actions Regardless on whether you're using Cloudflare Pages or Cloudflare Workers, you can use the [Wrangler GitHub actions](https://github.com/marketplace/actions/deploy-to-cloudflare-workers-with-wrangler) to deploy your application. ::note **Note:** Remember to [instruct Nitro to use the correct preset](/deploy#changing-the-deployment-preset) (note that this is necessary for all presets including the `cloudflare_pages` one). :: ## Environment Variables Nitro allows you to universally access environment variables using `process.env` or `import.meta.env` or the runtime config. ::note Make sure to only access environment variables **within the event lifecycle** and not in global contexts since Cloudflare only makes them available during the request lifecycle and not before. :: **Example:** If you have set the `SECRET` and `NITRO_HELLO_THERE` environment variables set you can access them in the following way: ```ts import { defineHandler } from "nitro"; import { useRuntimeConfig } from "nitro/runtime-config"; console.log(process.env.SECRET) // note that this is in the global scope! so it doesn't actually work and the variable is undefined! export default defineHandler((event) => { // note that all the below are valid ways of accessing the above mentioned variables useRuntimeConfig().helloThere useRuntimeConfig().secret process.env.NITRO_HELLO_THERE import.meta.env.SECRET }); ``` ### Specify Variables in Development Mode For development, you can use a `.env` or `.env.local` file to specify environment variables: ```ini NITRO_HELLO_THERE="captain" SECRET="top-secret" ``` ::note **Note:** Make sure you add `.env` and `.env.local` to the `.gitignore` file so that you don't commit it as it can contain sensitive information. :: ### Specify Variables for local previews After build, when you try out your project locally with `wrangler dev` or `wrangler pages dev`, in order to have access to environment variables you will need to specify the in a `.dev.vars` file in the root of your project (as presented in the [Pages](https://developers.cloudflare.com/pages/functions/bindings/#interact-with-your-environment-variables-locally) and [Workers](https://developers.cloudflare.com/workers/configuration/environment-variables/#interact-with-environment-variables-locally) documentation). If you are using a `.env` or `.env.local` file while developing, your `.dev.vars` should be identical to it. ::note **Note:** Make sure you add `.dev.vars` to the `.gitignore` file so that you don't commit it as it can contain sensitive information. :: ### Specify Variables for Production For production, use the Cloudflare dashboard or the [`wrangler secret`](https://developers.cloudflare.com/workers/wrangler/commands/#secret) command to set environment variables and secrets. ### Specify Variables using `wrangler.toml`/`wrangler.json` You can specify a custom `wrangler.toml`/`wrangler.json` file and define vars inside. ::warning Note that this isn't recommend for sensitive data like secrets. :: **Example:** ::code-group ```ini [wrangler.toml] # Shared [vars] NITRO_HELLO_THERE="general" SECRET="secret" # Override values for `--env production` usage [env.production.vars] NITRO_HELLO_THERE="captain" SECRET="top-secret" ``` ```json [wrangler.json] { "vars": { "NITRO_HELLO_THERE": "general", "SECRET": "secret" }, "env": { "production": { "vars": { "NITRO_HELLO_THERE": "captain", "SECRET": "top-secret" } } } } ``` :: ## Direct access to Cloudflare bindings Bindings are what allows you to interact with resources from the Cloudflare platform, examples of such resources are key-value data storages ([KVs](https://developers.cloudflare.com/kv/)) and serverless SQL databases ([D1s](https://developers.cloudflare.com/d1/)). ::read-more For more details on Bindings and how to use them please refer to the Cloudflare [Pages](https://developers.cloudflare.com/pages/functions/bindings/) and [Workers](https://developers.cloudflare.com/workers/configuration/bindings/#bindings) documentation. :: > [!TIP] > Nitro provides high level API to interact with primitives such as [KV Storage](/docs/storage) and [Database](/docs/database) and you are highly recommended to prefer using them instead of directly depending on low-level APIs for usage stability. :read-more{title="Database Layer" to="/docs/database"} :read-more{title="KV Storage" to="/docs/storage"} In runtime, you can access bindings from the request event via `event.req.runtime.cloudflare.env`. This is for example how you can access a D1 binding: ```ts import { defineHandler } from "nitro"; defineHandler(async (event) => { const { env } = event.req.runtime.cloudflare const stmt = await env.MY_D1.prepare('SELECT id FROM table') const { results } = await stmt.all() }) ``` ### Access to the bindings in local dev To access bindings in dev mode, we first define them. You can do this in a `wrangler.jsonc`/`wrangler.json`/`wrangler.toml` file For example, to define a variable and a KV namespace in `wrangler.toml`: ::code-group ```ini [wrangler.toml] [vars] MY_VARIABLE="my-value" [[kv_namespaces]] binding = "MY_KV" id = "xxx" ``` ```json [wrangler.json] { "vars": { "MY_VARIABLE": "my-value", }, "kv_namespaces": [ { "binding": "MY_KV", "id": "xxx" } ] } ``` :: Next we install the required `wrangler` package (if not already installed): :pm-install{name="wrangler -D"} From this moment, when running :pm-run{script="dev"} you will be able to access the `MY_VARIABLE` and `MY_KV` from the request event just as illustrated above. #### Wrangler environments If you have multiple Wrangler environments, you can specify which Wrangler environment to use during Cloudflare dev emulation: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ preset: 'cloudflare_module', cloudflare: { dev: { environment: 'preview' } } }) ``` ================================================ FILE: docs/2.deploy/20.providers/deno-deploy.md ================================================ # Deno Deploy > Deploy Nitro apps to [Deno Deploy](https://deno.com/deploy). **Preset:** `deno_deploy` :read-more{title="Deno Deploy" to="https://deno.com/deploy"} ## Deploy with the CLI You can use [deployctl](https://deno.com/deploy/docs/deployctl) to deploy your app. Login to [Deno Deploy](https://dash.deno.com/account#access-tokens) to obtain a `DENO_DEPLOY_TOKEN` access token, and set it as an environment variable. ```bash # Build with the deno_deploy NITRO preset NITRO_PRESET=deno_deploy npm run build # Make sure to run the deployctl command from the output directory cd .output deployctl deploy --project=my-project server/index.ts ``` ## Deploy within CI/CD using GitHub actions You just need to include the deployctl GitHub Action as a step in your workflow. You do not need to set up any secrets for this to work. You do need to link your GitHub repository to your Deno Deploy project and choose the "GitHub Actions" deployment mode. You can do this in your project settings on [Deno Deploy](https://dash.deno.com). Create the following workflow file in your `.github/workflows` directory: ```yaml [.github/workflows/deno_deploy.yml] name: deno-deploy on: push: branches: - main pull_request: branches: - main jobs: deploy: steps: - uses: actions/checkout@v5 - run: corepack enable - uses: actions/setup-node@v6 with: node-version: 18 cache: pnpm - run: pnpm install - run: pnpm build env: NITRO_PRESET: deno_deploy - name: Deploy to Deno Deploy uses: denoland/deployctl@v1 with: project: my-project entrypoint: server/index.ts root: .output ``` ## Deno runtime :read-more{to="/deploy/runtimes/deno"} ================================================ FILE: docs/2.deploy/20.providers/digitalocean.md ================================================ # DigitalOcean > Deploy Nitro apps to DigitalOcean. **Preset:** `digital_ocean` :read-more{title="Digital Ocean App Platform" to="https://docs.digitalocean.com/products/app-platform/"} ## Set up application 1. Create a new Digital Ocean app following the [guide](https://docs.digitalocean.com/products/app-platform/how-to/create-apps/). 1. Next, you'll need to configure environment variables. In your app settings, ensure the following app-level environment variables are set: ```bash NITRO_PRESET=digital_ocean ``` [More information](https://docs.digitalocean.com/products/app-platform/how-to/use-environment-variables/). 1. You will need to ensure you set an `engines.node` field in your app's `package.json` to ensure Digital Ocean uses a supported version of Node.js: ```json { "engines": { "node": "20.x" } } ``` [See more information](https://docs.digitalocean.com/products/app-platform/languages-frameworks/nodejs/#node-version). 1. You'll also need to add a run command so Digital Ocean knows what command to run after a build. You can do so by adding a start script to your `package.json`: ```json { "scripts": { "start": "node .output/server/index.mjs" } } ``` 1. Finally, you'll need to add this start script to your Digital Ocean app's run command. Go to `Components > Settings > Commands`, click "Edit", then add `npm run start` Your app should be live at a Digital Ocean generated URL and you can now follow [the rest of the Digital Ocean deployment guide](https://docs.digitalocean.com/products/app-platform/how-to/manage-deployments/). ================================================ FILE: docs/2.deploy/20.providers/firebase.md ================================================ # Firebase > Deploy Nitro apps to Firebase. ::note You will need to be on the [**Blaze plan**](https://firebase.google.com/pricing) (Pay as you go) to get started. :: ## Firebase app hosting Preset: `firebase_app_hosting` :read-more{title="Firebase App Hosting" to="https://firebase.google.com/docs/app-hosting"} ::tip You can integrate with this provider using [zero configuration](/deploy/#zero-config-providers). :: ### Project setup 1. Go to the Firebase [console](https://console.firebase.google.com/) and set up a new project. 2. Select **Build > App Hosting** from the sidebar. - You may need to upgrade your billing plan at this step. 3. Click **Get Started**. - Choose a region. - Import a GitHub repository (you’ll need to link your GitHub account). - Configure deployment settings (project root directory and branch), and enable automatic rollouts. - Choose a unique ID for your backend. 4. Click Finish & Deploy to create your first rollout. When you deploy with Firebase App Hosting, the App Hosting preset will be run automatically at build time. ================================================ FILE: docs/2.deploy/20.providers/flightcontrol.md ================================================ # Flightcontrol > Deploy Nitro apps to AWS via Flightcontrol. **Preset:** `flightcontrol` :read-more{title="flightcontrol.dev" to="https://flightcontrol.dev?ref=nitro"} ## Set Up your flightcontrol account On a high level, the steps you will need to follow to deploy a project for the first time are: 1. Create an account at [Flightcontrol](https://app.flightcontrol.dev/signup?ref=nitro) 2. Create an account at [AWS](https://portal.aws.amazon.com/billing/signup) (if you don't already have one) 3. Link your AWS account to the Flightcontrol 4. Authorize the Flightcontrol GitHub App to access your chosen repositories, public or private. 5. Create a Flightcontrol project with configuration via the Dashboard or with configuration via `flightcontrol.json`. ### Create a project with configuration via the dashboard 1. Create a Flightcontrol project from the Dashboard. Select a repository for the source. 2. Select the `GUI` config type. 3. Select the Nuxt preset. This preset will also work for any Nitro-based applications. 4. Select your preferred AWS server size. 5. Submit the new project form. ### Create a project with configuration via `flightcontrol.json` 1. Create a Flightcontrol project from your dashboard. Select a repository for the source. 2. Select the `flightcontrol.json` config type. 3. Add a new file at the root of your repository called `flightcontrol.json`. Here is an example configuration that creates an AWS fargate service for your app: ```json [flightcontrol.json] { "$schema": "https://app.flightcontrol.dev/schema.json", "environments": [ { "id": "production", "name": "Production", "region": "us-west-2", "source": { "branch": "main" }, "services": [ { "id": "nitro", "buildType": "nixpacks", "name": "My Nitro site", "type": "fargate", "domain": "www.yourdomain.com", "outputDirectory": ".output", "startCommand": "node .output/server/index.mjs", "cpu": 0.25, "memory": 0.5 } ] } ] } ``` 4. Submit the new project form. ::read-more{to="https://www.flightcontrol.dev/docs?ref=nitro"} Learn more about Flightcontrol's [configuration](https://www.flightcontrol.dev/docs?ref=nitro). :: ================================================ FILE: docs/2.deploy/20.providers/genezio.md ================================================ # Genezio > Deploy Nitro apps to Genezio. **Preset:** `genezio` :read-more{title="Genezio" to="https://genezio.com"} > [!IMPORTANT] > 🚧 This preset is currently experimental. ## 1. Project Setup Create `genezio.yaml` file: ```yaml # The name of the project. name: nitro-app # The version of the Genezio YAML configuration to parse. yamlVersion: 2 backend: # The root directory of the backend. path: .output/ # Information about the backend's programming language. language: # The name of the programming language. name: js # The package manager used by the backend. packageManager: npm # Information about the backend's functions. functions: # The name (label) of the function. - name: nitroServer # The path to the function's code. path: server/ # The name of the function handler handler: handler # The entry point for the function. entry: index.mjs ``` ::read-more{to="https://genezio.com/docs/project-structure/genezio-configuration-file/"} To further customize the file to your needs, you can consult the [official documentation](https://genezio.com/docs/project-structure/genezio-configuration-file/). :: ## 2. Deploy your project Build with the genezio nitro preset: ```bash NITRO_PRESET=genezio npm run build ``` Deploy with [`genezio`](https://npmjs.com/package/genezio) cli: :pm-x{command="genezio deploy"} ::read-more{title="Backend Environment Variables" to="https://genezio.com/docs/project-structure/backend-environment-variables"} To set environment viarables, please check out [Genezio - Environment Variables](https://genezio.com/docs/project-structure/backend-environment-variables). :: ## 3. Monitor your project You can monitor and manage your application through the [Genezio App Dashboard](https://app.genez.io/dashboard). The dashboard URL, also provided after deployment, allows you to access comprehensive views of your project's status and logs. ================================================ FILE: docs/2.deploy/20.providers/github-pages.md ================================================ # GitHub Pages > Deploy Nitro apps to GitHub Pages. **Preset:** `github_pages` :read-more{title="GitHub Pages" to="https://pages.github.com/"} ## Setup Follow the steps to [create a GitHub Pages site](https://docs.github.com/en/pages/getting-started-with-github-pages/creating-a-github-pages-site). ## Deployment Here is an example GitHub Actions workflow to deploy your site to GitHub Pages using the `github_pages` preset: ```yaml [.github/workflows/deploy.yml] # https://github.com/actions/deploy-pages#usage name: Deploy to GitHub Pages on: workflow_dispatch: push: branches: - main jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - run: corepack enable - uses: actions/setup-node@v6 with: node-version: "18" - run: npx nypm install - run: npm run build env: NITRO_PRESET: github_pages - name: Upload artifact uses: actions/upload-pages-artifact@v1 with: path: ./.output/public # Deployment job deploy: # Add a dependency to the build job needs: build # Grant GITHUB_TOKEN the permissions required to make a Pages deployment permissions: pages: write # to deploy to Pages id-token: write # to verify the deployment originates from an appropriate source # Deploy to the github_pages environment environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} # Specify runner + deployment step runs-on: ubuntu-latest steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v1 ``` ================================================ FILE: docs/2.deploy/20.providers/gitlab-pages.md ================================================ # GitLab Pages > Deploy Nitro apps to GitLab Pages. **Preset:** `gitlab_pages` :read-more{title="GitLab Pages" to="https://pages.github.com/"} ## Setup Follow the steps to [create a GitLab Pages site](https://docs.gitlab.com/ee/user/project/pages/#getting-started). ## Deployment 1. Here is an example GitLab Pages workflow to deploy your site to GitLab Pages: ```yaml [.gitlab-ci.yml] image: node:lts before_script: - npx nypm install pages: cache: paths: - node_modules/ variables: NITRO_PRESET: gitlab_pages script: - npm run build artifacts: paths: - .output/public publish: .output/public rules: # This ensures that only pushes to the default branch # will trigger a pages deploy - if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH ``` ================================================ FILE: docs/2.deploy/20.providers/heroku.md ================================================ # Heroku > Deploy Nitro apps to Heroku. **Preset:** `heroku` :read-more{title="heroku.com" to="https://heroku.com/"} ## Using the heroku CLI 1. Create a new Heroku app. ```bash heroku create myapp ``` 1. Configure Heroku to use the nodejs buildpack. ```bash heroku buildpacks:set heroku/nodejs ``` 1. Configure your app. ```bash heroku config:set NITRO_PRESET=heroku ``` 1. Ensure you have `start` and `build` commands in your `package.json` file. ```json5 "scripts": { "build": "nitro build", // or `nuxt build` if using nuxt "start": "node .output/server/index.mjs" } ``` ## With nginx 1. Add the heroku Nginx buildpack [here](https://github.com/heroku/heroku-buildpack-nginx.git) 1. Change to the 'node' preset in your `nitro.config` ```json5 "nitro": { "preset":"node", } ``` 1. From the **Existing app** section of buildpack doc, 2 key steps are required to get things running Step 1: Listen on a socket at 'tmp/nginx.socket' Step 2: Create a file '/tmp/app-initialized' when your app is ready to accept connections 1. Create custom app runner, eg: apprunner.mjs at the root of the project (or any other preferred location), in this file, create a server, using the listener generated by the node preset, then listen on the socket as detailed in the buildpack doc ```ts import { createServer } from 'node:http' import { listener } from './.output/server/index.mjs' const server = createServer(listener) server.listen('/tmp/nginx.socket') //following the buildpack doc ``` 1. To create the 'tmp/app-initialized' file, use a nitro plugin, create file 'initServer.ts' at the root of the project (or any other preferred location) ```ts import fs from "fs" export default definePlugin((nitroApp) => { if((process.env.NODE_ENV || 'development') != 'development') { fs.openSync('/tmp/app-initialized', 'w') } }) ``` 1. Finally, create file 'Procfile' at the root of the project, with the Procfile, we tell heroku to start nginx and use the custom apprunner.mjs to start the server web: bin/start-nginx node apprunner.mjs 1. Bonus: create file 'config/nginx.conf.erb' to customize your nginx config. With the node preset, by default, static files handlers will not be generated, you can use nginx to server static files, just add the right location rule to the server block(s), or, force the node preset to generate handlers for the static files by setting serveStatic to true. ================================================ FILE: docs/2.deploy/20.providers/iis.md ================================================ # IIS > Deploy Nitro apps to IIS. ## Using [IISnode](https://github.com/Azure/iisnode) **Preset:** `iis_node` 1. Install the latest LTS version of [Node.js](https://nodejs.org/en/) on your Windows Server. 2. Install [IISnode](https://github.com/azure/iisnode/releases) 3. Install [IIS `URLRewrite` Module](https://www.iis.net/downloads/microsoft/url-rewrite). 4. In IIS, add `.mjs` as a new mime type and set its content type to `application/javascript`. 5. Deploy the contents of your `.output` folder to your website in IIS. ## Using IIS handler **Preset:** `iis_handler` You can use IIS http handler directly. 1. Install the latest LTS version of [Node.js](https://nodejs.org/en/) on your Windows Server. 2. Install [IIS `HttpPlatformHandler` Module](https://www.iis.net/downloads/microsoft/httpplatformhandler) 3. Copy your `.output` directory into the Windows Server, and create a website on IIS pointing to that exact directory. ## IIS config options ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ // IIS options default iis: { // merges in a pre-existing web.config file to the nitro default file mergeConfig: true, // overrides the default nitro web.config file all together overrideConfig: false, }, }); ``` ================================================ FILE: docs/2.deploy/20.providers/koyeb.md ================================================ # Koyeb > Deploy Nitro apps to Koyeb. **Preset:** `koyeb` :read-more{to="https://www.koyeb.com"} ## Using the control panel 1. In the [Koyeb control panel](https://app.koyeb.com/), click **Create App**. 2. Choose **GitHub** as your deployment method. 3. Choose the GitHub **repository** and **branch** containing your application code. 4. Name your Service. 5. If you did not add a `start` command to your `package.json` file, under the **Build and deployment settings**, toggle the override switch associated with the run command field. In the **Run command** field, enter: ```bash node .output/server/index.mjs` ``` 6. In the **Advanced** section, click **Add Variable** and add a `NITRO_PRESET` variable set to `koyeb`. 7. Name the App. 8. Click the **Deploy** button. ## Using the Koyeb CLI 1. Follow the instructions targeting your operating system to [install the Koyeb CLI client](https://www.koyeb.com/docs/cli/installation) with an installer. Alternatively, visit the [releases page on GitHub](https://github.com/koyeb/koyeb-cli/releases) to directly download required files. 2. Create a Koyeb API access token by visiting the [API settings for your organization](https://app.koyeb.com/settings/api) in the Koyeb control panel. 3. Log into your account with the Koyeb CLI by typing: ```bash koyeb login ``` Paste your API credentials when prompted. 4. Deploy your Nitro application from a GitHub repository with the following command. Be sure to substitute your own values for ``, ``, and ``: ```bash koyeb app init \ --git github.com// \ --git-branch main \ --git-run-command "node .output/server/index.mjs" \ --ports 3000:http \ --routes /:3000 \ --env PORT=3000 \ --env NITRO_PRESET=koyeb ``` ## Using a docker container 1. Create a `.dockerignore` file in the root of your project and add the following lines: ``` Dockerfile .dockerignore node_modules npm-debug.log .nitro .output .git dist README.md ``` 2. Add a `Dockerfile` to the root of your project: ``` FROM node:18-alpine AS base FROM base AS deps RUN apk add --no-cache libc6-compat WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci FROM base AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . RUN npm run build && npm cache clean --force FROM base AS runner WORKDIR /app RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nitro COPY --from=builder /app . USER nitro EXPOSE 3000 ENV PORT 3000 CMD ["npm", "run", "start"] ``` The Dockerfile above provides the minimum requirements to run the Nitro application. You can easily extend it depending on your needs. You will then need to push your Docker image to a registry. You can use [Docker Hub](https://hub.docker.com/) or [GitHub Container Registry](https://docs.github.com/en/packages/guides/about-github-container-registry) for example. In the Koyeb control panel, use the image and the tag field to specify the image you want to deploy. You can also use the [Koyeb CLI](https://www.koyeb.com/docs/build-and-deploy/cli/installation) Refer to the Koyeb [Docker documentation](https://www.koyeb.com/docs/build-and-deploy/prebuilt-docker-images) for more information. ================================================ FILE: docs/2.deploy/20.providers/netlify.md ================================================ # Netlify > Deploy Nitro apps to Netlify functions or edge. **Preset:** `netlify` :read-more{title="Netlify Functions" to="https://www.netlify.com/platform/core/functions/"} ::note Integration with this provider is possible with [zero configuration](/deploy/#zero-config-providers). :: Normally, the deployment to Netlify does not require any configuration. Nitro will auto-detect that you are in a [Netlify](https://www.netlify.com) build environment and build the correct version of your server. For new sites, Netlify will detect that you are using Nitro and set the publish directory to `dist` and build command to `npm run build`. If you are upgrading an existing site you should check these and update them if needed. If you want to add custom redirects, you can do so with [`routeRules`](/config#routerules) or by adding a [`_redirects`](https://docs.netlify.com/routing/redirects/#syntax-for-the-redirects-file) file to your `public` directory. For deployment, just push to your git repository [as you would normally do for Netlify](https://docs.netlify.com/configure-builds/get-started/). ::note{type="note"} Make sure the publish directory is set to `dist` when creating a new project. :: ## Netlify edge functions **Preset:** `netlify_edge` Netlify Edge Functions use Deno and the powerful V8 JavaScript runtime to let you run globally distributed functions for the fastest possible response times. :read-more{title="Netlify Edge functions" to="https://docs.netlify.com/edge-functions/overview/"} Nitro output can directly run the server at the edge. Closer to your users. ::note{type="note"} Make sure the publish directory is set to `dist` when creating a new project. :: ## Custom deploy configuration You can provide additional deploy configuration using the `netlify` key inside `nitro.config`. It will be merged with built-in auto-generated config. Currently the only supported value is `images.remote_images`, for [configuring Netlify Image CDN](https://docs.netlify.com/image-cdn/create-integration/). ================================================ FILE: docs/2.deploy/20.providers/platform-sh.md ================================================ # Platform.sh > Deploy Nitro apps to platform.sh **Preset:** `platform_sh` :read-more{to="https://platform.sh"} ## Setup First, create a new project on platform.sh and link it to the repository you want to auto-deploy with. Then in repository create `.platform.app.yaml` file: ```yaml [.platform.app.yaml] name: nitro-app type: 'nodejs:20' disk: 128 web: commands: start: "node .output/server/index.mjs" build: flavor: none hooks: build: | corepack enable npx nypm install NITRO_PRESET=platform_sh npm run build mounts: '.data': source: local source_path: .data ``` :read-more{title="Complete list of all available properties" to="https://docs.platform.sh/create-apps/app-reference.html"} :read-more{title="Complete list of all available properties" to="https://unjs.io/blog/2023-08-25-nitro-2.6#default-persistent-data-storage"} ================================================ FILE: docs/2.deploy/20.providers/render.md ================================================ # Render.com > Deploy Nitro apps to Render.com. **Preset:** `render_com` :read-more{title="render.com" to="https://render.com"} ## Set up application 1. [Create a new Web Service](https://dashboard.render.com/select-repo?type=web) and select the repository that contains your code. 2. Ensure the 'Node' environment is selected. 3. Update the start command to `node .output/server/index.mjs` 4. Click 'Advanced' and add an environment variable with `NITRO_PRESET` set to `render_com`. You may also need to add a `NODE_VERSION` environment variable set to `20` for the build to succeed ([docs](https://render.com/docs/node-version)). 5. Click 'Create Web Service'. ## Infrastructure as Code (IaC) 1. Create a file called `render.yaml` with following content at the root of your repository. > This file followed by [Infrastructure as Code](https://render.com/docs/infrastructure-as-code) on Render ```yaml services: - type: web name: env: node branch: main startCommand: node .output/server/index.mjs buildCommand: npx nypm install && npm run build envVars: - key: NITRO_PRESET value: render_com ``` 1. [Create a new Blueprint Instance](https://dashboard.render.com/select-repo?type=blueprint) and select the repository containing your `render.yaml` file. You should be good to go! ================================================ FILE: docs/2.deploy/20.providers/stormkit.md ================================================ # StormKit > Deploy Nitro apps to StormKit. **Preset:** `stormkit` :read-more{title="Stormkit" to="https://www.stormkit.io"} ::note Integration with [Stormkit](https://www.stormkit.io/) is possible with [zero configuration](/deploy#zero-config-providers). :: ## Setup Follow the steps to [create a new app](https://app.stormkit.io/apps/new) on Stormkit. ![Create a new app on Stormkit](/images/stormkit-new-app.png) ## Deployment By default, Stormkit will deploy your apps automatically when you push changes to your main branch. But to trigger a manual deploy (for example, you might do this for the very first deployment), you may click `Deploy now`. ![Trigger a manual deploy with Deploy Now](/images/stormkit-deploy.png) ================================================ FILE: docs/2.deploy/20.providers/vercel.md ================================================ # Vercel > Deploy Nitro apps to Vercel. **Preset:** `vercel` :read-more{title="Vercel Framework Support" to="https://vercel.com/docs/frameworks"} ::note Integration with this provider is possible with [zero configuration](/deploy/#zero-config-providers). :: ## Getting started Deploying to Vercel comes with the following features: - [Preview deployments](https://vercel.com/docs/deployments/environments) - [Fluid compute](https://vercel.com/docs/fluid-compute) - [Observability](https://vercel.com/docs/observability) - [Vercel Firewall](https://vercel.com/docs/vercel-firewall) And much more. Learn more in [the Vercel documentation](https://vercel.com/docs). ### Deploy with Git Vercel supports Nitro with zero-configuration. [Deploy Nitro to Vercel now](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fvercel%2Ftree%2Fmain%2Fexamples%2Fnitro). ## API routes Nitro `/api` directory isn't compatible with Vercel. Instead, you should use: - `routes/api/` for standalone usage ## Bun runtime :read-more{title="Vercel" to="https://vercel.com/docs/functions/runtimes/bun"} You can use [Bun](https://bun.com) instead of Node.js by specifying the runtime using the `vercel.functions` key inside `nitro.config`: ```ts [nitro.config.ts] export default defineNitroConfig({ vercel: { functions: { runtime: "bun1.x" } } }) ``` Alternatively, Nitro also detects Bun automatically if you specify a `bunVersion` property in your `vercel.json`: ```json [vercel.json] { "$schema": "https://openapi.vercel.sh/vercel.json", "bunVersion": "1.x" } ``` ## Proxy route rules Nitro automatically optimizes `proxy` route rules on Vercel by generating [CDN-level rewrites](https://vercel.com/docs/rewrites) at build time. This means matching requests are proxied at the edge without invoking a serverless function, reducing latency and cost. ```ts [nitro.config.ts] export default defineNitroConfig({ routeRules: { // Proxied at CDN level — no function invocation "/api/**": { proxy: "https://api.example.com/**", }, }, }); ``` ### When CDN rewrites apply A proxy rule is offloaded to a Vercel CDN rewrite when **all** of the following are true: - The target is an **external URL** (starts with `http://` or `https://`). - No advanced `ProxyOptions` are set on the rule. ### Fallback to runtime proxy When the proxy rule uses any of the following `ProxyOptions`, Nitro keeps it as a runtime proxy handled by the serverless function: - `headers` — custom headers on the outgoing request to the upstream - `forwardHeaders` / `filterHeaders` — header filtering - `fetchOptions` — custom fetch options - `cookieDomainRewrite` / `cookiePathRewrite` — cookie manipulation - `onResponse` — response callback ::note Response headers defined on the route rule via the `headers` option are still applied to CDN-level rewrites. Only request-level `ProxyOptions.headers` (sent to the upstream) require a runtime proxy. :: ## Scheduled tasks (Cron Jobs) :read-more{title="Vercel Cron Jobs" to="https://vercel.com/docs/cron-jobs"} Nitro automatically converts your [`scheduledTasks`](/docs/tasks#scheduled-tasks) configuration into [Vercel Cron Jobs](https://vercel.com/docs/cron-jobs) at build time. Define your schedules in your Nitro config and deploy - no manual `vercel.json` cron configuration required. ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ experimental: { tasks: true }, scheduledTasks: { // Run `cms:update` every hour '0 * * * *': ['cms:update'], // Run `db:cleanup` every day at midnight '0 0 * * *': ['db:cleanup'] } }) ``` ### Secure cron job endpoints :read-more{title="Securing cron jobs" to="https://vercel.com/docs/cron-jobs/manage-cron-jobs#securing-cron-jobs"} To prevent unauthorized access to the cron handler, set a `CRON_SECRET` environment variable in your Vercel project settings. When `CRON_SECRET` is set, Nitro validates the `Authorization` header on every cron invocation. ## Custom build output configuration You can provide additional [build output configuration](https://vercel.com/docs/build-output-api/v3) using `vercel.config` key inside `nitro.config`. It will be merged with built-in auto-generated config. ## On-Demand incremental static regeneration (ISR) On-demand revalidation allows you to purge the cache for an ISR route whenever you want, foregoing the time interval required with background revalidation. To revalidate a page on demand: 1. Create an Environment Variable which will store a revalidation secret - You can use the command `openssl rand -base64 32` or [Generate a Secret](https://generate-secret.vercel.app/32) to generate a random value. 2. Update your configuration: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ vercel: { config: { bypassToken: process.env.VERCEL_BYPASS_TOKEN } } }) ``` 3. To trigger "On-Demand Incremental Static Regeneration (ISR)" and revalidate a path to a Prerender Function, make a GET or HEAD request to that path with a header of x-prerender-revalidate: `bypassToken`. When that Prerender Function endpoint is accessed with this header set, the cache will be revalidated. The next request to that function should return a fresh response. ### Fine-grained ISR config via route rules By default, query params affect cache keys but are not passed to the route handler unless specified. You can pass an options object to `isr` route rule to configure caching behavior. - `expiration`: Expiration time (in seconds) before the cached asset will be re-generated by invoking the Serverless Function. Setting the value to `false` (or `isr: true` route rule) means it will never expire. - `group`: Group number of the asset. Prerender assets with the same group number will all be re-validated at the same time. - `allowQuery`: List of query string parameter names that will be cached independently. - If an empty array, query values are not considered for caching. - If `undefined` each unique query value is cached independently. - For wildcard `/**` route rules, `url` is always added - `passQuery`: When `true`, the query string will be present on the `request` argument passed to the invoked function. The `allowQuery` filter still applies. - `exposeErrBody`: When `true`, expose the response body regardless of status code including error status codes. (default `false` ```ts export default defineNitroConfig({ routeRules: { "/products/**": { isr: { allowQuery: ["q"], passQuery: true, exposeErrBody: true }, }, }, }); ``` ================================================ FILE: docs/2.deploy/20.providers/zeabur.md ================================================ # Zeabur > Deploy Nitro apps to [Zeabur](https://zeabur.com). **Preset:** `zeabur` :read-more{title="Zeabur" to="https://zeabur.com"} ::note Integration with this provider is possible with [zero configuration](/deploy/#zero-config-providers). :: ## Deploy using git 1. Push your code to your git repository (Currently only GitHub supported). 2. [Import your project](https://zeabur.com/docs/get-started) into Zeabur. 3. Zeabur will detect that you are using Nitro and will enable the correct settings for your deployment. 4. Your application is deployed! ================================================ FILE: docs/2.deploy/20.providers/zephyr.md ================================================ # Zephyr Cloud > Deploy Nitro apps to [Zephyr Cloud](https://zephyr-cloud.io). **Preset:** `zephyr` :read-more{title="Zephyr Cloud Docs" to="https://docs.zephyr-cloud.io"} Zephyr support is built into Nitro through the `zephyr` preset. For most Zephyr-specific topics such as BYOC, cloud integrations, environments, and CI/CD authentication, refer to the [Zephyr Cloud docs](https://docs.zephyr-cloud.io). ::note Zephyr is a little different from most Nitro deployment providers. Instead of targeting a single hosting vendor directly, Zephyr acts as a deployment control plane on top of either Zephyr-managed infrastructure or your own cloud integrations. :: ## BYOC model Zephyr supports a BYOC (Bring Your Own Cloud) model. In Zephyr's architecture, the control plane stays managed by Zephyr, while the data plane (workers and storage) runs in your cloud accounts. This lets you keep Zephyr's deployment workflow while using any supported Zephyr cloud integration. See the [Zephyr BYOC docs](https://docs.zephyr-cloud.io/features/byoc) for the current list of supported providers. ## Deploy with Nitro CLI Use Nitro's deploy command to build and upload your app to Zephyr in one step: ```bash npx nitro deploy --preset zephyr ``` Nitro will upload the generated output using `zephyr-agent`. If `zephyr-agent` is missing, Nitro will prompt to install it locally and will install it automatically in CI. ## Deploy during build Zephyr is a little different here from most Nitro providers: we recommend enabling deployment during `nitro build` and treating build as the primary deployment step. If your CI pipeline already runs `nitro build`, enable deployment during the build step: ```ts [nitro.config.ts] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ preset: "zephyr", zephyr: { deployOnBuild: true, }, }); ``` Then your normal build command is enough: :pm-run{script="build"} After the build finishes, Nitro uploads the generated output to Zephyr, deploys it to the edge, and prints the deployment URL: ```txt ◐ Building [Nitro] (preset: zephyr, compatibility: YYYY-MM-DD) ... ZEPHYR Uploaded local snapshot in 110ms ZEPHYR Deployed to Zephyr's edge in 700ms. ZEPHYR ZEPHYR https://my-app.zephyrcloud.app ``` ## CI authentication Zephyr requires an API token for non-interactive deployments. The example below uses the simpler personal-token style setup with `ZE_SECRET_TOKEN` together with `zephyr.deployOnBuild`. ```yaml [.github/workflows/deploy.yml] name: Deploy with Zephyr on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest env: ZE_SECRET_TOKEN: ${{ secrets.ZEPHYR_AUTH_TOKEN }} steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - run: npm ci - run: npm run build ``` For more advanced CI/CD setups, Zephyr also documents organization-level server-token authentication using `ZE_SERVER_TOKEN`. See the [Zephyr CI/CD server token docs](https://docs.zephyr-cloud.io/features/ci-cd-server-token). ## Options ### `zephyr.deployOnBuild` Deploy to Zephyr during `nitro build` when using the `zephyr` preset. - Default: `false` ================================================ FILE: docs/2.deploy/20.providers/zerops.md ================================================ # Zerops > Deploy Nitro apps to [Zerops](https://zerops.io). **Preset:** `zerops` :read-more{title="zerops.io" to="https://zerops.io"} > [!IMPORTANT] > 🚧 This preset is currently experimental. Zerops supports deploying both static and server-side rendered apps with a simple configuration file in your project root. ## Starter templates If you want to quckly get started with zerops and nitro you can use repositories [`zeropsio/recipe-nitro-nodejs`](https://github.com/zeropsio/recipe-nitro-nodejs) and [`zeropsio/recipe-nitro-static`](https://github.com/zeropsio/recipe-nitro-static) starter templates. ## Project setup Projects and services can be added either through [project add wizard](https://app.zerops.io/dashboard/project-add) or imported using `zerops-project-import.yml`. ::code-group ```yml [zerops-project-import.yml (node.js)] project: name: nitro-app services: - hostname: app type: nodejs@20 ``` ```yml [zerops-project-import.yml (static)] project: name: nitro-app services: - hostname: app type: static ``` :: Then create a `zerops.yml` config in your project root: ::code-group ```yml [zerops.yml (node.js)] zerops: - setup: app build: base: nodejs@20 envVariables: NITRO_PRESET: zerops buildCommands: - pnpm i - pnpm run build deployFiles: - .output - package.json - node_modules run: base: nodejs@20 ports: - port: 3000 httpSupport: true start: node .output/server/index.mjs ``` ```yml [zerops.yml (static)] zerops: - setup: app build: base: nodejs@20 envVariables: NITRO_PRESET: zerops-static buildCommands: - pnpm i - pnpm build deployFiles: - .zerops/output/static/~ run: base: static ``` :: Now you can trigger the [build & deploy pipeline using the Zerops CLI](#building-deploying-your-app) or by connecting the app service with your [GitHub](https://docs.zerops.io/references/github-integration/) / [GitLab](https://docs.zerops.io/references/gitlab-integration) repository from inside the service detail. ## Build and deploy Open [Settings > Access Token Management](https://app.zerops.io/settings/token-management) in the Zerops app and generate a new access token. Log in using your access token with the following command: :pm-x{command="@zerops/zcli login "} Navigate to the root of your app (where `zerops.yml` is located) and run the following command to trigger the deploy: :pm-x{command="@zerops/zcli push"} Your code can be deployed automatically on each commit or a new tag by connecting the service with your [GitHub](https://docs.zerops.io/references/gitlab-integration) / [GitLab](https://docs.zerops.io/references/gitlab-integration) repository. This connection can be set up in the service detail. :read-more{title="Zerops Documentation" to="https://docs.zerops.io/"} ================================================ FILE: docs/3.config/0.index.md ================================================ --- icon: ri:settings-3-line --- # Config :read-more{to="/guide/configuration"} ## General ### `preset` Use `preset` option or `NITRO_PRESET` environment variable for custom **production** preset. Preset for development mode is always `nitro_dev` and default `node_server` for production building a standalone Node.js server. The preset will automatically be detected when the `preset` option is not set and running in known environments. ```ts export default defineNitroConfig({ preset: "cloudflare_pages", // deploy to Cloudflare Pages }); ``` ### `debug` - Default: `false`{lang=ts} (`true`{lang=ts} when `DEBUG` environment variable is set) Enable debug mode for verbose logging and additional development information. ```ts export default defineNitroConfig({ debug: true, }); ``` ### `logLevel` - Default: `3`{lang=ts} (`1`{lang=ts} when the testing environment is detected) Log verbosity level. See [consola](https://github.com/unjs/consola?tab=readme-ov-file#log-level) for more information. ```ts export default defineNitroConfig({ logLevel: 4, // verbose logging }); ``` ### `runtimeConfig` - Default: `{ nitro: { ... }, ...yourOptions }`{lang=ts} Server runtime configuration. **Note:** `nitro` namespace is reserved. ```ts export default defineNitroConfig({ runtimeConfig: { apiSecret: "default-secret", // override with NITRO_API_SECRET }, }); ``` ### `compatibilityDate` Deployment providers introduce new features that Nitro presets can leverage, but some of them need to be explicitly opted into. Set it to latest tested date in `YYYY-MM-DD` format to leverage latest preset features. If this configuration is not provided, Nitro will use `"latest"` behavior by default. ```ts export default defineNitroConfig({ compatibilityDate: "2025-01-01", }); ``` ### `static` - Default: `false`{lang=ts} Enable static site generation mode. ```ts export default defineNitroConfig({ static: true, // prerender all routes }); ``` ## Features ### `features` - Default: `{}`{lang=ts} Enable built-in features. #### `runtimeHooks` - Default: auto-detected (enabled if there is at least one nitro plugin) Enable runtime hooks for request and response. #### `websocket` - Default: `false`{lang=ts} Enable WebSocket support. ```ts export default defineNitroConfig({ features: { runtimeHooks: true, websocket: true, // enable WebSocket support }, }); ``` ### `experimental` - Default: `{}` Enable experimental features. #### `openAPI` - Default: `false`{lang=ts} Enable `/_scalar`, `/_swagger` and `/_openapi.json` endpoints. ::note Prefer using the top-level [`openAPI`](#openapi) option for configuration. :: #### `typescriptBundlerResolution` Enable TypeScript bundler module resolution. See [TypeScript#51669](https://github.com/microsoft/TypeScript/pull/51669). #### `asyncContext` Enable native async context support for `useRequest()`. #### `sourcemapMinify` Set to `false` to disable experimental sourcemap minification. #### `envExpansion` Allow env expansion in runtime config. See [#2043](https://github.com/nitrojs/nitro/pull/2043). #### `database` Enable experimental database support. See [Database](/docs/database). #### `tasks` Enable experimental tasks support. See [Tasks](/docs/tasks). ```ts export default defineNitroConfig({ experimental: { typescriptBundlerResolution: true, asyncContext: true, envExpansion: true, database: true, tasks: true, }, }); ``` ### `openAPI` Top-level OpenAPI configuration. You can pass an object to modify your OpenAPI specification: ```js openAPI: { meta: { title: 'My Awesome Project', description: 'This might become the next big thing.', version: '1.0' } } ``` These routes are disabled by default in production. To enable them, use the `production` key. `"runtime"` allows middleware usage, and `"prerender"` is the most efficient because the JSON response is constant. ```js openAPI: { // IMPORTANT: make sure to protect OpenAPI routes if necessary! production: "runtime", // or "prerender" } ``` If you like to customize the Scalar integration, you can [pass a configuration object](https://github.com/scalar/scalar) like this: ```js openAPI: { ui: { scalar: { theme: 'purple' } } } ``` Or if you want to customize the endpoints: ```js openAPI: { route: "/_docs/openapi.json", ui: { scalar: { route: "/_docs/scalar" }, swagger: { route: "/_docs/swagger" } } } ``` ### `future` - Default: `{}` New features pending for a major version to avoid breaking changes. #### `nativeSWR` Uses built-in SWR functionality (using caching layer and storage) for Netlify and Vercel presets instead of falling back to ISR behavior. ```ts export default defineNitroConfig({ future: { nativeSWR: true, }, }); ``` ### `storage` - Default: `{}` Storage configuration, read more in the [Storage Layer](/docs/storage) section. ```ts export default defineNitroConfig({ storage: { redis: { driver: "redis", url: "redis://localhost:6379", }, }, }); ``` ### `devStorage` - Default: `{}` Storage configuration overrides for development mode. ```ts export default defineNitroConfig({ devStorage: { redis: { driver: "fs", base: "./data/redis", // use filesystem in development }, }, }); ``` ### `database` Database connection configurations. Requires `experimental.database: true`. ```ts database: { default: { connector: "sqlite", options: { name: "db" } } } ``` ### `devDatabase` Database connection configuration overrides for development mode. ```ts export default defineNitroConfig({ devDatabase: { default: { connector: "sqlite", options: { name: "db-dev" }, // separate dev database }, }, }); ``` ### `renderer` - Type: `false`{lang=ts} | `{ handler?: string, static?: boolean, template?: string }`{lang=ts} Points to main render entry (file should export an event handler as default). ```ts export default defineNitroConfig({ renderer: { handler: "~/renderer", // path to the render handler }, }); ``` ### `serveStatic` - Type: `boolean`{lang=ts} | `'node'`{lang=ts} | `'deno'`{lang=ts} | `'inline'`{lang=ts} - Default: depends on the deployment preset used. Serve `public/` assets in production. **Note:** It is highly recommended that your edge CDN (Nginx, Apache, Cloud) serves the `.output/public/` directory instead to enable compression and higher level caching. ```ts export default defineNitroConfig({ serveStatic: "node", // serve static assets using Node.js }); ``` ### `noPublicDir` - Default: `false`{lang=ts} If enabled, disables `.output/public` directory creation. Skips copying `public/` dir and also disables pre-rendering. ```ts export default defineNitroConfig({ noPublicDir: true, // skip public directory output }); ``` ### `publicAssets` Public asset directories to serve in development and bundle in production. If a `public/` directory is detected, it will be added by default, but you can add more by yourself too! It's possible to set Cache-Control headers for assets using the `maxAge` option: ```ts publicAssets: [ { baseURL: "images", dir: "public/images", maxAge: 60 * 60 * 24 * 7, // 7 days }, ], ``` The config above generates the following header in the assets under `public/images/` folder: `cache-control: public, max-age=604800, immutable` The `dir` option is where your files live on your file system; the `baseURL` option is the folder they will be accessible from when served/bundled. ### `compressPublicAssets` - Default: `{ gzip: false, brotli: false, zstd: false }`{lang=ts} If enabled, Nitro will generate a pre-compressed (gzip, brotli, and/or zstd) version of supported types of public assets and prerendered routes larger than 1024 bytes into the public directory. Default compression levels are used. Using this option you can support zero overhead asset compression without using a CDN. ```ts export default defineNitroConfig({ compressPublicAssets: { gzip: true, brotli: true, // enable gzip and brotli pre-compression }, }); ``` ### `serverAssets` Assets can be accessed in server logic and bundled in production. [Read more](/docs/assets#server-assets). ```ts export default defineNitroConfig({ serverAssets: [ { baseName: "templates", dir: "./templates", // bundle templates/ as server assets }, ], }); ``` ### `modules` - Default: `[]`{lang=ts} An array of Nitro modules. Modules can be a string (path), a module object with a `setup` function, or a function. ```ts export default defineNitroConfig({ modules: [ "./modules/my-module.ts", (nitro) => { nitro.hooks.hook("compiled", () => { /* ... */ }); }, ], }); ``` ### `plugins` - Default: `[]` An array of paths to nitro plugins. They will be executed by order on the first initialization. Note that Nitro auto-registers the plugins in the `plugins/` directory, [learn more](/docs/plugins). ```ts export default defineNitroConfig({ plugins: [ "~/plugins/my-plugin.ts", ], }); ``` ### `tasks` - Default: `{}` Task definitions. Each key is a task name with a `handler` path and optional `description`. ```ts tasks: { 'db:migrate': { handler: './tasks/db-migrate', description: 'Run database migrations' } } ``` ### `scheduledTasks` - Default: `{}` Map of cron expressions to task name(s). ```ts scheduledTasks: { '0 * * * *': 'cleanup:temp', '*/5 * * * *': ['health:check', 'metrics:collect'] } ``` ### `imports` - Default: `false`{lang=ts} Auto import options. Set to an object to enable. See [unimport](https://github.com/unjs/unimport) for more information. ```ts export default defineNitroConfig({ imports: { dirs: ["./utils"], // auto-import from utils/ directory }, }); ``` ### `virtual` - Default: `{}` A map from dynamic virtual import names to their contents or an (async) function that returns it. ```ts export default defineNitroConfig({ virtual: { "#config": `export default { version: "1.0.0" }`, }, }); ``` ### `ignore` - Default: `[]` Array of glob patterns to ignore when scanning directories. ```ts export default defineNitroConfig({ ignore: [ "routes/_legacy/**", // skip legacy route handlers ], }); ``` ### `wasm` - Default: `{}`{lang=ts} - Type: `false`{lang=ts} | `UnwasmPluginOptions`{lang=ts} WASM support configuration. See [unwasm](https://github.com/unjs/unwasm) for options. ```ts export default defineNitroConfig({ wasm: {}, // enable WASM import support }); ``` ## Dev ### `devServer` - Default: `{ watch: [] }`{lang=ts} Dev server options. You can use `watch` to make the dev server reload if any file changes in specified paths. Supports `port`, `hostname`, `watch`, and `runner` options. ```ts export default defineNitroConfig({ devServer: { port: 3001, watch: ["./server/plugins"], }, }); ``` ### `watchOptions` Watch options for development mode. See [chokidar](https://github.com/paulmillr/chokidar) for more information. ```ts export default defineNitroConfig({ watchOptions: { ignored: ["**/node_modules/**", "**/dist/**"], }, }); ``` ### `devProxy` Proxy configuration for development server. You can use this option to override development server routes and proxy-pass requests. ```js { devProxy: { '/proxy/test': 'http://localhost:3001', '/proxy/example': { target: 'https://example.com', changeOrigin: true } } } ``` See [httpxy](https://github.com/unjs/httpxy) for all available target options. ## Logging ### `logging` - Default: `{ compressedSizes: true, buildSuccess: true }`{lang=ts} Control build logging behavior. Set `compressedSizes` to `false` to skip reporting compressed bundle sizes. Set `buildSuccess` to `false` to suppress the build success message. ```ts export default defineNitroConfig({ logging: { compressedSizes: false, // skip compressed size reporting buildSuccess: false, }, }); ``` ## Routing ### `baseURL` Default: `/`{lang=ts} (or `NITRO_APP_BASE_URL` environment variable if provided) Server's main base URL. ```ts export default defineNitroConfig({ baseURL: "/app/", // serve app under /app/ prefix }); ``` ### `apiBaseURL` - Default: `/api` Changes the default API base URL prefix. ```ts export default defineNitroConfig({ apiBaseURL: "/server/api", // api routes under /server/api/ }); ``` ### `handlers` Server handlers and routes. If `routes/`, `api/` or `middleware/` directories exist inside the server directory, they will be automatically added to the handlers array. ```ts export default defineNitroConfig({ handlers: [ { route: "/health", handler: "./handlers/health.ts" }, { route: "/admin/**", handler: "./handlers/admin.ts", method: "get" }, ], }); ``` ### `devHandlers` Regular handlers refer to the path of handlers to be imported and transformed by the bundler. There are situations in that we directly want to provide a handler instance with programmatic usage. We can use `devHandlers` but note that they are **only available in development mode** and **not in production build**. ```ts export default defineNitroConfig({ devHandlers: [ { route: "/__dev", handler: eventHandler(() => "dev-only route") }, ], }); ``` ### `routes` - Default: `{}`{lang=ts} Inline route definitions. A map from route pattern to handler path or handler options. ```ts export default defineNitroConfig({ routes: { "/hello": "./routes/hello.ts", "/greet": { handler: "./routes/greet.ts", method: "post" }, }, }); ``` ### `errorHandler` - Type: `string`{lang=ts} | `string[]`{lang=ts} Path(s) to custom runtime error handler(s). Replaces nitro's built-in error page. **Example:** ```js [nitro.config] import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ errorHandler: "~/error", }); ``` ```js [error.ts] export default defineNitroErrorHandler((error, event) => { return new Response('[custom error handler] ' + error.stack, { headers: { 'Content-Type': 'text/plain' } }); }); ``` ### `routeRules` **🧪 Experimental!** Route options. It is a map from route pattern (following [rou3](https://github.com/h3js/rou3)) to route options. When `cache` option is set, handlers matching pattern will be automatically wrapped with `defineCachedEventHandler`. See the [Cache API](/docs/cache) for all available cache options. ::note `swr: true|number` is shortcut for `cache: { swr: true, maxAge: number }` :: **Example:** ```js routeRules: { '/blog/**': { swr: true }, '/blog/**': { swr: 600 }, '/blog/**': { static: true }, '/blog/**': { cache: { /* cache options*/ } }, '/assets/**': { headers: { 'cache-control': 's-maxage=0' } }, '/api/v1/**': { cors: true, headers: { 'access-control-allow-methods': 'GET' } }, '/old-page': { redirect: '/new-page' }, // uses status code 307 (Temporary Redirect) '/old-page2': { redirect: { to:'/new-page2', statusCode: 301 } }, '/old-page/**': { redirect: '/new-page/**' }, '/proxy/example': { proxy: 'https://example.com' }, '/proxy/**': { proxy: '/api/**' }, '/admin/**': { basicAuth: { username: 'admin', password: 'secret' } }, } ``` ### `prerender` Default: ```ts { autoSubfolderIndex: true, concurrency: 1, interval: 0, failOnError: false, crawlLinks: false, ignore: [], routes: [], retry: 3, retryDelay: 500 } ``` Prerendered options. Any route specified will be fetched during the build and copied to the `.output/public` directory as a static asset. Any route (string) that starts with a prefix listed in `ignore` or matches a regular expression or function will be ignored. If `crawlLinks` option is set to `true`, nitro starts with `/` by default (or all routes in `routes` array) and for HTML pages extracts `` tags and prerender them as well. You can set `failOnError` option to `true` to stop the CI when Nitro could not prerender a route. The `interval` and `concurrency` options lets you control the speed of pre-rendering, can be useful to avoid hitting some rate-limit if you call external APIs. Set `autoSubfolderIndex` lets you control how to generate the files in the `.output/public` directory: ```bash # autoSubfolderIndex: true (default) /about -> .output/public/about/index.html # autoSubfolderIndex: false /about -> .output/public/about.html ``` This option is useful when your hosting provider does not give you an option regarding the trailing slash. The prerenderer will attempt to render pages 3 times with a delay of 500ms. Use `retry` and `retryDelay` to change this behavior. ## Directories ### `workspaceDir` Project workspace root directory. The workspace (e.g. pnpm workspace) directory is automatically detected when the `workspaceDir` option is not set. ```ts export default defineNitroConfig({ workspaceDir: "../", // monorepo root }); ``` ### `rootDir` Project main directory. ```ts export default defineNitroConfig({ rootDir: "./src/server", }); ``` ### `serverDir` - Default: `false`{lang=ts} - Type: `boolean`{lang=ts} | `"./"` | `"./server"` | `string`{lang=ts} Server directory for scanning `api/`, `routes/`, `plugins/`, `utils/`, `middleware/`, `assets/`, and `tasks/` folders. When set to `false`, automatic directory scanning is disabled. Set to `"./"` to use the root directory, or `"./server"` to use a `server/` subdirectory. ```ts export default defineNitroConfig({ serverDir: "./server", // scan server/ subdirectory }); ``` ### `scanDirs` - Default: (source directory when empty array) List of directories to scan and auto-register files, such as API routes. ```ts export default defineNitroConfig({ scanDirs: ["./modules/auth/api", "./modules/billing/api"], }); ``` ### `apiDir` - Default: `api` Defines a different directory to scan for api route handlers. ```ts export default defineNitroConfig({ apiDir: "endpoints", // scan endpoints/ instead of api/ }); ``` ### `routesDir` - Default: `routes` Defines a different directory to scan for route handlers. ```ts export default defineNitroConfig({ routesDir: "pages", // scan pages/ instead of routes/ }); ``` ### `buildDir` - Default: `node_modules/.nitro` Nitro's temporary working directory for generating build-related files. ```ts export default defineNitroConfig({ buildDir: ".nitro", // use .nitro/ in project root }); ``` ### `output` - Default: `{ dir: '.output', serverDir: '.output/server', publicDir: '.output/public' }` Output directories for production bundle. ```ts export default defineNitroConfig({ output: { dir: "dist", serverDir: "dist/server", publicDir: "dist/public", }, }); ``` ## Build ### `builder` - Type: `"rollup"`{lang=ts} | `"rolldown"`{lang=ts} | `"vite"`{lang=ts} - Default: `undefined`{lang=ts} (auto-detected) Specify the bundler to use for building. ```ts export default defineNitroConfig({ builder: "vite", }); ``` ### `rollupConfig` Additional rollup configuration. ```ts export default defineNitroConfig({ rollupConfig: { output: { manualChunks: { vendor: ["lodash-es"] } }, }, }); ``` ### `rolldownConfig` Additional rolldown configuration. ```ts export default defineNitroConfig({ rolldownConfig: { output: { banner: "/* built with nitro */" }, }, }); ``` ### `entry` Bundler entry point. ```ts export default defineNitroConfig({ entry: "./server/entry.ts", // custom entry file }); ``` ### `unenv` [unenv](https://github.com/unjs/unenv/) preset(s) for environment compatibility. ```ts export default defineNitroConfig({ unenv: { alias: { "my-module": "my-module/web" }, }, }); ``` ### `alias` Path aliases for module resolution. ```ts export default defineNitroConfig({ alias: { "~utils": "./src/utils", "#shared": "./shared", }, }); ``` ### `minify` - Default: `false` Minify bundle. ```ts export default defineNitroConfig({ minify: true, // minify production bundle }); ``` ### `inlineDynamicImports` - Default: `false` Bundle all code into a single file instead of creating separate chunks per route. When `false`, each route handler becomes a separate chunk loaded on-demand. When `true`, everything is bundled together. Some presets enable this by default. ```ts export default defineNitroConfig({ inlineDynamicImports: true, // single output file }); ``` ### `sourcemap` - Default: `false`{lang=ts} Enable source map generation. See [options](https://rollupjs.org/configuration-options/#output-sourcemap). ```ts export default defineNitroConfig({ sourcemap: true, // generate .map files }); ``` ### `node` - Default: `true`{lang=ts} Specify whether the build is used for Node.js or not. If set to `false`, nitro tries to mock Node.js dependencies using [unenv](https://github.com/unjs/unenv) and adjust its behavior. ```ts export default defineNitroConfig({ node: false, // target non-Node.js runtimes }); ``` ### `moduleSideEffects` Default: `['unenv/polyfill/']` Specifies module imports that have side-effects. ```ts export default defineNitroConfig({ moduleSideEffects: ["unenv/polyfill/", "reflect-metadata"], }); ``` ### `replace` Build-time string replacements. ```ts export default defineNitroConfig({ replace: { "process.env.APP_VERSION": JSON.stringify("1.0.0"), }, }); ``` ### `commonJS` Specifies additional configuration for the rollup CommonJS plugin. ```ts export default defineNitroConfig({ commonJS: { requireReturnsDefault: "auto", }, }); ``` ### `exportConditions` Custom export conditions for module resolution. ```ts export default defineNitroConfig({ exportConditions: ["worker", "production"], }); ``` ### `noExternals` - Default: `false`{lang=ts} Prevent specific packages from being externalized. Set to `true` to bundle all dependencies, or pass an array of package names/patterns. ```ts export default defineNitroConfig({ noExternals: true, // bundle all dependencies }); ``` ### `traceDeps` - Default: `[]`{lang=ts} Additional dependencies to trace and include in the build output. ```ts export default defineNitroConfig({ traceDeps: ["sharp", "better-sqlite3"], }); ``` ### `oxc` OXC options for rolldown builds. Includes `minify` and `transform` sub-options. ```ts export default defineNitroConfig({ oxc: { minify: { compress: true, mangle: true }, }, }); ``` ## Advanced ### `dev` - Default: `true` for development and `false` for production. **⚠️ Caution! This is an advanced configuration. Things can go wrong if misconfigured.** ```ts export default defineNitroConfig({ dev: true, // force development mode behavior }); ``` ### `typescript` Default: `{ strict: true, generateRuntimeConfigTypes: false, generateTsConfig: false }`{lang=ts} TypeScript configuration options including `strict`, `generateRuntimeConfigTypes`, `generateTsConfig`, `tsConfig`, `generatedTypesDir`, and `tsconfigPath`. ```ts export default defineNitroConfig({ typescript: { strict: true, generateTsConfig: true, }, }); ``` ### `hooks` **⚠️ Caution! This is an advanced configuration. Things can go wrong if misconfigured.** nitro hooks. See [hookable](https://github.com/unjs/hookable) for more information. ```ts export default defineNitroConfig({ hooks: { compiled(nitro) { console.log("Build compiled successfully!"); }, }, }); ``` ### `commands` **⚠️ Caution! This is an advanced configuration. Things can go wrong if misconfigured.** Preview and deploy command hints are usually filled by deployment presets. ```ts export default defineNitroConfig({ commands: { preview: "node ./server/index.mjs", }, }); ``` ### `devErrorHandler` **⚠️ Caution! This is an advanced configuration. Things can go wrong if misconfigured.** A custom error handler function for development errors. ```ts export default defineNitroConfig({ devErrorHandler: (error, event) => { return new Response(`Dev error: ${error.message}`, { status: 500 }); }, }); ``` ### `framework` - Default: `{ name: "nitro", version: "" }`{lang=ts} Framework information. Used by presets and build info. Typically set by higher-level frameworks (e.g. Nuxt). ```ts export default defineNitroConfig({ framework: { name: "my-framework", version: "2.0.0" }, }); ``` ## Preset options ### `firebase` The options for the firebase functions preset. See [Preset Docs](/deploy/providers/firebase#options) ```ts export default defineNitroConfig({ firebase: { gen: 2, // use Cloud Functions 2nd gen region: "us-central1", }, }); ``` ### `vercel` The options for the vercel preset. See [Preset Docs](/deploy/providers/vercel) ```ts export default defineNitroConfig({ vercel: { config: { runtime: "nodejs20.x" }, }, }); ``` ### `cloudflare` The options for the cloudflare preset. See [Preset Docs](/deploy/providers/cloudflare) ```ts export default defineNitroConfig({ cloudflare: { wrangler: { compatibility_date: "2025-01-01" }, }, }); ``` ### `zephyr` The options for the zephyr preset. See [Preset Docs](/deploy/providers/zephyr#options) ================================================ FILE: docs/4.examples/0.index.md ================================================ --- icon: i-lucide-folder-code --- # Examples > Explore Nitro examples to learn how to build full-stack applications ================================================ FILE: docs/4.examples/api-routes.md ================================================ --- category: features icon: i-lucide-route --- # API Routes > File-based API routing with HTTP method support and dynamic parameters. ::code-tree{defaultValue="api/hello.ts" expandAll} ```html [index.html] API Routes

API Routes:

``` ```ts [nitro.config.ts] import { defineConfig } from "nitro"; export default defineConfig({ serverDir: "./", }); ``` ```json [package.json] { "type": "module", "scripts": { "dev": "nitro dev", "build": "nitro build" }, "devDependencies": { "nitro": "latest" } } ``` ```json [tsconfig.json] { "extends": "nitro/tsconfig" } ``` ```ts [vite.config.ts] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ``` ```ts [api/hello.ts] import { defineHandler } from "nitro"; export default defineHandler(() => "Nitro is amazing!"); ``` ```ts [api/test.get.ts] import { defineHandler } from "nitro"; export default defineHandler(() => "Test get handler"); ``` ```ts [api/test.post.ts] import { defineHandler } from "nitro"; export default defineHandler(async (event) => { const body = await event.req.json(); return { message: "Test post handler", body, }; }); ``` ```ts [api/hello/[name].ts] import { defineHandler } from "nitro"; export default defineHandler((event) => `Hello (param: ${event.context.params!.name})!`); ``` :: Nitro supports file-based routing in the `api/` or `routes/` directory. Each file becomes an API endpoint based on its path. ## Basic Route Create a file in the `api/` directory to define a route. The file path becomes the URL path: ```ts [api/hello.ts] import { defineHandler } from "nitro"; export default defineHandler(() => "Nitro is amazing!"); ``` This creates a `GET /api/hello` endpoint. ## Dynamic Routes Use square brackets `[param]` for dynamic URL segments. Access params via `event.context.params`: ```ts [api/hello/[name].ts] import { defineHandler } from "nitro"; export default defineHandler((event) => `Hello (param: ${event.context.params!.name})!`); ``` This creates a `GET /api/hello/:name` endpoint (e.g., `/api/hello/world`). ## HTTP Methods Suffix your file with the HTTP method (`.get.ts`, `.post.ts`, `.put.ts`, `.delete.ts`, etc.): ### GET Handler ```ts [api/test.get.ts] import { defineHandler } from "nitro"; export default defineHandler(() => "Test get handler"); ``` ### POST Handler ```ts [api/test.post.ts] import { defineHandler } from "nitro"; export default defineHandler(async (event) => { const body = await event.req.json(); return { message: "Test post handler", body, }; }); ``` ## Learn More - [Routing](/docs/routing) ================================================ FILE: docs/4.examples/auto-imports.md ================================================ --- category: config icon: i-lucide-import --- # Auto Imports > Automatic imports for utilities and composables. ::code-tree{defaultValue="nitro.config.ts" expandAll} ```ts [nitro.config.ts] import { defineConfig } from "nitro"; export default defineConfig({ serverDir: true, imports: {}, }); ``` ```json [package.json] { "type": "module", "scripts": { "dev": "nitro dev", "build": "nitro build" }, "devDependencies": { "nitro": "latest" } } ``` ```ts [server.ts] import { defineHandler } from "nitro"; import { makeGreeting } from "./server/utils/hello.ts"; export default defineHandler(() => `

${makeGreeting("Nitro")}

`); ``` ```json [tsconfig.json] { "include": [".nitro/types/nitro-imports.d.ts", "src"] } ``` ```ts [vite.config.ts] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ``` ```ts [server/utils/hello.ts] export function makeGreeting(name: string) { return `Hello, ${name}!`; } ``` :: Functions exported from `server/utils/` are automatically available without explicit imports when auto-imports are enabled. Define a utility once and use it anywhere in your server code. ## Configuration Enable auto-imports by setting `imports` in your config: ```ts [nitro.config.ts] import { defineConfig } from "nitro"; export default defineConfig({ serverDir: true, imports: {}, }); ``` ## Using Auto Imports 1. Create a utility file in `server/utils/`: ```ts [server/utils/hello.ts] export function makeGreeting(name: string) { return `Hello, ${name}!`; } ``` 2. The function is available without importing it: ```ts [server.ts] import { defineHandler } from "nitro"; import { makeGreeting } from "./server/utils/hello.ts"; export default defineHandler(() => `

${makeGreeting("Nitro")}

`); ``` With this setup, any function exported from `server/utils/` becomes globally available. Nitro scans the directory and generates the necessary imports automatically. ## Learn More - [Configuration](/docs/configuration) ================================================ FILE: docs/4.examples/cached-handler.md ================================================ --- category: features icon: i-lucide-clock --- # Cached Handler > Cache route responses with configurable bypass logic. ::code-tree{defaultValue="server.ts" expandAll} ```ts [nitro.config.ts] import { defineConfig } from "nitro"; export default defineConfig({}); ``` ```json [package.json] { "type": "module", "scripts": { "dev": "nitro dev", "build": "nitro build" }, "devDependencies": { "nitro": "latest" } } ``` ```ts [server.ts] import { html } from "nitro"; import { defineCachedHandler } from "nitro/cache"; export default defineCachedHandler( async () => { await new Promise((resolve) => setTimeout(resolve, 500)); return html` Response generated at ${new Date().toISOString()} (took 500ms)
(skip cache) `; }, { shouldBypassCache: ({ req }) => req.url.includes("skipCache=true") } ); ``` ```json [tsconfig.json] { "extends": "nitro/tsconfig" } ``` ```ts [vite.config.ts] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ``` :: This example shows how to cache an expensive operation (a 500 ms delay) and conditionally bypass the cache using a query parameter. On first request, the handler executes and caches the result. Subsequent requests return the cached response instantly until the cache expires or is bypassed. ## How It Works ```ts [server.ts] import { html } from "nitro"; import { defineCachedHandler } from "nitro/cache"; export default defineCachedHandler( async () => { await new Promise((resolve) => setTimeout(resolve, 500)); return html` Response generated at ${new Date().toISOString()} (took 500ms)
(skip cache) `; }, { shouldBypassCache: ({ req }) => req.url.includes("skipCache=true") } ); ``` The handler simulates a slow operation with a 500ms delay. As `defineCachedHandler` wraps it, the response is cached after the first execution. The `shouldBypassCache` option checks for `?skipCache=true` in the URL and when present the cache is skipped and the handler runs fresh. ## Learn More - [Cache](/docs/cache) - [Storage](/docs/storage) ================================================ FILE: docs/4.examples/custom-error-handler.md ================================================ --- category: features icon: i-lucide-alert-circle --- # Custom Error Handler > Customize error responses with a global error handler. ::code-tree{defaultValue="error.ts" expandAll} ```ts [error.ts] import { defineErrorHandler } from "nitro"; export default defineErrorHandler((error, _event) => { return new Response(`Custom Error Handler: ${error.message}`, { status: 500, headers: { "Content-Type": "text/plain" }, }); }); ``` ```ts [nitro.config.ts] import { defineConfig } from "nitro"; // import errorHandler from "./error"; export default defineConfig({ errorHandler: "./error.ts", // devErrorHandler: errorHandler, }); ``` ```json [package.json] { "type": "module", "scripts": { "dev": "nitro dev", "build": "nitro build" }, "devDependencies": { "nitro": "latest" } } ``` ```ts [server.ts] import { defineHandler, HTTPError } from "nitro"; export default defineHandler(() => { throw new HTTPError("Example Error!", { status: 500 }); }); ``` ```json [tsconfig.json] { "extends": "nitro/tsconfig" } ``` ```ts [vite.config.ts] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ``` :: This example shows how to intercept all errors and return a custom response format. When any route throws an error, Nitro calls your error handler instead of returning the default error page. ## Error Handler Create an `error.ts` file in your project root to define the global error handler: ```ts [error.ts] import { defineErrorHandler } from "nitro"; export default defineErrorHandler((error, _event) => { return new Response(`Custom Error Handler: ${error.message}`, { status: 500, headers: { "Content-Type": "text/plain" }, }); }); ``` The handler receives the thrown error and the H3 event object. You can use the event to access request details like headers, cookies, or the URL path to customize responses per route. ## Triggering an Error The main handler throws an error to demonstrate the custom error handler: ```ts [server.ts] import { defineHandler, HTTPError } from "nitro"; export default defineHandler(() => { throw new HTTPError("Example Error!", { status: 500 }); }); ``` When you visit the page, instead of seeing a generic error page, you'll see "Custom Error Handler: Example Error!" because the error handler intercepts the thrown error. ## Learn More - [Server Entry](/docs/server-entry) ================================================ FILE: docs/4.examples/database.md ================================================ --- category: features icon: i-lucide-database --- # Database > Built-in database support with SQL template literals. ::code-tree{defaultValue="server.ts" expandAll} ```ts [nitro.config.ts] import { defineConfig } from "nitro"; export default defineConfig({ experimental: { database: true, tasks: true, }, database: { default: { connector: "sqlite" }, }, }); ``` ```json [package.json] { "type": "module", "scripts": { "dev": "nitro dev", "build": "nitro build" }, "devDependencies": { "nitro": "latest" } } ``` ```ts [server.ts] import { defineHandler } from "nitro"; import { useDatabase } from "nitro/database"; export default defineHandler(async () => { const db = useDatabase(); // Create users table await db.sql`DROP TABLE IF EXISTS users`; await db.sql`CREATE TABLE IF NOT EXISTS users ("id" TEXT PRIMARY KEY, "firstName" TEXT, "lastName" TEXT, "email" TEXT)`; // Add a new user const userId = String(Math.round(Math.random() * 10_000)); await db.sql`INSERT INTO users VALUES (${userId}, 'John', 'Doe', '')`; // Query for users const { rows } = await db.sql`SELECT * FROM users WHERE id = ${userId}`; return { rows, }; }); ``` ```json [tsconfig.json] { "extends": "nitro/tsconfig" } ``` ```ts [vite.config.ts] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ``` ```ts [tasks/db/migrate.ts] import { defineTask } from "nitro/task"; import { useDatabase } from "nitro/database"; export default defineTask({ meta: { description: "Run database migrations", }, async run() { const db = useDatabase(); console.log("Running database migrations..."); // Create users table await db.sql`DROP TABLE IF EXISTS users`; await db.sql`CREATE TABLE IF NOT EXISTS users ("id" TEXT PRIMARY KEY, "firstName" TEXT, "lastName" TEXT, "email" TEXT)`; return { result: "Database migrations complete!", }; }, }); ``` :: Nitro provides a built-in database layer that uses SQL template literals for safe, parameterized queries. This example creates a users table, inserts a record, and queries it back. ## Querying the Database ```ts [server.ts] import { defineHandler } from "nitro"; import { useDatabase } from "nitro/database"; export default defineHandler(async () => { const db = useDatabase(); // Create users table await db.sql`DROP TABLE IF EXISTS users`; await db.sql`CREATE TABLE IF NOT EXISTS users ("id" TEXT PRIMARY KEY, "firstName" TEXT, "lastName" TEXT, "email" TEXT)`; // Add a new user const userId = String(Math.round(Math.random() * 10_000)); await db.sql`INSERT INTO users VALUES (${userId}, 'John', 'Doe', '')`; // Query for users const { rows } = await db.sql`SELECT * FROM users WHERE id = ${userId}`; return { rows, }; }); ``` Retrieve the database instance using `useDatabase()`. The database can be queried using `db.sql`, and variables like `${userId}` are automatically escaped to prevent SQL injection. ## Running Migrations with Tasks Nitro tasks let you run operations outside of request handlers. For database migrations, create a task file in `tasks/` and run it via the CLI. This keeps schema changes separate from your application code. ```ts [tasks/db/migrate.ts] import { defineTask } from "nitro/task"; import { useDatabase } from "nitro/database"; export default defineTask({ meta: { description: "Run database migrations", }, async run() { const db = useDatabase(); console.log("Running database migrations..."); // Create users table await db.sql`DROP TABLE IF EXISTS users`; await db.sql`CREATE TABLE IF NOT EXISTS users ("id" TEXT PRIMARY KEY, "firstName" TEXT, "lastName" TEXT, "email" TEXT)`; return { result: "Database migrations complete!", }; }, }); ``` ## Learn More - [Database](/docs/database) - [Tasks](/docs/tasks) ================================================ FILE: docs/4.examples/elysia.md ================================================ --- category: backend frameworks icon: i-skill-icons-elysia-dark --- # Elysia > Integrate Elysia with Nitro using the server entry. ::code-tree{defaultValue="server.ts" expandAll} ```ts [nitro.config.ts] import { defineConfig } from "nitro"; export default defineConfig({}); ``` ```json [package.json] { "type": "module", "scripts": { "build": "nitro build", "dev": "nitro dev" }, "devDependencies": { "elysia": "^1.4.28", "nitro": "latest" } } ``` ```ts [server.ts] import { Elysia } from "elysia"; const app = new Elysia(); app.get("/", () => "Hello, Elysia with Nitro!"); export default app.compile(); ``` ```json [tsconfig.json] { "extends": "nitro/tsconfig" } ``` ```ts [vite.config.ts] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ``` :: ## Server Entry ```ts [server.ts] import { Elysia } from "elysia"; const app = new Elysia(); app.get("/", () => "Hello, Elysia with Nitro!"); export default app.compile(); ``` Nitro auto-detects `server.ts` in your project root and uses it as the server entry. The Elysia app handles all incoming requests, giving you full control over routing and middleware. Call `app.compile()` before exporting to optimize the router for production. ## Learn More - [Server Entry](/docs/server-entry) - [Elysia Documentation](https://elysiajs.com/) ================================================ FILE: docs/4.examples/express.md ================================================ --- category: backend frameworks icon: i-simple-icons-express --- # Express > Integrate Express with Nitro using the server entry. ::code-tree{defaultValue="server.node.ts" expandAll} ```ts [nitro.config.ts] import { defineConfig } from "nitro"; export default defineConfig({}); ``` ```json [package.json] { "type": "module", "scripts": { "build": "nitro build", "dev": "nitro dev" }, "devDependencies": { "@types/express": "^5.0.6", "express": "^5.2.1", "nitro": "latest" } } ``` ```ts [server.node.ts] import Express from "express"; const app = Express(); app.use("/", (_req, res) => { res.send("Hello from Express with Nitro!"); }); export default app; ``` ```json [tsconfig.json] { "extends": "nitro/tsconfig" } ``` ```ts [vite.config.ts] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ``` :: ## Server Entry ```ts [server.node.ts] import Express from "express"; const app = Express(); app.use("/", (_req, res) => { res.send("Hello from Express with Nitro!"); }); export default app; ``` Nitro auto-detects `server.node.ts` in your project root and uses it as the server entry. The Express app handles all incoming requests, giving you full control over routing and middleware. ::note The `.node.ts` suffix indicates this entry is Node.js specific and won't work in other runtimes like Cloudflare Workers or Deno. :: ## Learn More - [Server Entry](/docs/server-entry) - [Express Documentation](https://expressjs.com/) ================================================ FILE: docs/4.examples/fastify.md ================================================ --- category: backend frameworks icon: i-simple-icons-fastify --- # Fastify > Integrate Fastify with Nitro using the server entry. ::code-tree{defaultValue="server.node.ts" expandAll} ```ts [nitro.config.ts] import { defineConfig } from "nitro"; export default defineConfig({}); ``` ```json [package.json] { "type": "module", "scripts": { "build": "nitro build", "dev": "nitro dev" }, "devDependencies": { "fastify": "^5.8.2", "nitro": "latest" } } ``` ```ts [server.node.ts] import Fastify from "fastify"; const app = Fastify(); app.get("/", () => "Hello, Fastify with Nitro!"); await app.ready(); export default app.routing; ``` ```json [tsconfig.json] { "extends": "nitro/tsconfig" } ``` ```ts [vite.config.ts] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ``` :: ## Server Entry ```ts [server.node.ts] import Fastify from "fastify"; const app = Fastify(); app.get("/", () => "Hello, Fastify with Nitro!"); await app.ready(); export default app.routing; ``` Nitro auto-detects `server.node.ts` in your project root and uses it as the server entry. Call `await app.ready()` to initialize all registered plugins before exporting. Export `app.routing` (not `app`) to provide Nitro with the request handler function. ::note The `.node.ts` suffix indicates this entry is Node.js specific and won't work in other runtimes like Cloudflare Workers or Deno. :: ## Learn More - [Server Entry](/docs/server-entry) - [Fastify Documentation](https://fastify.dev/) ================================================ FILE: docs/4.examples/hello-world.md ================================================ --- category: features icon: i-lucide-sparkles --- # Hello World > Minimal Nitro server using the web standard fetch handler. ::code-tree{defaultValue="server.ts" expandAll} ```ts [nitro.config.ts] import { defineConfig } from "nitro"; export default defineConfig({}); ``` ```json [package.json] { "type": "module", "scripts": { "build": "nitro build", "dev": "nitro dev", "preview": "node .output/server/index.mjs" }, "devDependencies": { "nitro": "latest" } } ``` ```ts [server.ts] export default { fetch(req: Request) { return new Response("Nitro Works!"); }, }; ``` ```json [tsconfig.json] { "extends": "nitro/tsconfig" } ``` ```ts [vite.config.ts] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ``` :: The simplest Nitro server. Export an object with a `fetch` method that receives a standard `Request` and returns a `Response`. No frameworks, no abstractions, just the web platform. ## Server Entry ```ts [server.ts] export default { fetch(req: Request) { return new Response("Nitro Works!"); }, }; ``` The `fetch` method follows the same signature as Service Workers and Cloudflare Workers. This pattern works across all deployment targets because it uses web standards. Add the Nitro plugin to Vite and it handles the rest: dev server, hot reloading, and production builds. ## Learn More - [Server Entry](/docs/server-entry) - [Configuration](/docs/configuration) ================================================ FILE: docs/4.examples/hono.md ================================================ --- category: backend frameworks icon: i-logos-hono --- # Hono > Integrate Hono with Nitro using the server entry. ::code-tree{defaultValue="server.ts" expandAll} ```ts [nitro.config.ts] import { defineConfig } from "nitro"; export default defineConfig({}); ``` ```json [package.json] { "type": "module", "scripts": { "build": "nitro build", "dev": "nitro dev" }, "devDependencies": { "hono": "^4.12.8", "nitro": "latest" } } ``` ```ts [server.ts] import { Hono } from "hono"; const app = new Hono(); app.get("/", (c) => { return c.text("Hello, Hono with Nitro!"); }); export default app; ``` ```json [tsconfig.json] { "extends": "nitro/tsconfig" } ``` ```ts [vite.config.ts] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ``` :: ## Server Entry ```ts [server.ts] import { Hono } from "hono"; const app = new Hono(); app.get("/", (c) => { return c.text("Hello, Hono with Nitro!"); }); export default app; ``` Nitro auto-detects `server.ts` in your project root and uses it as the server entry. The Hono app handles all incoming requests, giving you full control over routing and middleware. Hono is cross-runtime compatible, so this server entry works across all Nitro deployment targets including Node.js, Deno, Bun, and Cloudflare Workers. ## Learn More - [Server Entry](/docs/server-entry) - [Hono Documentation](https://hono.dev/) ================================================ FILE: docs/4.examples/import-alias.md ================================================ --- category: config icon: i-lucide-at-sign --- # Import Alias > Custom import aliases for cleaner module paths. ::code-tree{defaultValue="server/routes/index.ts" expandAll} ```ts [nitro.config.ts] import { defineConfig } from "nitro"; export default defineConfig({ serverDir: true, }); ``` ```json [package.json] { "type": "module", "imports": { "#server/*": "./server/*" }, "scripts": { "build": "nitro build", "dev": "nitro dev", "preview": "node .output/server/index.mjs" }, "devDependencies": { "nitro": "latest" } } ``` ```json [tsconfig.json] { "extends": "nitro/tsconfig", "compilerOptions": { "paths": { "~server/*": ["./server/*"] } } } ``` ```ts [vite.config.ts] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()], resolve: { tsconfigPaths: true } }); ``` ```ts [server/routes/index.ts] import { sum } from "~server/utils/math.ts"; import { rand } from "#server/utils/math.ts"; export default () => { const [a, b] = [rand(1, 10), rand(1, 10)]; const result = sum(a, b); return `The sum of ${a} + ${b} = ${result}`; }; ``` ```ts [server/utils/math.ts] export function rand(min: number, max: number): number { return Math.floor(Math.random() * (max - min + 1)) + min; } export function sum(a: number, b: number): number { return a + b; } ``` :: Import aliases like `~` and `#` let you reference modules with shorter paths instead of relative imports. ## Importing Using Aliases ```ts [server/routes/index.ts] import { sum } from "~server/utils/math.ts"; import { rand } from "#server/utils/math.ts"; export default () => { const [a, b] = [rand(1, 10), rand(1, 10)]; const result = sum(a, b); return `The sum of ${a} + ${b} = ${result}`; }; ``` The route imports the `sum` function using `~server/` and `rand` using `#server/`. Both resolve to the same `server/utils/math.ts` file. The handler generates two random numbers and returns their sum. ## Configuration Aliases can be configured in `package.json` imports field or `nitro.config.ts`. ## Learn More - [Configuration](/docs/configuration) ================================================ FILE: docs/4.examples/middleware.md ================================================ --- category: features icon: i-lucide-layers --- # Middleware > Request middleware for authentication, logging, and request modification. ::code-tree{defaultValue="server/middleware/auth.ts" expandAll} ```ts [nitro.config.ts] import { defineConfig } from "nitro"; export default defineConfig({ serverDir: true, }); ``` ```json [package.json] { "type": "module", "scripts": { "dev": "nitro dev", "build": "nitro build" }, "devDependencies": { "nitro": "latest" } } ``` ```ts [server.ts] import { defineHandler } from "nitro"; export default defineHandler((event) => ({ auth: event.context.auth, })); ``` ```json [tsconfig.json] { "extends": "nitro/tsconfig" } ``` ```ts [vite.config.ts] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ``` ```ts [server/middleware/auth.ts] import { defineMiddleware } from "nitro"; export default defineMiddleware((event) => { event.context.auth = { name: "User " + Math.round(Math.random() * 100) }; }); ``` :: Middleware functions run before route handlers on every request. They can modify the request, add context, or return early responses. ## Defining Middleware Create files in `server/middleware/`. They run in alphabetical order: ```ts [server/middleware/auth.ts] import { defineMiddleware } from "nitro"; export default defineMiddleware((event) => { event.context.auth = { name: "User " + Math.round(Math.random() * 100) }; }); ``` Middleware can: - Add data to `event.context` for use in handlers - Return a response early to short-circuit the request - Modify request headers or other properties ## Accessing Context in Handlers Data added to `event.context` in middleware is available in all subsequent handlers: ```ts [server.ts] import { defineHandler } from "nitro"; export default defineHandler((event) => ({ auth: event.context.auth, })); ``` ## Learn More - [Routing](/docs/routing) ================================================ FILE: docs/4.examples/mono-jsx.md ================================================ --- category: server side rendering icon: i-lucide-brackets --- # Mono JSX > Server-side JSX rendering in Nitro with mono-jsx. ::code-tree{defaultValue="server.tsx" expandAll} ```ts [nitro.config.ts] import { defineConfig } from "nitro"; export default defineConfig({}); ``` ```json [package.json] { "type": "module", "scripts": { "dev": "nitro dev", "build": "nitro build" }, "devDependencies": { "mono-jsx": "latest", "nitro": "latest" } } ``` ```tsx [server.tsx] export default () => (

Nitro + mono-jsx works!

); ``` ```json [tsconfig.json] { "extends": "nitro/tsconfig", "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "mono-jsx" } } ``` ```ts [vite.config.ts] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ``` :: ## Server Entry ```tsx [server.tsx] export default () => (

Nitro + mono-jsx works!

); ``` Nitro auto-detects `server.tsx` and uses mono-jsx to transform JSX into HTML. Export a function that returns JSX, and Nitro sends the rendered HTML as the response. ## Learn More - [Renderer](/docs/renderer) - [mono-jsx](https://github.com/aspect-dev/mono-jsx) ================================================ FILE: docs/4.examples/nano-jsx.md ================================================ --- category: server side rendering icon: i-lucide-brackets --- # Nano JSX > Server-side JSX rendering in Nitro with nano-jsx. ::code-tree{defaultValue="server.tsx" expandAll} ```ts [nitro.config.ts] import { defineConfig } from "nitro"; export default defineConfig({}); ``` ```json [package.json] { "type": "module", "scripts": { "dev": "nitro dev", "build": "nitro build" }, "devDependencies": { "nano-jsx": "^0.2.1", "nitro": "latest" } } ``` ```tsx [server.tsx] import { defineHandler, html } from "nitro"; import { renderSSR } from "nano-jsx"; export default defineHandler(() => { return html(renderSSR(() =>

Nitro + nano-jsx works!

)); }); ``` ```json [tsconfig.json] { "extends": "nitro/tsconfig", "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "nano-jsx/esm" } } ``` ```ts [vite.config.ts] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ``` :: ## Server Entry ```tsx [server.tsx] import { defineHandler, html } from "nitro"; import { renderSSR } from "nano-jsx"; export default defineHandler(() => { return html(renderSSR(() =>

Nitro + nano-jsx works!

)); }); ``` Nitro auto-detects `server.tsx` and uses it as the server entry. Use `renderSSR` from nano-jsx to convert JSX into an HTML string. The `html` helper from H3 sets the correct content type header. ## Learn More - [Renderer](/docs/renderer) - [nano-jsx](https://nanojsx.io/) ================================================ FILE: docs/4.examples/plugins.md ================================================ --- category: features icon: i-lucide-plug --- # Plugins > Extend Nitro with custom plugins for hooks and lifecycle events. ::code-tree{defaultValue="server/plugins/test.ts" expandAll} ```ts [nitro.config.ts] import { defineConfig } from "nitro"; export default defineConfig({ serverDir: true, }); ``` ```json [package.json] { "type": "module", "scripts": { "dev": "nitro dev", "build": "nitro build" }, "devDependencies": { "nitro": "latest" } } ``` ```ts [server.ts] import { eventHandler } from "h3"; export default eventHandler(() => "

Hello Nitro!

"); ``` ```json [tsconfig.json] { "extends": "nitro/tsconfig" } ``` ```ts [vite.config.ts] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ``` ```ts [server/plugins/test.ts] import { definePlugin } from "nitro"; import { useNitroHooks } from "nitro/app"; export default definePlugin((nitroApp) => { const hooks = useNitroHooks(); hooks.hook("response", (event) => { event.headers.set("content-type", "html; charset=utf-8"); }); }); ``` :: Plugins let you hook into Nitro's runtime lifecycle. This example shows a plugin that modifies the `Content-Type` header on every response. Create files in `server/plugins/` and they're automatically loaded at startup. ## Defining a Plugin ```ts [server/plugins/test.ts] import { definePlugin } from "nitro"; import { useNitroHooks } from "nitro/app"; export default definePlugin((nitroApp) => { const hooks = useNitroHooks(); hooks.hook("response", (event) => { event.headers.set("content-type", "html; charset=utf-8"); }); }); ``` The plugin uses `useNitroHooks()` to access the hooks system, then registers a `response` hook that runs after every request. Here it sets the content type to HTML, but you could log requests, add security headers, or modify responses in any way. ## Main Handler ```ts [server.ts] import { eventHandler } from "h3"; export default eventHandler(() => "

Hello Nitro!

"); ``` The handler returns HTML without setting a content type. The plugin automatically adds the correct `Content-Type: html; charset=utf-8` header to the response. ## Learn More - [Plugins](/docs/plugins) - [Lifecycle](/docs/lifecycle) ================================================ FILE: docs/4.examples/renderer.md ================================================ --- category: server side rendering icon: i-lucide-code --- # Custom Renderer > Build a custom HTML renderer in Nitro with server-side data fetching. ::code-tree{defaultValue="renderer.ts" expandAll} ```ts [nitro.config.ts] import { defineConfig } from "nitro"; export default defineConfig({ serverDir: "./", renderer: { handler: "./renderer" }, }); ``` ```json [package.json] { "type": "module", "scripts": { "dev": "nitro dev", "build": "nitro build" }, "devDependencies": { "nitro": "latest" } } ``` ```ts [renderer.ts] import { fetch } from "nitro"; export default async function renderer({ url }: { req: Request; url: URL }) { const apiRes = await fetch("/api/hello").then((res) => res.text()); return new Response( /* html */ ` Custom Renderer

Hello from custom renderer!

Current path: ${url.pathname}

API says: ${apiRes}

`, { headers: { "content-type": "text/html; charset=utf-8" } } ); } ``` ```json [tsconfig.json] { "extends": "nitro/tsconfig" } ``` ```ts [vite.config.ts] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ``` ```ts [api/hello.ts] import { defineHandler } from "nitro"; export default defineHandler(() => "Nitro is amazing!"); ``` :: Create a custom renderer that generates HTML responses with data from API routes. Use Nitro's internal `fetch` to call routes without network overhead. ## Renderer ```ts [renderer.ts] import { fetch } from "nitro"; export default async function renderer({ url }: { req: Request; url: URL }) { const apiRes = await fetch("/api/hello").then((res) => res.text()); return new Response( /* html */ ` Custom Renderer

Hello from custom renderer!

Current path: ${url.pathname}

API says: ${apiRes}

`, { headers: { "content-type": "text/html; charset=utf-8" } } ); } ``` Nitro auto-detects `renderer.ts` in your project root and uses it for all non-API routes. The renderer function receives the request URL and returns a `Response`. Use `fetch` from `nitro` to call API routes without network overhead—these requests stay in-process. ## API Route ```ts [api/hello.ts] import { defineHandler } from "nitro"; export default defineHandler(() => "Nitro is amazing!"); ``` Define API routes in the `api/` directory. When the renderer calls `fetch("/api/hello")`, this handler runs and returns its response. ## Learn More - [Renderer](/docs/renderer) ================================================ FILE: docs/4.examples/runtime-config.md ================================================ --- category: config icon: i-lucide-settings --- # Runtime Config > Environment-aware configuration with runtime access. ::code-tree{defaultValue="nitro.config.ts" expandAll} ```ts [nitro.config.ts] import { defineConfig } from "nitro"; export default defineConfig({ serverDir: "./", runtimeConfig: { apiKey: "", }, }); ``` ```json [package.json] { "type": "module", "scripts": { "dev": "nitro dev", "build": "nitro build" }, "devDependencies": { "nitro": "latest" } } ``` ```ts [server.ts] import { defineHandler } from "nitro"; import { useRuntimeConfig } from "nitro/runtime-config"; export default defineHandler((event) => { const runtimeConfig = useRuntimeConfig(); return { runtimeConfig }; }); ``` ```json [tsconfig.json] { "extends": "nitro/tsconfig" } ``` ```ts [vite.config.ts] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ``` :: Runtime config lets you define configuration values that can be overridden by environment variables at runtime. ## Define Config Schema Declare your runtime config with default values in `nitro.config.ts`: ```ts [nitro.config.ts] import { defineConfig } from "nitro"; export default defineConfig({ serverDir: "./", runtimeConfig: { apiKey: "", }, }); ``` ## Access at Runtime Use `useRuntimeConfig` to access configuration values in your handlers: ```ts [server.ts] import { defineHandler } from "nitro"; import { useRuntimeConfig } from "nitro/runtime-config"; export default defineHandler((event) => { const runtimeConfig = useRuntimeConfig(); return { runtimeConfig }; }); ``` ## Environment Variables Override config values via environment variables prefixed with `NITRO_`: ```sh [.env] # NEVER COMMIT SENSITIVE DATA. THIS IS ONLY FOR DEMO PURPOSES. NITRO_API_KEY=secret-api-key ``` ## Learn More - [Configuration](/docs/configuration) ================================================ FILE: docs/4.examples/server-fetch.md ================================================ --- category: features icon: i-lucide-arrow-right-left --- # Server Fetch > Internal server-to-server requests without network overhead. ::code-tree{defaultValue="routes/index.ts" expandAll} ```ts [nitro.config.ts] import { defineConfig, serverFetch } from "nitro"; export default defineConfig({ serverDir: "./", hooks: { "dev:start": async () => { const res = await serverFetch("/hello"); const text = await res.text(); console.log("Fetched /hello in nitro module:", res.status, text); }, }, }); ``` ```json [package.json] { "type": "module", "scripts": { "dev": "nitro dev", "build": "nitro build" }, "devDependencies": { "nitro": "latest" } } ``` ```json [tsconfig.json] { "extends": "nitro/tsconfig" } ``` ```ts [vite.config.ts] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ``` ```ts [routes/hello.ts] import { defineHandler } from "nitro"; export default defineHandler(() => "Hello!"); ``` ```ts [routes/index.ts] import { defineHandler } from "nitro"; import { fetch } from "nitro"; export default defineHandler(() => fetch("/hello")); ``` :: When you need one route to call another, use Nitro's `fetch` function instead of the global fetch. It makes internal requests that stay in-process, avoiding network round-trips. The request never leaves the server. ## Main Route ```ts [routes/index.ts] import { defineHandler } from "nitro"; import { fetch } from "nitro"; export default defineHandler(() => fetch("/hello")); ``` The index route imports `fetch` from `nitro` (not the global fetch) and calls the `/hello` route. This request is handled internally without going through the network stack. ## Internal API Route ```ts [routes/hello.ts] import { defineHandler } from "nitro"; export default defineHandler(() => "Hello!"); ``` A simple route that returns "Hello!". When the index route calls `fetch("/hello")`, this handler runs and its response is returned directly. ## Learn More - [Routing](/docs/routing) ================================================ FILE: docs/4.examples/shiki.md ================================================ --- category: integrations icon: i-lucide-highlighter --- # Shiki > Server-side syntax highlighting in Nitro with Shiki. ::code-tree{defaultValue="api/highlight.ts" expandAll} ```html [index.html] Hello World Snippet
JavaScript
{{{ hl(`console.log("💚 Simple is beautiful!");`) }}}
``` ```ts [nitro.config.ts] import { defineConfig } from "nitro"; export default defineConfig({ serverDir: "./", }); ``` ```json [package.json] { "type": "module", "scripts": { "dev": "vite dev", "build": "vite build" }, "devDependencies": { "nitro": "latest", "shiki": "^3.23.0" } } ``` ```css [styles.css] html, body { height: 100%; margin: 0; } body { display: flex; align-items: center; justify-content: center; background: #f6f8fa; font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif; } .card { text-align: left; background: #0b1220; color: #e6edf3; padding: 1rem; border-radius: 8px; box-shadow: 0 8px 24px rgba(2, 6, 23, 0.2); max-width: 90%; width: 520px; } .label { font-size: 12px; color: #9aa7b2; margin-bottom: 8px; } pre { margin: 0; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, "Courier New", monospace; font-size: 14px; background: transparent; white-space: pre; overflow: auto; } ``` ```json [tsconfig.json] { "extends": "nitro/tsconfig" } ``` ```ts [vite.config.ts] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()], }); ``` ```ts [api/highlight.ts] import { createHighlighterCore } from "shiki/core"; import { createOnigurumaEngine } from "shiki/engine/oniguruma"; const highlighter = await createHighlighterCore({ engine: createOnigurumaEngine(import("shiki/wasm")), themes: [await import("shiki/themes/vitesse-dark.mjs")], langs: [await import("shiki/langs/ts.mjs")], }); export default async ({ req }: { req: Request }) => { const code = await req.text(); const html = await highlighter.codeToHtml(code, { lang: "ts", theme: "vitesse-dark", }); return new Response(html, { headers: { "Content-Type": "text/html; charset=utf-8" }, }); }; ``` :: Use Shiki for syntax highlighting with TextMate grammars. This example highlights code on the server using Nitro's server scripts feature, which runs JavaScript inside HTML files before sending the response. ## API Route ```ts [api/highlight.ts] import { createHighlighterCore } from "shiki/core"; import { createOnigurumaEngine } from "shiki/engine/oniguruma"; const highlighter = await createHighlighterCore({ engine: createOnigurumaEngine(import("shiki/wasm")), themes: [await import("shiki/themes/vitesse-dark.mjs")], langs: [await import("shiki/langs/ts.mjs")], }); export default async ({ req }: { req: Request }) => { const code = await req.text(); const html = await highlighter.codeToHtml(code, { lang: "ts", theme: "vitesse-dark", }); return new Response(html, { headers: { "Content-Type": "text/html; charset=utf-8" }, }); }; ``` Create a Shiki highlighter with the Vitesse Dark theme and TypeScript language support. When the API receives a POST request, it reads the code from the request body and returns highlighted HTML. ## Server-Side Rendering ```html [index.html] Hello World Snippet
JavaScript
{{{ hl(`console.log("💚 Simple is beautiful!");`) }}}
``` The ` rscStream ); // Browser root component to (re-)render RSC payload as state function BrowserRoot() { const [payload, setPayload_] = React.useState(initialPayload); React.useEffect(() => { setPayload = (v) => React.startTransition(() => setPayload_(v)); }, [setPayload_]); // Re-fetch/render on client side navigation React.useEffect(() => { return listenNavigation(() => fetchRscPayload()); }, []); return payload.root; } // Re-fetch RSC and trigger re-rendering async function fetchRscPayload() { const renderRequest = createRscRenderRequest(globalThis.location.href); const payload = await createFromFetch(fetch(renderRequest)); setPayload(payload); } // Register a handler which will be internally called by React // on server function request after hydration. setServerCallback(async (id, args) => { const temporaryReferences = createTemporaryReferenceSet(); const renderRequest = createRscRenderRequest(globalThis.location.href, { id, body: await encodeReply(args, { temporaryReferences }), }); const payload = await createFromFetch(fetch(renderRequest), { temporaryReferences, }); setPayload(payload); const { ok, data } = payload.returnValue!; if (!ok) throw data; return data; }); // Hydration const browserRoot = ( ); if ("__NO_HYDRATE" in globalThis) { createRoot(document).render(browserRoot); } else { hydrateRoot(document, browserRoot, { formState: initialPayload.formState, }); } // Implement server HMR by triggering re-fetch/render of RSC upon server code change if (import.meta.hot) { import.meta.hot.on("rsc:update", () => { fetchRscPayload(); }); } } // A little helper to setup events interception for client side navigation function listenNavigation(onNavigation: () => void) { globalThis.addEventListener("popstate", onNavigation); const oldPushState = globalThis.history.pushState; globalThis.history.pushState = function (...args) { const res = oldPushState.apply(this, args); onNavigation(); return res; }; const oldReplaceState = globalThis.history.replaceState; globalThis.history.replaceState = function (...args) { const res = oldReplaceState.apply(this, args); onNavigation(); return res; }; function onClick(e: MouseEvent) { const link = (e.target as Element).closest("a"); if ( link && link instanceof HTMLAnchorElement && link.href && (!link.target || link.target === "_self") && link.origin === location.origin && !link.hasAttribute("download") && e.button === 0 && // left clicks only !e.metaKey && // open in new tab (mac) !e.ctrlKey && // open in new tab (windows) !e.altKey && // download !e.shiftKey && !e.defaultPrevented ) { e.preventDefault(); history.pushState(null, "", link.href); } } document.addEventListener("click", onClick); return () => { document.removeEventListener("click", onClick); globalThis.removeEventListener("popstate", onNavigation); globalThis.history.pushState = oldPushState; globalThis.history.replaceState = oldReplaceState; }; } // eslint-disable-next-line unicorn/prefer-top-level-await main(); ``` ```tsx [app/framework/entry.rsc.tsx] import { renderToReadableStream, createTemporaryReferenceSet, decodeReply, loadServerAction, decodeAction, decodeFormState, } from "@vitejs/plugin-rsc/rsc"; import type { ReactFormState } from "react-dom/client"; import { Root } from "../root.tsx"; import { parseRenderRequest } from "./request.tsx"; // The schema of payload which is serialized into RSC stream on rsc environment // and deserialized on ssr/client environments. export type RscPayload = { // this demo renders/serializes/deserializes entire root html element // but this mechanism can be changed to render/fetch different parts of components // based on your own route conventions. root: React.ReactNode; // Server action return value of non-progressive enhancement case returnValue?: { ok: boolean; data: unknown }; // Server action form state (e.g. useActionState) of progressive enhancement case formState?: ReactFormState; }; // The plugin by default assumes `rsc` entry having default export of request handler. // however, how server entries are executed can be customized by registering own server handler. export default async function handler(request: Request): Promise { // Differentiate RSC, SSR, action, etc. const renderRequest = parseRenderRequest(request); request = renderRequest.request; // Handle server function request let returnValue: RscPayload["returnValue"] | undefined; let formState: ReactFormState | undefined; let temporaryReferences: unknown | undefined; let actionStatus: number | undefined; if (renderRequest.isAction === true) { if (renderRequest.actionId) { // Action is called via `ReactClient.setServerCallback`. const contentType = request.headers.get("content-type"); const body = contentType?.startsWith("multipart/form-data") ? await request.formData() : await request.text(); temporaryReferences = createTemporaryReferenceSet(); const args = await decodeReply(body, { temporaryReferences }); const action = await loadServerAction(renderRequest.actionId); try { // eslint-disable-next-line prefer-spread const data = await action.apply(null, args); returnValue = { ok: true, data }; } catch (error_) { returnValue = { ok: false, data: error_ }; actionStatus = 500; } } else { // Otherwise server function is called via `
` // before hydration (e.g. when JavaScript is disabled). // aka progressive enhancement. const formData = await request.formData(); const decodedAction = await decodeAction(formData); try { const result = await decodedAction(); formState = await decodeFormState(result, formData); } catch { // there's no single general obvious way to surface this error, // so explicitly return classic 500 response. return new Response("Internal Server Error: server action failed", { status: 500, }); } } } // Serialization from React VDOM tree to RSC stream. // We render RSC stream after handling server function request // so that new render reflects updated state from server function call // to achieve single round trip to mutate and fetch from server. const rscPayload: RscPayload = { root: , formState, returnValue, }; const rscOptions = { temporaryReferences }; const rscStream = renderToReadableStream(rscPayload, rscOptions); // Respond RSC stream without HTML rendering as decided by `RenderRequest` if (renderRequest.isRsc) { return new Response(rscStream, { status: actionStatus, headers: { "content-type": "text/x-component;charset=utf-8", }, }); } // Delegate to SSR environment for HTML rendering. // The plugin provides `loadModule` helper to allow loading SSR environment entry module // in RSC environment. however this can be customized by implementing own runtime communication // e.g. `@cloudflare/vite-plugin`'s service binding. const ssrEntryModule = await import.meta.viteRsc.loadModule( "ssr", "index" ); const ssrResult = await ssrEntryModule.renderHTML(rscStream, { formState, // Allow quick simulation of JavaScript disabled browser debugNoJS: renderRequest.url.searchParams.has("__nojs"), }); // Respond HTML return new Response(ssrResult.stream, { status: ssrResult.status, headers: { "Content-Type": "text/html", }, }); } if (import.meta.hot) { import.meta.hot.accept(); } ``` ```tsx [app/framework/entry.ssr.tsx] import { createFromReadableStream } from "@vitejs/plugin-rsc/ssr"; import React from "react"; import type { ReactFormState } from "react-dom/client"; import { renderToReadableStream } from "react-dom/server.edge"; import { injectRSCPayload } from "rsc-html-stream/server"; import type { RscPayload } from "./entry.rsc"; export default { fetch: async (request: Request) => { const rscEntryModule = await import.meta.viteRsc.loadModule( "rsc", "index" ); return rscEntryModule.default(request); }, }; export async function renderHTML( rscStream: ReadableStream, options: { formState?: ReactFormState; nonce?: string; debugNoJS?: boolean; } ): Promise<{ stream: ReadableStream; status?: number }> { // Duplicate one RSC stream into two. // - one for SSR (ReactClient.createFromReadableStream below) // - another for browser hydration payload by injecting . const [rscStream1, rscStream2] = rscStream.tee(); // Deserialize RSC stream back to React VDOM let payload: Promise | undefined; function SsrRoot() { // Deserialization needs to be kicked off inside ReactDOMServer context // for ReactDOMServer preinit/preloading to work payload ??= createFromReadableStream(rscStream1); return React.use(payload).root; } // Render HTML (traditional SSR) const bootstrapScriptContent = await import.meta.viteRsc.loadBootstrapScriptContent("index"); let htmlStream: ReadableStream; let status: number | undefined; try { htmlStream = await renderToReadableStream(, { bootstrapScriptContent: options?.debugNoJS ? undefined : bootstrapScriptContent, nonce: options?.nonce, formState: options?.formState, }); } catch { // fallback to render an empty shell and run pure CSR on browser, // which can replay server component error and trigger error boundary. status = 500; htmlStream = await renderToReadableStream( , { bootstrapScriptContent: `self.__NO_HYDRATE=1;` + (options?.debugNoJS ? "" : bootstrapScriptContent), nonce: options?.nonce, } ); } let responseStream: ReadableStream = htmlStream; if (!options?.debugNoJS) { // Initial RSC stream is injected in HTML stream as // using utility made by devongovett https://github.com/devongovett/rsc-html-stream responseStream = responseStream.pipeThrough( injectRSCPayload(rscStream2, { nonce: options?.nonce, }) ); } return { stream: responseStream, status }; } ``` ```tsx [app/framework/error-boundary.tsx] "use client"; import React from "react"; // Minimal ErrorBoundary example to handle errors globally on browser export function GlobalErrorBoundary(props: { children?: React.ReactNode }) { return {props.children}; } // https://github.com/vercel/next.js/blob/33f8428f7066bf8b2ec61f025427ceb2a54c4bdf/packages/next/src/client/components/error-boundary.tsx // https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary class ErrorBoundary extends React.Component<{ children?: React.ReactNode; errorComponent: React.FC<{ error: Error; reset: () => void; }>; }> { override state: { error?: Error } = {}; static getDerivedStateFromError(error: Error) { return { error }; } reset = () => { this.setState({ error: null }); }; override render() { const error = this.state.error; if (error) { return ; } return this.props.children; } } // https://github.com/vercel/next.js/blob/677c9b372faef680d17e9ba224743f44e1107661/packages/next/src/build/webpack/loaders/next-app-loader.ts#L73 // https://github.com/vercel/next.js/blob/677c9b372faef680d17e9ba224743f44e1107661/packages/next/src/client/components/error-boundary.tsx#L145 function DefaultGlobalErrorPage(props: { error: Error; reset: () => void }) { return ( Unexpected Error

Caught an unexpected error

          Error:{" "}
          {import.meta.env.DEV && "message" in props.error ? props.error.message : "(Unknown)"}
        
); } ``` ```tsx [app/framework/request.tsx] // Framework conventions (arbitrary choices for this demo): // - Use `_.rsc` URL suffix to differentiate RSC requests from SSR requests // - Use `x-rsc-action` header to pass server action ID const URL_POSTFIX = "_.rsc"; const HEADER_ACTION_ID = "x-rsc-action"; // Parsed request information used to route between RSC/SSR rendering and action handling. // Created by parseRenderRequest() from incoming HTTP requests. type RenderRequest = { isRsc: boolean; // true if request should return RSC payload (via _.rsc suffix) isAction: boolean; // true if this is a server action call (POST request) actionId?: string; // server action ID from x-rsc-action header request: Request; // normalized Request with _.rsc suffix removed from URL url: URL; // normalized URL with _.rsc suffix removed }; export function createRscRenderRequest( urlString: string, action?: { id: string; body: BodyInit } ): Request { const url = new URL(urlString); url.pathname += URL_POSTFIX; const headers = new Headers(); if (action) { headers.set(HEADER_ACTION_ID, action.id); } return new Request(url.toString(), { method: action ? "POST" : "GET", headers, body: action?.body, }); } export function parseRenderRequest(request: Request): RenderRequest { const url = new URL(request.url); const isAction = request.method === "POST"; if (url.pathname.endsWith(URL_POSTFIX)) { url.pathname = url.pathname.slice(0, -URL_POSTFIX.length); const actionId = request.headers.get(HEADER_ACTION_ID) || undefined; if (request.method === "POST" && !actionId) { throw new Error("Missing action id header for RSC action request"); } return { isRsc: true, isAction, actionId, request: new Request(url, request), url, }; } else { return { isRsc: false, isAction, request, url, }; } } ``` :: This example demonstrates React Server Components (RSC) using Vite's experimental RSC plugin with Nitro. It includes server components, client components, server actions, and streaming SSR. ## Overview 1. **SSR Entry** handles incoming requests and renders React components to HTML 2. **Root Component** defines the page structure as a server component 3. **Client Components** use the `"use client"` directive for interactive parts ## 1. SSR Entry ```tsx [app/framework/entry.ssr.tsx] import { createFromReadableStream } from "@vitejs/plugin-rsc/ssr"; import React from "react"; import type { ReactFormState } from "react-dom/client"; import { renderToReadableStream } from "react-dom/server.edge"; import { injectRSCPayload } from "rsc-html-stream/server"; import type { RscPayload } from "./entry.rsc"; export default { fetch: async (request: Request) => { const rscEntryModule = await import.meta.viteRsc.loadModule( "rsc", "index" ); return rscEntryModule.default(request); }, }; export async function renderHTML( rscStream: ReadableStream, options: { formState?: ReactFormState; nonce?: string; debugNoJS?: boolean; } ): Promise<{ stream: ReadableStream; status?: number }> { // Duplicate one RSC stream into two. // - one for SSR (ReactClient.createFromReadableStream below) // - another for browser hydration payload by injecting . const [rscStream1, rscStream2] = rscStream.tee(); // Deserialize RSC stream back to React VDOM let payload: Promise | undefined; function SsrRoot() { // Deserialization needs to be kicked off inside ReactDOMServer context // for ReactDOMServer preinit/preloading to work payload ??= createFromReadableStream(rscStream1); return React.use(payload).root; } // Render HTML (traditional SSR) const bootstrapScriptContent = await import.meta.viteRsc.loadBootstrapScriptContent("index"); let htmlStream: ReadableStream; let status: number | undefined; try { htmlStream = await renderToReadableStream(, { bootstrapScriptContent: options?.debugNoJS ? undefined : bootstrapScriptContent, nonce: options?.nonce, formState: options?.formState, }); } catch { // fallback to render an empty shell and run pure CSR on browser, // which can replay server component error and trigger error boundary. status = 500; htmlStream = await renderToReadableStream( , { bootstrapScriptContent: `self.__NO_HYDRATE=1;` + (options?.debugNoJS ? "" : bootstrapScriptContent), nonce: options?.nonce, } ); } let responseStream: ReadableStream = htmlStream; if (!options?.debugNoJS) { // Initial RSC stream is injected in HTML stream as // using utility made by devongovett https://github.com/devongovett/rsc-html-stream responseStream = responseStream.pipeThrough( injectRSCPayload(rscStream2, { nonce: options?.nonce, }) ); } return { stream: responseStream, status }; } ``` The SSR entry handles the rendering pipeline. It loads the RSC entry module, duplicates the RSC stream (one for SSR, one for hydration), deserializes the stream back to React VDOM, and renders it to HTML. The RSC payload is injected into the HTML for client hydration. ## 2. Root Server Component ```tsx [app/root.tsx] import "./index.css"; // css import is automatically injected in exported server components import viteLogo from "./assets/vite.svg"; import { getServerCounter, updateServerCounter } from "./action.tsx"; import reactLogo from "./assets/react.svg"; import nitroLogo from "./assets/nitro.svg"; import { ClientCounter } from "./client.tsx"; export function Root(props: { url: URL }) { return ( {/* eslint-disable-next-line unicorn/text-encoding-identifier-case */} Nitro + Vite + RSC ); } function App(props: { url: URL }) { return (

Vite + RSC + Nitro

Request URL: {props.url?.href}
  • Edit src/client.tsx to test client HMR.
  • Edit src/root.tsx to test server HMR.
  • Visit{" "} _.rsc {" "} to view RSC stream payload.
  • Visit{" "} ?__nojs {" "} to test server action without js enabled.
); } ``` Server components run only on the server. They can import CSS directly, use server-side data, and call server actions. The `ClientCounter` component is imported but runs on the client because it has the `"use client"` directive. ## 3. Client Component ```tsx [app/client.tsx] "use client"; import React from "react"; export function ClientCounter() { const [count, setCount] = React.useState(0); return ; } ``` The `"use client"` directive marks this as a client component. It hydrates on the browser and handles interactive state. Server components can import and render client components, but client components cannot import server components. ## Learn More - [React Server Components](https://react.dev/reference/rsc/server-components) ================================================ FILE: docs/4.examples/vite-ssr-html.md ================================================ --- category: server side rendering icon: i-logos-html-5 --- # Vite SSR HTML > Server-side rendering with vanilla HTML, Vite, and Nitro. ::code-tree{defaultValue="app/entry-server.ts" expandAll} ```html [index.html] Nitro Quotes
Powered by Vite and Nitro v3.
``` ```json [package.json] { "type": "module", "scripts": { "build": "vite build", "dev": "vite dev", "preview": "vite preview" }, "devDependencies": { "@tailwindcss/vite": "^4.2.2", "nitro": "latest", "tailwindcss": "^4.2.2", "vite": "latest" } } ``` ```json [tsconfig.json] { "extends": "nitro/tsconfig" } ``` ```ts [vite.config.ts] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; import tailwindcss from "@tailwindcss/vite"; export default defineConfig({ plugins: [ nitro({ serverDir: "./", }), tailwindcss(), ], }); ``` ```ts [app/entry-server.ts] import { fetch } from "nitro"; export default { async fetch() { const quote = (await fetch("/quote").then((res) => res.json())) as { text: string; }; return tokenizedStream(quote.text, 50); }, }; function tokenizedStream(text: string, delay: number): ReadableStream { const tokens = text.split(" "); return new ReadableStream({ start(controller) { let index = 0; function push() { if (index < tokens.length) { const word = tokens[index++] + (index < tokens.length ? " " : ""); controller.enqueue(new TextEncoder().encode(word)); setTimeout(push, delay); } else { controller.close(); } } push(); }, }); } ``` ```ts [routes/quote.ts] const QUOTES_URL = "https://github.com/JamesFT/Database-Quotes-JSON/raw/refs/heads/master/quotes.json"; let _quotes: Promise | undefined; function getQuotes() { return (_quotes ??= fetch(QUOTES_URL).then((res) => res.json())) as Promise< { quoteText: string; quoteAuthor: string }[] >; } export default async function quotesHandler() { const quotes = await getQuotes(); const randomQuote = quotes[Math.floor(Math.random() * quotes.length)]; return Response.json({ text: randomQuote.quoteText, author: randomQuote.quoteAuthor, }); } ``` :: This example renders an HTML template with server-side data and streams the response word by word. It demonstrates how to use Nitro's Vite SSR integration without a framework. ## Overview 1. **Add the Nitro Vite plugin** to enable SSR 2. **Create an HTML template** with a `` comment where server content goes 3. **Create a server entry** that fetches data and returns a stream 4. **Add API routes** for server-side data ## How It Works The `index.html` file contains an `` comment that marks where server-rendered content will be inserted. Nitro replaces this comment with the output from your server entry. The server entry exports an object with a `fetch` method. It calls the `/quote` API route using Nitro's internal fetch, then returns a `ReadableStream` that emits the quote text word by word with a 50ms delay between each word. The quote route fetches a JSON file of quotes from GitHub, caches the result, and returns a random quote. The server entry calls this route to get content for the page. ## Learn More - [Renderer](/docs/renderer) - [Server Entry](/docs/server-entry) ================================================ FILE: docs/4.examples/vite-ssr-preact.md ================================================ --- category: server side rendering icon: i-logos-preact --- # SSR with Preact > Server-side rendering with Preact in Nitro using Vite. ::code-tree{defaultValue="src/entry-server.tsx" expandAll} ```json [package.json] { "type": "module", "scripts": { "build": "vite build", "preview": "vite preview", "dev": "vite dev" }, "devDependencies": { "@preact/preset-vite": "^2.10.4", "@tailwindcss/vite": "^4.2.2", "nitro": "latest", "preact": "^10.29.0", "preact-render-to-string": "^6.6.6", "tailwindcss": "^4.2.2", "vite": "latest" } } ``` ```json [tsconfig.json] { "extends": "nitro/tsconfig", "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "preact" } } ``` ```js [vite.config.mjs] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; import preact from "@preact/preset-vite"; export default defineConfig({ plugins: [nitro(), preact()], environments: { client: { build: { rollupOptions: { input: "./src/entry-client.tsx", }, }, }, }, }); ``` ```tsx [src/app.tsx] import { useState } from "preact/hooks"; export function App() { const [count, setCount] = useState(0); return ; } ``` ```tsx [src/entry-client.tsx] import { hydrate } from "preact"; import { App } from "./app.tsx"; function main() { hydrate(, document.querySelector("#app")!); } main(); ``` ```tsx [src/entry-server.tsx] import "./styles.css"; import { renderToReadableStream } from "preact-render-to-string/stream"; import { App } from "./app.jsx"; import clientAssets from "./entry-client?assets=client"; import serverAssets from "./entry-server?assets=ssr"; export default { async fetch(request: Request) { const url = new URL(request.url); const htmlStream = renderToReadableStream(); return new Response(htmlStream, { headers: { "Content-Type": "text/html;charset=utf-8" }, }); }, }; function Root(props: { url: URL }) { const assets = clientAssets.merge(serverAssets); return ( {assets.css.map((attr: any) => ( ))} {assets.js.map((attr: any) => ( ))} ``` ```json [package.json] { "type": "module", "scripts": { "build": "vite build", "dev": "vite dev", "preview": "vite preview" }, "devDependencies": { "@tanstack/react-router": "^1.168.1", "@tanstack/react-router-devtools": "^1.166.10", "@tanstack/router-plugin": "^1.167.1", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^5.2.0", "nitro": "latest", "react": "^19.2.4", "react-dom": "^19.2.4", "vite": "latest" } } ``` ```json [tsconfig.json] { "extends": "nitro/tsconfig", "compilerOptions": { "baseUrl": ".", "jsx": "react-jsx", "paths": { "@/*": ["sec/*"] } } } ``` ```js [vite.config.mjs] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; import react from "@vitejs/plugin-react"; import { tanstackRouter } from "@tanstack/router-plugin/vite"; export default defineConfig({ plugins: [tanstackRouter({ target: "react", autoCodeSplitting: true }), react(), nitro()], }); ``` ```tsx [src/main.tsx] import { StrictMode } from "react"; import ReactDOM from "react-dom/client"; import { RouterProvider, createRouter } from "@tanstack/react-router"; // Import the generated route tree import { routeTree } from "./routeTree.gen.ts"; // Create a new router instance const router = createRouter({ routeTree }); // Register the router instance for type safety declare module "@tanstack/react-router" { interface Register { router: typeof router; } } // Render the app const rootElement = document.querySelector("#root")!; if (!rootElement.innerHTML) { const root = ReactDOM.createRoot(rootElement); root.render( ); } ``` ```ts [src/routeTree.gen.ts] /* eslint-disable */ // @ts-nocheck // noinspection JSUnusedGlobalSymbols // This file was automatically generated by TanStack Router. // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. import { Route as rootRouteImport } from './routes/__root' import { Route as IndexRouteImport } from './routes/index' const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute } export interface FileRoutesByTo { '/': typeof IndexRoute } export interface FileRoutesById { __root__: typeof rootRouteImport '/': typeof IndexRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath fullPaths: '/' fileRoutesByTo: FileRoutesByTo to: '/' id: '__root__' | '/' fileRoutesById: FileRoutesById } export interface RootRouteChildren { IndexRoute: typeof IndexRoute } declare module '@tanstack/react-router' { interface FileRoutesByPath { '/': { id: '/' path: '/' fullPath: '/' preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } } } const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() ``` ```css [src/assets/main.css] :root { font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; line-height: 1.5; font-weight: 400; color-scheme: light dark; color: rgba(255, 255, 255, 0.87); background-color: #242424; font-synthesis: none; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } a { font-weight: 500; color: #ff2056; text-decoration: inherit; } a:hover { color: #ff637e; } body { margin: 0; display: flex; flex-direction: column; place-items: center; justify-content: center; min-width: 320px; min-height: 100vh; } h1 { font-size: 3.2em; line-height: 1.1; } #app { max-width: 1280px; margin: 0 auto; padding: 2rem; text-align: center; } .logo { height: 6em; padding: 1.5em; will-change: filter; transition: filter 300ms; transition: transform 300ms; } .logo:hover { transform: scale(1.1); } .card { padding: 2em; } .read-the-docs { color: #888; } button { border-radius: 8px; border: 1px solid transparent; padding: 0.6em 1.2em; font-size: 1em; font-weight: 500; font-family: inherit; background-color: #1a1a1a; cursor: pointer; transition: border-color 0.25s; } button:hover { border-color: #646cff; } button:focus, button:focus-visible { outline: 4px auto -webkit-focus-ring-color; } @media (prefers-color-scheme: light) { :root { color: #213547; background-color: #ffffff; } a:hover { color: #747bff; } button { background-color: #f9f9f9; } } ``` ```tsx [src/routes/__root.tsx] import { createRootRoute, Link, Outlet } from "@tanstack/react-router"; import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"; const RootLayout = () => ( <>
Home

); export const Route = createRootRoute({ component: RootLayout }); ``` ```tsx [src/routes/index.tsx] import { createFileRoute } from "@tanstack/react-router"; export const Route = createFileRoute("/")({ loader: async () => { const r = await fetch("/api/hello"); return r.json(); }, component: Index, }); function Index() { const r = Route.useLoaderData(); return (

{JSON.stringify(r)}

); } ``` :: Set up TanStack Router with React, Vite, and Nitro. This setup provides file-based routing with type-safe navigation and automatic code splitting. ## Overview 1. Add the Nitro Vite plugin to your Vite config 2. Create an HTML template with your app entry 3. Create a main entry that initializes the router 4. Define routes using file-based routing ## 1. Configure Vite Add the Nitro, React, and TanStack Router plugins to your Vite config: ```js [vite.config.mjs] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; import react from "@vitejs/plugin-react"; import { tanstackRouter } from "@tanstack/router-plugin/vite"; export default defineConfig({ plugins: [tanstackRouter({ target: "react", autoCodeSplitting: true }), react(), nitro()], }); ``` The `tanstackRouter` plugin generates a route tree from your `routes/` directory structure. Enable `autoCodeSplitting` to automatically split routes into separate chunks. Place the TanStack Router plugin before the React plugin in the array. ## 2. Create the HTML Template Create an HTML file that serves as your app shell: ```html [index.html] Nitro + TanStack Router + React
``` ## 3. Create the App Entry Create the main entry that initializes TanStack Router: ```tsx [src/main.tsx] import { StrictMode } from "react"; import ReactDOM from "react-dom/client"; import { RouterProvider, createRouter } from "@tanstack/react-router"; // Import the generated route tree import { routeTree } from "./routeTree.gen.ts"; // Create a new router instance const router = createRouter({ routeTree }); // Register the router instance for type safety declare module "@tanstack/react-router" { interface Register { router: typeof router; } } // Render the app const rootElement = document.querySelector("#root")!; if (!rootElement.innerHTML) { const root = ReactDOM.createRoot(rootElement); root.render( ); } ``` The `routeTree.gen.ts` file is auto-generated from your `routes/` directory structure. The `Register` interface declaration provides full type inference for route paths and params. The `!rootElement.innerHTML` check prevents re-rendering during hot module replacement. ## 4. Create the Root Route The root route (`__root.tsx`) defines your app's layout: ```tsx [src/routes/__root.tsx] import { createRootRoute, Link, Outlet } from "@tanstack/react-router"; import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"; const RootLayout = () => ( <>
Home

); export const Route = createRootRoute({ component: RootLayout }); ``` Use `Link` for type-safe navigation with active state styling. The `Outlet` component renders child routes. Include `TanStackRouterDevtools` for development tools (automatically removed in production). ## 5. Create Page Routes Page routes use `createFileRoute` and can include loaders: ```tsx [src/routes/index.tsx] import { createFileRoute } from "@tanstack/react-router"; export const Route = createFileRoute("/")({ loader: async () => { const r = await fetch("/api/hello"); return r.json(); }, component: Index, }); function Index() { const r = Route.useLoaderData(); return (

{JSON.stringify(r)}

); } ``` Fetch data before rendering with the `loader` function—data is available via `Route.useLoaderData()`. File paths determine URL paths: `routes/index.tsx` maps to `/`, `routes/about.tsx` to `/about`, and `routes/users/$id.tsx` to `/users/:id`. ## Learn More - [TanStack Router Documentation](https://tanstack.com/router) - [Renderer](/docs/renderer) ================================================ FILE: docs/4.examples/vite-ssr-tss-react.md ================================================ --- category: server side rendering icon: i-simple-icons-tanstack --- # SSR with TanStack Start > Full-stack React with TanStack Start in Nitro using Vite. ::code-tree{defaultValue="server.ts" expandAll} ```json [package.json] { "type": "module", "scripts": { "build": "vite build", "dev": "vite dev", "start": "node .output/server/index.mjs" }, "dependencies": { "@tanstack/react-router": "^1.168.1", "@tanstack/react-router-devtools": "^1.166.10", "@tanstack/react-start": "^1.167.1", "nitro": "latest", "react": "^19.2.4", "react-dom": "^19.2.4", "tailwind-merge": "^3.5.0", "zod": "^4.3.6" }, "devDependencies": { "@tailwindcss/vite": "^4.2.2", "@types/node": "latest", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^5.2.0", "tailwindcss": "^4.2.2", "typescript": "^5.9.3", "vite": "latest", "vite-tsconfig-paths": "^6.1.1" } } ``` ```ts [server.ts] import handler, { createServerEntry } from "@tanstack/react-start/server-entry"; export default createServerEntry({ fetch(request) { return handler.fetch(request); }, }); ``` ```json [tsconfig.json] { "extends": "nitro/tsconfig", "compilerOptions": { "baseUrl": ".", "jsx": "react-jsx", "paths": { "~/*": ["./src/*"] } } } ``` ```js [vite.config.mjs] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; import { tanstackStart } from "@tanstack/react-start/plugin/vite"; import viteReact from "@vitejs/plugin-react"; import viteTsConfigPaths from "vite-tsconfig-paths"; import tailwindcss from "@tailwindcss/vite"; export default defineConfig({ plugins: [ viteTsConfigPaths({ projects: ["./tsconfig.json"] }), tanstackStart(), viteReact(), tailwindcss(), nitro(), ], environments: { ssr: { build: { rollupOptions: { input: "./server.ts" } } }, }, }); ``` ```tsx [src/router.tsx] import { createRouter } from "@tanstack/react-router"; import { routeTree } from "./routeTree.gen.ts"; export function getRouter() { const router = createRouter({ routeTree, defaultPreload: "intent", defaultErrorComponent: () =>
Internal Server Error
, defaultNotFoundComponent: () =>
Not Found
, scrollRestoration: true, }); return router; } ``` ```ts [src/routeTree.gen.ts] /* eslint-disable */ // @ts-nocheck // noinspection JSUnusedGlobalSymbols // This file was automatically generated by TanStack Router. // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. import { Route as rootRouteImport } from './routes/__root' import { Route as IndexRouteImport } from './routes/index' import { Route as ApiTestRouteImport } from './routes/api/test' const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) const ApiTestRoute = ApiTestRouteImport.update({ id: '/api/test', path: '/api/test', getParentRoute: () => rootRouteImport, } as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute '/api/test': typeof ApiTestRoute } export interface FileRoutesByTo { '/': typeof IndexRoute '/api/test': typeof ApiTestRoute } export interface FileRoutesById { __root__: typeof rootRouteImport '/': typeof IndexRoute '/api/test': typeof ApiTestRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath fullPaths: '/' | '/api/test' fileRoutesByTo: FileRoutesByTo to: '/' | '/api/test' id: '__root__' | '/' | '/api/test' fileRoutesById: FileRoutesById } export interface RootRouteChildren { IndexRoute: typeof IndexRoute ApiTestRoute: typeof ApiTestRoute } declare module '@tanstack/react-router' { interface FileRoutesByPath { '/': { id: '/' path: '/' fullPath: '/' preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/api/test': { id: '/api/test' path: '/api/test' fullPath: '/api/test' preLoaderRoute: typeof ApiTestRouteImport parentRoute: typeof rootRouteImport } } } const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, ApiTestRoute: ApiTestRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() import type { getRouter } from './router.tsx' import type { createStart } from '@tanstack/react-start' declare module '@tanstack/react-start' { interface Register { ssr: true router: Awaited> } } ``` ```tsx [src/routes/__root.tsx] /// import { HeadContent, Link, Scripts, createRootRoute } from "@tanstack/react-router"; import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"; import * as React from "react"; import appCss from "~/styles/app.css?url"; export const Route = createRootRoute({ head: () => ({ meta: [ { charSet: "utf8" }, { name: "viewport", content: "width=device-width, initial-scale=1" }, ], links: [{ rel: "stylesheet", href: appCss }], scripts: [{ src: "/customScript.js", type: "text/javascript" }], }), errorComponent: () =>

500: Internal Server Error

, notFoundComponent: () =>

404: Page Not Found

, shellComponent: RootDocument, }); function RootDocument({ children }: { children: React.ReactNode }) { return (
Home {" "} 404

{children} ); } ``` ```tsx [src/routes/index.tsx] import { createFileRoute } from "@tanstack/react-router"; export const Route = createFileRoute("/")({ component: Home }); function Home() { return (

Welcome Home!

/api/test
); } ``` ```css [src/styles/app.css] @import "tailwindcss"; @layer base { *, ::after, ::before, ::backdrop, ::file-selector-button { border-color: var(--color-gray-200, currentcolor); } } @layer base { html { color-scheme: light dark; } * { @apply border-gray-200 dark:border-gray-800; } html, body { @apply text-gray-900 bg-gray-50 dark:bg-gray-950 dark:text-gray-200; } .using-mouse * { outline: none !important; } } ``` :: Set up TanStack Start with Nitro for a full-stack React framework experience with server-side rendering, file-based routing, and integrated API routes. ## Overview 1. Add the Nitro Vite plugin to your Vite config 2. Create a server entry using TanStack Start's server handler 3. Configure the router with default components 4. Define routes and API endpoints using file-based routing ## 1. Configure Vite Add the Nitro, React, TanStack Start, and Tailwind plugins to your Vite config: ```js [vite.config.mjs] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; import { tanstackStart } from "@tanstack/react-start/plugin/vite"; import viteReact from "@vitejs/plugin-react"; import viteTsConfigPaths from "vite-tsconfig-paths"; import tailwindcss from "@tailwindcss/vite"; export default defineConfig({ plugins: [ viteTsConfigPaths({ projects: ["./tsconfig.json"] }), tanstackStart(), viteReact(), tailwindcss(), nitro(), ], environments: { ssr: { build: { rollupOptions: { input: "./server.ts" } } }, }, }); ``` The `tanstackStart()` plugin provides full SSR integration with automatic client entry handling. Use `viteTsConfigPaths()` to enable path aliases like `~/` from tsconfig. The `environments.ssr` option points to the server entry file. ## 2. Create the Server Entry Create a server entry that uses TanStack Start's handler: ```ts [server.ts] import handler, { createServerEntry } from "@tanstack/react-start/server-entry"; export default createServerEntry({ fetch(request) { return handler.fetch(request); }, }); ``` TanStack Start handles SSR automatically. The `createServerEntry` wrapper integrates with Nitro's server entry format, and the `handler.fetch` processes all incoming requests. ## 3. Configure the Router Create a router factory function with default error and not-found components: ```tsx [src/router.tsx] import { createRouter } from "@tanstack/react-router"; import { routeTree } from "./routeTree.gen.ts"; export function getRouter() { const router = createRouter({ routeTree, defaultPreload: "intent", defaultErrorComponent: () =>
Internal Server Error
, defaultNotFoundComponent: () =>
Not Found
, scrollRestoration: true, }); return router; } ``` The router factory configures preloading behavior, scroll restoration, and default error/not-found components. ## 4. Create the Root Route The root route defines your HTML shell with head management and scripts: ```tsx [src/routes/__root.tsx] /// import { HeadContent, Link, Scripts, createRootRoute } from "@tanstack/react-router"; import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"; import * as React from "react"; import appCss from "~/styles/app.css?url"; export const Route = createRootRoute({ head: () => ({ meta: [ { charSet: "utf8" }, { name: "viewport", content: "width=device-width, initial-scale=1" }, ], links: [{ rel: "stylesheet", href: appCss }], scripts: [{ src: "/customScript.js", type: "text/javascript" }], }), errorComponent: () =>

500: Internal Server Error

, notFoundComponent: () =>

404: Page Not Found

, shellComponent: RootDocument, }); function RootDocument({ children }: { children: React.ReactNode }) { return (
Home {" "} 404

{children} ); } ``` Define meta tags, stylesheets, and scripts in the `head()` function. The `shellComponent` provides the HTML document shell that wraps all pages. Use `HeadContent` to render the head configuration and `Scripts` to inject the client-side JavaScript for hydration. ## 5. Create Page Routes Page routes define your application pages: ```tsx [src/routes/index.tsx] import { createFileRoute } from "@tanstack/react-router"; export const Route = createFileRoute("/")({ component: Home }); function Home() { return (

Welcome Home!

/api/test
); } ``` ## API Routes TanStack Start supports API routes alongside page routes. Create files in `src/routes/api/` to define server endpoints that Nitro serves automatically. ## Learn More - [TanStack Start Documentation](https://tanstack.com/start) - [Server Entry](/docs/server-entry) ================================================ FILE: docs/4.examples/vite-ssr-vue-router.md ================================================ --- category: server side rendering icon: i-logos-vue --- # SSR with Vue Router > Server-side rendering with Vue Router in Nitro using Vite. ::code-tree{defaultValue="app/entry-server.ts" expandAll} ```json [package.json] { "type": "module", "scripts": { "build": "vite build", "dev": "vite dev", "preview": "vite preview" }, "devDependencies": { "@vitejs/plugin-vue": "^6.0.5", "nitro": "latest", "unhead": "^2.1.12", "vite": "latest", "vite-plugin-devtools-json": "^1.0.0", "vue": "^3.5.30", "vue-router": "^4.6.4" } } ``` ```json [tsconfig.json] { "extends": "nitro/tsconfig" } ``` ```js [vite.config.mjs] import vue from "@vitejs/plugin-vue"; import { defineConfig } from "vite"; import devtoolsJson from "vite-plugin-devtools-json"; import { nitro } from "nitro/vite"; export default defineConfig((_env) => ({ plugins: [patchVueExclude(vue(), /\?assets/), devtoolsJson(), nitro()], environments: { client: { build: { rollupOptions: { input: "./app/entry-client.ts" } } }, ssr: { build: { rollupOptions: { input: "./app/entry-server.ts" } } }, }, })); // Workaround https://github.com/vitejs/vite-plugin-vue/issues/677 function patchVueExclude(plugin, exclude) { const original = plugin.transform.handler; plugin.transform.handler = function (...args) { if (exclude.test(args[1])) return; return original.call(this, ...args); }; return plugin; } ``` ```vue [app/app.vue] ``` ```ts [app/entry-client.ts] import { createSSRApp } from "vue"; import { RouterView, createRouter, createWebHistory } from "vue-router"; import { routes } from "./routes.ts"; async function main() { const app = createSSRApp(RouterView); const router = createRouter({ history: createWebHistory(), routes }); app.use(router); await router.isReady(); app.mount("#root"); } // eslint-disable-next-line unicorn/prefer-top-level-await main(); ``` ```ts [app/entry-server.ts] import { createSSRApp } from "vue"; import { renderToString } from "vue/server-renderer"; import { RouterView, createMemoryHistory, createRouter } from "vue-router"; import { createHead, transformHtmlTemplate } from "unhead/server"; import { routes } from "./routes.ts"; import clientAssets from "./entry-client.ts?assets=client"; async function handler(request: Request): Promise { const app = createSSRApp(RouterView); const router = createRouter({ history: createMemoryHistory(), routes }); app.use(router); const url = new URL(request.url); const href = url.href.slice(url.origin.length); await router.push(href); await router.isReady(); const assets = clientAssets.merge( ...(await Promise.all( router.currentRoute.value.matched .map((to) => to.meta.assets) .filter(Boolean) .map((fn) => (fn as any)().then((m: any) => m.default)) )) ); const head = createHead(); head.push({ link: [ ...assets.css.map((attrs: any) => ({ rel: "stylesheet", ...attrs })), ...assets.js.map((attrs: any) => ({ rel: "modulepreload", ...attrs })), ], script: [{ type: "module", src: clientAssets.entry }], }); const renderedApp = await renderToString(app); const html = await transformHtmlTemplate(head, htmlTemplate(renderedApp)); return new Response(html, { headers: { "Content-Type": "text/html;charset=utf-8" }, }); } function htmlTemplate(body: string): string { return /* html */ ` Vue Router Custom Framework
${body}
`; } export default { fetch: handler, }; ``` ```ts [app/routes.ts] import type { RouteRecordRaw } from "vue-router"; export const routes: RouteRecordRaw[] = [ { path: "/", name: "app", component: () => import("./app.vue"), meta: { assets: () => import("./app.vue?assets"), }, children: [ { path: "/", name: "home", component: () => import("./pages/index.vue"), meta: { assets: () => import("./pages/index.vue?assets"), }, }, { path: "/about", name: "about", component: () => import("./pages/about.vue"), meta: { assets: () => import("./pages/about.vue?assets"), }, }, { path: "/:catchAll(.*)", name: "not-found", component: () => import("./pages/not-found.vue"), meta: { assets: () => import("./pages/not-found.vue?assets"), }, }, ], }, ]; ``` ```ts [app/shims.d.ts] declare module "*.vue" { import type { DefineComponent } from "vue"; const component: DefineComponent<{}, {}, any>; export default component; } ``` ```css [app/styles.css] * { box-sizing: border-box; } body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: #f5f5f5; color: #333; } main { max-width: 800px; margin: 0 auto; padding: 2rem; } h1 { font-size: 2.5rem; margin-bottom: 0.5rem; } .card { background: white; border-radius: 8px; padding: 2rem; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); margin: 2rem 0; } button { background: rgb(83, 91, 242); color: white; border: none; padding: 0.5rem 1rem; border-radius: 4px; font-size: 1rem; cursor: pointer; } button:hover { background: #535bf2; } .subtitle { color: #666; font-size: 1.1rem; margin-bottom: 2rem; } ``` ```vue [app/pages/about.vue] ``` ```vue [app/pages/index.vue] ``` ```vue [app/pages/not-found.vue] ``` :: Set up server-side rendering (SSR) with Vue, Vue Router, Vite, and Nitro. This setup enables per-route code splitting, head management with unhead, and client hydration. ## Overview 1. Add the Nitro Vite plugin to your Vite config 2. Define routes with lazy-loaded components 3. Create a server entry that renders your app with router support 4. Create a client entry that hydrates and takes over routing 5. Create page components ## 1. Configure Vite Add the Nitro and Vue plugins to your Vite config. Define both `client` and `ssr` environments: ```js [vite.config.mjs] import vue from "@vitejs/plugin-vue"; import { defineConfig } from "vite"; import devtoolsJson from "vite-plugin-devtools-json"; import { nitro } from "nitro/vite"; export default defineConfig((_env) => ({ plugins: [patchVueExclude(vue(), /\?assets/), devtoolsJson(), nitro()], environments: { client: { build: { rollupOptions: { input: "./app/entry-client.ts" } } }, ssr: { build: { rollupOptions: { input: "./app/entry-server.ts" } } }, }, })); // Workaround https://github.com/vitejs/vite-plugin-vue/issues/677 function patchVueExclude(plugin, exclude) { const original = plugin.transform.handler; plugin.transform.handler = function (...args) { if (exclude.test(args[1])) return; return original.call(this, ...args); }; return plugin; } ``` The `patchVueExclude` helper prevents the Vue plugin from processing asset imports (files with `?assets` query parameter). ## 2. Define Routes Create route definitions with lazy-loaded components and asset metadata: ```ts [app/routes.ts] import type { RouteRecordRaw } from "vue-router"; export const routes: RouteRecordRaw[] = [ { path: "/", name: "app", component: () => import("./app.vue"), meta: { assets: () => import("./app.vue?assets"), }, children: [ { path: "/", name: "home", component: () => import("./pages/index.vue"), meta: { assets: () => import("./pages/index.vue?assets"), }, }, { path: "/about", name: "about", component: () => import("./pages/about.vue"), meta: { assets: () => import("./pages/about.vue?assets"), }, }, { path: "/:catchAll(.*)", name: "not-found", component: () => import("./pages/not-found.vue"), meta: { assets: () => import("./pages/not-found.vue?assets"), }, }, ], }, ]; ``` Use dynamic imports for lazy-loaded components to enable code splitting. The `meta.assets` function loads route-specific CSS and JS chunks. Define child routes under a root layout component for nested routing. ## 3. Create the Server Entry The server entry renders your Vue app with router support and head management: ```ts [app/entry-server.ts] import { createSSRApp } from "vue"; import { renderToString } from "vue/server-renderer"; import { RouterView, createMemoryHistory, createRouter } from "vue-router"; import { createHead, transformHtmlTemplate } from "unhead/server"; import { routes } from "./routes.ts"; import clientAssets from "./entry-client.ts?assets=client"; async function handler(request: Request): Promise { const app = createSSRApp(RouterView); const router = createRouter({ history: createMemoryHistory(), routes }); app.use(router); const url = new URL(request.url); const href = url.href.slice(url.origin.length); await router.push(href); await router.isReady(); const assets = clientAssets.merge( ...(await Promise.all( router.currentRoute.value.matched .map((to) => to.meta.assets) .filter(Boolean) .map((fn) => (fn as any)().then((m: any) => m.default)) )) ); const head = createHead(); head.push({ link: [ ...assets.css.map((attrs: any) => ({ rel: "stylesheet", ...attrs })), ...assets.js.map((attrs: any) => ({ rel: "modulepreload", ...attrs })), ], script: [{ type: "module", src: clientAssets.entry }], }); const renderedApp = await renderToString(app); const html = await transformHtmlTemplate(head, htmlTemplate(renderedApp)); return new Response(html, { headers: { "Content-Type": "text/html;charset=utf-8" }, }); } function htmlTemplate(body: string): string { return /* html */ ` Vue Router Custom Framework
${body}
`; } export default { fetch: handler, }; ``` The server uses `createMemoryHistory()` since there's no browser URL bar—the router navigates to the requested URL before rendering. Assets are loaded dynamically based on matched routes, ensuring only the CSS and JS needed for the current page are included. The `unhead` library manages `` elements, injecting stylesheets and scripts via `transformHtmlTemplate`. ## 4. Create the Client Entry The client entry hydrates the server-rendered HTML and takes over routing: ```ts [app/entry-client.ts] import { createSSRApp } from "vue"; import { RouterView, createRouter, createWebHistory } from "vue-router"; import { routes } from "./routes.ts"; async function main() { const app = createSSRApp(RouterView); const router = createRouter({ history: createWebHistory(), routes }); app.use(router); await router.isReady(); app.mount("#root"); } // eslint-disable-next-line unicorn/prefer-top-level-await main(); ``` The client entry creates a Vue app with `createWebHistory()` for browser-based routing. After the router is ready, it mounts to the `#root` element and hydrates the server-rendered HTML. ## 5. Create the Root Component The root component provides navigation and renders child routes: ```vue [app/app.vue] ``` ## Learn More - [Vue Router Documentation](https://router.vuejs.org/) - [Unhead Documentation](https://unhead.unjs.io/) - [Renderer](/docs/renderer) - [Server Entry](/docs/server-entry) ================================================ FILE: docs/4.examples/vite-trpc.md ================================================ --- category: vite icon: i-simple-icons-trpc --- # Vite + tRPC > End-to-end typesafe APIs with tRPC in Nitro using Vite. ::code-tree{defaultValue="server/trpc.ts" expandAll} ```html [index.html] tRPC Counter
Counter
``` ```json [package.json] { "type": "module", "scripts": { "dev": "vite", "build": "vite build", "preview": "vite preview" }, "devDependencies": { "@trpc/client": "^11.13.4", "@trpc/server": "^11.13.4", "nitro": "latest", "vite": "latest", "zod": "^4.3.6" } } ``` ```json [tsconfig.json] { "extends": "nitro/tsconfig", "compilerOptions": {} } ``` ```ts [vite.config.ts] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [ nitro({ routes: { "/trpc/**": "./server/trpc.ts", }, }), ], }); ``` ```ts [server/trpc.ts] import { initTRPC } from "@trpc/server"; import { fetchRequestHandler } from "@trpc/server/adapters/fetch"; let counter = 0; const t = initTRPC.create(); export const appRouter = t.router({ get: t.procedure.query(() => { return { value: counter }; }), inc: t.procedure.mutation(() => { counter++; return { value: counter }; }), }); export type AppRouter = typeof appRouter; export default { async fetch(request: Request): Promise { return fetchRequestHandler({ endpoint: "/trpc", req: request, router: appRouter, }); }, }; ``` :: Set up tRPC with Vite and Nitro for end-to-end typesafe APIs without code generation. This example builds a counter with server-side rendering for the initial value and client-side updates. ## Overview 1. Configure Vite with the Nitro plugin and route tRPC requests 2. Create a tRPC router with procedures 3. Create an HTML page with server-side rendering and client interactivity ## 1. Configure Vite Add the Nitro plugin and configure the `/trpc/**` route to point to your tRPC handler: ```ts [vite.config.ts] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [ nitro({ routes: { "/trpc/**": "./server/trpc.ts", }, }), ], }); ``` The `routes` option maps URL patterns to handler files. All requests to `/trpc/*` are handled by the tRPC router. ## 2. Create the tRPC Router Define your tRPC router with procedures and export it as a fetch handler: ```ts [server/trpc.ts] import { initTRPC } from "@trpc/server"; import { fetchRequestHandler } from "@trpc/server/adapters/fetch"; let counter = 0; const t = initTRPC.create(); export const appRouter = t.router({ get: t.procedure.query(() => { return { value: counter }; }), inc: t.procedure.mutation(() => { counter++; return { value: counter }; }), }); export type AppRouter = typeof appRouter; export default { async fetch(request: Request): Promise { return fetchRequestHandler({ endpoint: "/trpc", req: request, router: appRouter, }); }, }; ``` Define procedures using `t.procedure.query()` for read operations and `t.procedure.mutation()` for write operations. Export the `AppRouter` type so clients get full type inference. The default export uses tRPC's fetch adapter to handle incoming requests. ## 3. Create the HTML Page Create an HTML page with server-side rendering and client-side interactivity: ```html [index.html] tRPC Counter
Counter
``` The `

{{ message.user }}

Avatar

{{ message.text }}

{{ message.date }}

` ``` ```ts [nitro.config.ts] import { defineConfig } from "nitro"; export default defineConfig({ serverDir: "./", renderer: { static: true }, features: { websocket: true }, }); ``` ```json [package.json] { "type": "module", "scripts": { "dev": "nitro dev", "build": "nitro build" }, "devDependencies": { "nitro": "latest" } } ``` ```json [tsconfig.json] { "extends": "nitro/tsconfig" } ``` ```ts [vite.config.ts] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ``` ```ts [routes/_ws.ts] import { defineWebSocketHandler } from "nitro"; export default defineWebSocketHandler({ open(peer) { peer.send({ user: "server", message: `Welcome ${peer}!` }); peer.publish("chat", { user: "server", message: `${peer} joined!` }); peer.subscribe("chat"); }, message(peer, message) { if (message.text().includes("ping")) { peer.send({ user: "server", message: "pong" }); } else { const msg = { user: peer.toString(), message: message.toString(), }; peer.send(msg); // echo peer.publish("chat", msg); } }, close(peer) { peer.publish("chat", { user: "server", message: `${peer} left!` }); }, }); ``` :: This example implements a simple chat room using WebSockets. Clients connect, send messages, and receive messages from other users in real-time. The server broadcasts messages to all connected clients using pub/sub channels. ## WebSocket Handler Create a WebSocket route using `defineWebSocketHandler`. ```ts [routes/_ws.ts] import { defineWebSocketHandler } from "nitro"; export default defineWebSocketHandler({ open(peer) { peer.send({ user: "server", message: `Welcome ${peer}!` }); peer.publish("chat", { user: "server", message: `${peer} joined!` }); peer.subscribe("chat"); }, message(peer, message) { if (message.text().includes("ping")) { peer.send({ user: "server", message: "pong" }); } else { const msg = { user: peer.toString(), message: message.toString(), }; peer.send(msg); // echo peer.publish("chat", msg); } }, close(peer) { peer.publish("chat", { user: "server", message: `${peer} left!` }); }, }); ``` Different hooks are exposed by `defineWebSocketHandler()` to integrate with different parts of the websocket lifecycle. ## Learn More - [Routing](/docs/routing) - [crossws Documentation](https://crossws.h3.dev/guide/hooks) ================================================ FILE: docs/9.blog/1.v3-beta.md ================================================ --- date: 2026-03-11 category: release authors: - name: Pooya Parsa github: pi0 --- # Nitro v3 Beta is here! > Nitro v3 is now available as a public beta — a ground-up evolution of the server framework, built around web standards, Rolldown, Vite v8, and the same deploy-anywhere promise. ## A Brief History Nitro started as the server engine for [Nuxt 3](https://nuxt.com), designed to solve a specific problem: deployment-agnostic servers. Over time, Nitro grew beyond Nuxt. It became the foundation for many meta-frameworks and a toolkit for building standalone servers. With Nitro v3, we took the opportunity to rethink the fundamentals. leaner APIs, Web standards, first-class [Rolldown](https://rolldown.rs/) and [Vite v8](https://vite.dev/) integration, and a better experience for both humans and agents (more on that later!) Since we quietly announced v3 [alpha.0](https://github.com/nitrojs/nitro/releases/tag/v3.0.1-alpha.0) (11 Oct 2025) at the first [Vite Conf](https://viteconf.amsterdam/), Nitro v3 has been adopted by many users ([~280k](https://npmtrends.com/nitro-vs-nitro-nightly) weekly downloads!) and refined through amazing contributions and feedback. including [Tanstack Start](https://tanstack.com/start/latest/docs/framework/react/guide/hosting#nitro), [Vercel Workflows](https://useworkflow.dev/docs/getting-started), and production apps like [T3Chat](https://t3.chat/). A huge thanks to the VoidZero (Vite and Rolldown), Nuxt ([v5 is coming!](#nuxt-v5)) and TanStack Start teams and every contributor who helped bring Nitro v3 to this milestone. ❤️ ## Why Build Servers? We don't ship raw source files to the browser. We use build tools because they solve real problems: **HMR** for instant feedback, **code splitting** to load only what a route needs, **tree shaking** to eliminate dead code, and **minification** for smaller payloads. Tools like Webpack and then [Vite](https://vite.dev/) transformed frontend development from painful to productive. But frontend apps don't exist in isolation, they need APIs, databases, authentication, real-time data. They need a server. With the rise of serverless and edge computing, the server side now faces the same constraints the frontend solved years ago. **Cold starts** mean every millisecond of startup matters. **Memory limits** are strict — bloated dependencies can push you over. **Bundle size** directly impacts deploy speed and boot time. And your code needs to run everywhere: Node.js, Deno, Bun, Cloudflare Workers, Vercel, etc. Yet most server frameworks still ship unoptimized, unbundled code, assuming a long-running process where none of this matters. Nitro brings the build-tool philosophy to the backend. The same great DX you expect from frontend tooling: HMR for fast iteration and optimized builds powered by Rolldown with tree-shaken production output that performs as close to bare-metal as possible. **One codebase, any runtime, any platform.** ## ⚡ First-Class Vite Integration Nitro now has a native [Vite](https://vite.dev) plugin to build full stack apps. ```ts [vite.config.ts] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()], }); ``` Adding `nitro()` to your Vite apps gives you: - **API routes** via filesystem routing - **Server-side rendering** integrated with your frontend build - **A production server** — a single `vite build` produces an optimized `.output/` folder with both frontend and backend, ready to deploy This means you can add a full backend to any Vite project — See [examples](/examples) with [React](/examples/vite-ssr-react), [Vue](/examples/vite-ssr-vue-router) and [Solid.js](/examples/vite-ssr-solid). ## 🚀 Performance by Default, Zero Bloat Nitro compiles your routes at build time. There is no runtime router — each route loads on demand. Only the code needed to handle a specific request is loaded and executed. Minimal server bundle built with the `standard` preset is less than `10kB`, can be served with [srvx](https://srvx.h3.dev/) at close to native speeds, and includes all the good features from [H3](https://h3.dev/). We have also significantly reduced the number of dependencies, down to [less than 20](https://npmgraph.js.org/?q=nitro-nightly) from [321 dependencies](https://npmgraph.js.org/?q=nitropack). ## 🖌️ New Identity: `nitro` Nitro v3 ships under a new NPM package: [`nitro`](https://npmx.dev/package/nitro), replacing the legacy `nitropack`. All imports now use clean `nitro/*` subpaths: ```ts import { defineNitroConfig } from "nitro/config"; import { defineHandler } from "nitro"; import { useStorage } from "nitro/storage"; import { useDatabase } from "nitro/database"; ``` No more deep `nitropack/runtime/*` paths, plus, you can import nitro subpaths outside of builder useful for unit testing. ## 🔧 Bring Your Own Framework Nitro v3 is not opinionated about your HTTP layer. You can use the built-in filesystem routing, or take full control with a `server.ts` entry file and bring any framework you prefer: ```ts [server.ts] import { Hono } from "hono"; const app = new Hono(); app.get("/", (c) => c.text("Hello from Hono!")); export default app; ``` ## 🌐 H3 (v2) with Web Standards Nitro v3 upgrades to [H3 v2](https://h3.dev), which has been fully rewritten around web standard primitives — [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request), [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response), [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers), and [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL). The result is cleaner, more portable server code: ```ts [routes/hello.ts] import { defineHandler } from "nitro"; export default defineHandler((event) => { const ua = event.req.headers.get("user-agent"); return { message: "Hello Nitro v3!", ua }; }); ``` Reading request bodies uses native APIs: ```ts [routes/submit.ts] import { defineHandler } from "nitro"; export default defineHandler(async (event) => { const body = await event.req.json(); return { received: body }; }); ``` No wrappers, no abstractions for things the platform already provides. If you know the Web API, you know H3 v2. [Elysia](https://elysiajs.com/), [h3](https://h3.dev), [Hono](https://hono.dev) — anything that speaks web standards works with Nitro. ## 🗄️ Built-in Primitives Nitro ships with powerful but small and **fully opt-in** agnostic server primitives that work across every runtime. ::note When not used, nothing extra will be added to the server bundle. You can still use native platform primitives alongside Nitro's built-in ones. We are also bringing first class emulation for platform-specific primitives for dev See [env-runner](https://github.com/unjs/env-runner) and [nitrojs/nitro#4088](https://github.com/nitrojs/nitro/pull/4088) for more details. :: ### Storage A runtime-agnostic key-value layer with 20+ drivers — FS, Redis, S3, Cloudflare KV, Vercel Blob and [more](https://unstorage.unjs.io/drivers). Attach drivers to namespaces and swap them without changing your application code. ```ts import { useStorage } from "nitro/storage"; const storage = useStorage(); await storage.setItem("user:1", { name: "Nitro" }); ``` :read-more{to="/docs/storage"} ### Caching Cache server routes and functions, backed by the storage layer. Supports stale-while-revalidate, TTL, and custom cache keys out of the box. ```ts import { defineCachedHandler } from "nitro/cache"; export default defineCachedHandler((event) => { return "I am cached for an hour"; }, { maxAge: 60 * 60 }); ``` :read-more{to="/docs/cache"} ### Database A built-in SQL database that defaults to SQLite for development and can connect to Postgres, MySQL, and [more](https://db0.unjs.io/connectors) using the same API. ```ts import { useDatabase } from "nitro/database"; const db = useDatabase(); const users = await db.sql`SELECT * FROM users`; ``` :read-more{to="/docs/database"} ## 🌍 Deploy Anywhere Build your server into an optimized `.output/` folder compatible with: - **Runtimes**: Node.js, Bun, Deno - **Platforms**: Cloudflare Workers, Netlify, Vercel, AWS Lambda, Azure, Firebase, Deno Deploy, and more No configuration needed — Nitro auto-detects your deployment target. Take advantage of platform features like ISR, SWR, and edge rendering without changing a single line of code. ## 🎨 Server-Side Rendering Render HTML with your favorite templating engine, or use component libraries like React, Vue, or Svelte directly on the server. Go full universal rendering with client-side hydration. Nitro provides the foundation and a progressive approach — start with API routes, add rendering when you need it, and scale to full SSR at your own pace. :read-more{to="/docs/renderer"} ## 🟢 Nuxt v5 Nitro v3 will power the next major version of [Nuxt](https://nuxt.com). [Nuxt v5](https://nuxt.com/blog/roadmap-v4) will ship with Nitro v3 and H3 v2 at its core, bringing web-standard request handling, Rolldown-powered builds, and the Vite Environment API to the Nuxt ecosystem. If you're a Nuxt user, you can already start preparing by familiarizing yourself with Nitro v3's new APIs, which will carry directly into Nuxt 5, and you can [follow progress](https://github.com/nuxt/nuxt/discussions/34504) on adopting Nitro v3 in Nuxt ## 🏁 Getting Started ### Create a New Project :pm-x{command="create-nitro-app"} See the [quick start guide](/docs/quick-start) for a full step-by-step walkthrough. ## 🔄 Migrating from v2 Nitro v3 introduces intentional breaking changes to set a cleaner foundation. Here are the key ones: - `nitropack` → `nitro` (package rename) - `nitropack/runtime/*` → `nitro/*` (clean subpath imports) - `eventHandler` → `defineHandler` (H3 v2) - `createError` → `HTTPError` (H3 v2) - Web standard `event.req` headers and body APIs - Node.js minimum version: **20** - Preset renames and consolidation (e.g., `cloudflare` → `cloudflare_module`) For a complete list, see the [migration guide](/docs/migration). --- Thank you to everyone who has contributed to Nitro over the years. We can't wait to see what you build with the new Nitro! ❤️ - [GitHub](https://github.com/nitrojs/nitro) — Issues and discussions - [Discord](https://discord.nitro.build) — Chat with the community ================================================ FILE: docs/9.blog/index.md ================================================ # Blog Nitro blog posts. ================================================ FILE: docs/index.md ================================================ --- seo: title: Build Full-Stack Servers description: Nitro extends your Vite application with a production-ready server, compatible with any runtime. Add server routes to your application and deploy many hosting platform with a zero-config experience. --- ::u-page-hero --- orientation: horizontal --- ::code-group :::prose-pre --- filename: vite.config.ts --- ```ts import { defineConfig } from 'vite' import { nitro } from 'nitro/vite' export default defineConfig({ plugins: [nitro()], nitro: { serverDir: "./server" } }) ``` ::: :::prose-pre --- filename: nitro.config.ts --- ```ts import { defineConfig } from 'nitro' export default defineConfig({ preset: "node", serverDir: "./server", routeRules: { "/api/**": { cache: true } } }) ``` ::: :: :hero-background #title Build [/Servers]{.text-primary} #description Nitro extends your Vite application with a production-ready server, compatible with any runtime. Add server routes to your application and deploy many hosting platform with a zero-config experience. #links :app-hero-links :: ::hero-features --- features: - title: Fast description: Enjoy the fast Vite 8 (rolldown powered) development experience with HMR on the server and optimized for production. icon: i-lucide-zap color: text-amber-500 bgColor: bg-amber-500/10 borderColor: "group-hover:border-amber-500/30" - title: Agnostic description: Deploy the same codebase to any deployment provider with zero config and locked-in. icon: i-lucide-globe color: text-sky-500 bgColor: bg-sky-500/10 borderColor: "group-hover:border-sky-500/30" - title: Minimal description: Nitro adds no overhead to runtime. Build your servers with any modern tool you like. icon: i-lucide-feather color: text-emerald-500 bgColor: bg-emerald-500/10 borderColor: "group-hover:border-emerald-500/30" --- :: ::performance-showcase --- metrics: - label: Bare metal perf value: "~Native" unit: RPS description: Using compile router, and fast paths for request handling. icon: i-lucide-gauge color: text-emerald-500 bgColor: bg-emerald-500/10 barWidth: "95%" barColor: bg-emerald-500 - label: Minimum install Size value: Tiny unit: deps description: Minimal dependencies. No bloated node_modules. icon: i-lucide-package color: text-sky-500 bgColor: bg-sky-500/10 barWidth: "15%" barColor: bg-sky-500 - label: Small and portable output value: "‹ 10" unit: kB description: Standard server builds produce ultra-small output bundles. icon: i-lucide-file-output color: text-violet-500 bgColor: bg-violet-500/10 barWidth: "10%" barColor: bg-violet-500 - label: FAST builds value: "‹ 1" unit: sec description: Cold production builds complete in seconds, not minutes. icon: i-lucide-timer color: text-amber-500 bgColor: bg-amber-500/10 barWidth: "12%" barColor: bg-amber-500 --- :: ::landing-features #body :::feature-card --- headline: Routing link: /docs/routing link-label: Routing docs --- #title File-system routing #description Create server routes in the routes/ folder and they are automatically registered. Or bring your own framework — H3, Hono, Elysia, Express — via a server.ts entry. ::: :::feature-card --- headline: Versatile link: /deploy link-label: Explore deploy targets --- #title Deploy everywhere #description The same codebase deploys to Node.js, Cloudflare Workers, Deno, Bun, AWS Lambda, Vercel, Netlify, and more — zero config, no vendor lock-in. ::: :::feature-card --- headline: Storage link: /docs/storage link-label: Storage docs --- #title Universal storage #description Built-in key-value storage abstraction powered by unstorage. Works with filesystem, Redis, Cloudflare KV, and more — same API everywhere. ::: :::feature-card --- headline: Caching link: /docs/cache link-label: Caching docs --- #title Built-in caching #description Cache route handlers and arbitrary functions with a simple API. Supports multiple storage backends and stale-while-revalidate patterns. ::: :::feature-card --- headline: Server Entry link: /docs/server-entry link-label: Server entry docs --- #title Web standard server #description Go full Web standard and pick the library of your choice. Use H3, Hono, Elysia, Express, or the raw fetch API — Nitro handles the rest. ::: :::feature-card --- headline: Renderer link: /docs/renderer link-label: Renderer docs --- #title Universal renderer #description Use any frontend framework as your renderer. Nitro provides the server layer while your framework handles the UI. ::: :::feature-card --- headline: Plugins link: /docs/plugins link-label: Plugins docs --- #title Server plugins #description Extend Nitro's runtime behavior with plugins. Hook into lifecycle events, register custom logic, and auto-load from the plugins/ directory. ::: :::feature-card --- headline: Database link: /docs/database link-label: Database docs --- #title Built-in database #description Lightweight SQL database layer powered by db0. Pre-configured with SQLite out of the box, with support for PostgreSQL, MySQL, and Cloudflare D1. ::: :::feature-card --- headline: Assets link: /docs/assets link-label: Assets docs --- #title Static & server assets #description Serve public assets directly to clients or bundle server assets for programmatic access. Works seamlessly across all deployment targets. ::: :: ::page-sponsors ================================================ FILE: docs/package.json ================================================ { "private": true, "scripts": { "dev": "undocs dev", "build": "undocs build" }, "devDependencies": { "automd": "^0.4.3", "geist": "^1.7.0", "motion-v": "^2.0.0", "shaders": "^2.3.75", "undocs": "npm:undocs-nightly@0.4.17-20260310-213525-c3c295a", "zod": "^4.3.6" } } ================================================ FILE: docs/pnpm-workspace.yaml ================================================ packages: [] ignoredBuiltDependencies: - '@parcel/watcher' - '@tailwindcss/oxide' - esbuild - vue-demi onlyBuiltDependencies: - better-sqlite3 ================================================ FILE: examples/api-routes/README.md ================================================ Nitro supports file-based routing in the `api/` or `routes/` directory. Each file becomes an API endpoint based on its path. ## Basic Route Create a file in the `api/` directory to define a route. The file path becomes the URL path: ```ts [api/hello.ts] import { defineHandler } from "nitro"; export default defineHandler(() => "Nitro is amazing!"); ``` This creates a `GET /api/hello` endpoint. ## Dynamic Routes Use square brackets `[param]` for dynamic URL segments. Access params via `event.context.params`: ```ts [api/hello/[name].ts] import { defineHandler } from "nitro"; export default defineHandler((event) => `Hello (param: ${event.context.params!.name})!`); ``` This creates a `GET /api/hello/:name` endpoint (e.g., `/api/hello/world`). ## HTTP Methods Suffix your file with the HTTP method (`.get.ts`, `.post.ts`, `.put.ts`, `.delete.ts`, etc.): ### GET Handler ```ts [api/test.get.ts] import { defineHandler } from "nitro"; export default defineHandler(() => "Test get handler"); ``` ### POST Handler ```ts [api/test.post.ts] import { defineHandler } from "nitro"; export default defineHandler(async (event) => { const body = await event.req.json(); return { message: "Test post handler", body, }; }); ``` ================================================ FILE: examples/api-routes/api/hello/[name].ts ================================================ import { defineHandler } from "nitro"; export default defineHandler((event) => `Hello (param: ${event.context.params!.name})!`); ================================================ FILE: examples/api-routes/api/hello.ts ================================================ import { defineHandler } from "nitro"; export default defineHandler(() => "Nitro is amazing!"); ================================================ FILE: examples/api-routes/api/test.get.ts ================================================ import { defineHandler } from "nitro"; export default defineHandler(() => "Test get handler"); ================================================ FILE: examples/api-routes/api/test.post.ts ================================================ import { defineHandler } from "nitro"; export default defineHandler(async (event) => { const body = await event.req.json(); return { message: "Test post handler", body, }; }); ================================================ FILE: examples/api-routes/index.html ================================================ API Routes

API Routes:

================================================ FILE: examples/api-routes/nitro.config.ts ================================================ import { defineConfig } from "nitro"; export default defineConfig({ serverDir: "./", }); ================================================ FILE: examples/api-routes/package.json ================================================ { "type": "module", "scripts": { "dev": "nitro dev", "build": "nitro build" }, "devDependencies": { "nitro": "latest" } } ================================================ FILE: examples/api-routes/tsconfig.json ================================================ { "extends": "nitro/tsconfig" } ================================================ FILE: examples/api-routes/vite.config.ts ================================================ import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ================================================ FILE: examples/auto-imports/README.md ================================================ Functions exported from `server/utils/` are automatically available without explicit imports when auto-imports are enabled. Define a utility once and use it anywhere in your server code. ## Configuration Enable auto-imports by setting `imports` in your config: ```ts [nitro.config.ts] import { defineConfig } from "nitro"; export default defineConfig({ serverDir: true, imports: {}, }); ``` ## Using Auto Imports 1. Create a utility file in `server/utils/`: ```ts [server/utils/hello.ts] export function makeGreeting(name: string) { return `Hello, ${name}!`; } ``` 2. The function is available without importing it: ```ts [server.ts] import { defineHandler } from "nitro"; import { makeGreeting } from "./server/utils/hello.ts"; export default defineHandler(() => `

${makeGreeting("Nitro")}

`); ``` With this setup, any function exported from `server/utils/` becomes globally available. Nitro scans the directory and generates the necessary imports automatically. ================================================ FILE: examples/auto-imports/nitro.config.ts ================================================ import { defineConfig } from "nitro"; export default defineConfig({ serverDir: true, imports: {}, }); ================================================ FILE: examples/auto-imports/package.json ================================================ { "type": "module", "scripts": { "dev": "nitro dev", "build": "nitro build" }, "devDependencies": { "nitro": "latest" } } ================================================ FILE: examples/auto-imports/server/utils/hello.ts ================================================ export function makeGreeting(name: string) { return `Hello, ${name}!`; } ================================================ FILE: examples/auto-imports/server.ts ================================================ import { defineHandler } from "nitro"; import { makeGreeting } from "./server/utils/hello.ts"; export default defineHandler(() => `

${makeGreeting("Nitro")}

`); ================================================ FILE: examples/auto-imports/tsconfig.json ================================================ { "include": [".nitro/types/nitro-imports.d.ts", "src"] } ================================================ FILE: examples/auto-imports/vite.config.ts ================================================ import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ================================================ FILE: examples/cached-handler/README.md ================================================ This example shows how to cache an expensive operation (a 500 ms delay) and conditionally bypass the cache using a query parameter. On first request, the handler executes and caches the result. Subsequent requests return the cached response instantly until the cache expires or is bypassed. ## How It Works ```ts [server.ts] import { html } from "nitro"; import { defineCachedHandler } from "nitro/cache"; export default defineCachedHandler( async () => { await new Promise((resolve) => setTimeout(resolve, 500)); return html` Response generated at ${new Date().toISOString()} (took 500ms)
(skip cache) `; }, { shouldBypassCache: ({ req }) => req.url.includes("skipCache=true") } ); ``` The handler simulates a slow operation with a 500ms delay. As `defineCachedHandler` wraps it, the response is cached after the first execution. The `shouldBypassCache` option checks for `?skipCache=true` in the URL and when present the cache is skipped and the handler runs fresh. ================================================ FILE: examples/cached-handler/nitro.config.ts ================================================ import { defineConfig } from "nitro"; export default defineConfig({}); ================================================ FILE: examples/cached-handler/package.json ================================================ { "type": "module", "scripts": { "dev": "nitro dev", "build": "nitro build" }, "devDependencies": { "nitro": "latest" } } ================================================ FILE: examples/cached-handler/server.ts ================================================ import { html } from "nitro"; import { defineCachedHandler } from "nitro/cache"; export default defineCachedHandler( async () => { await new Promise((resolve) => setTimeout(resolve, 500)); return html` Response generated at ${new Date().toISOString()} (took 500ms)
(skip cache) `; }, { shouldBypassCache: ({ req }) => req.url.includes("skipCache=true") } ); ================================================ FILE: examples/cached-handler/tsconfig.json ================================================ { "extends": "nitro/tsconfig" } ================================================ FILE: examples/cached-handler/vite.config.ts ================================================ import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ================================================ FILE: examples/custom-error-handler/README.md ================================================ This example shows how to intercept all errors and return a custom response format. When any route throws an error, Nitro calls your error handler instead of returning the default error page. ## Error Handler Create an `error.ts` file in your project root to define the global error handler: ```ts [error.ts] import { defineErrorHandler } from "nitro"; export default defineErrorHandler((error, _event) => { return new Response(`Custom Error Handler: ${error.message}`, { status: 500, headers: { "Content-Type": "text/plain" }, }); }); ``` The handler receives the thrown error and the H3 event object. You can use the event to access request details like headers, cookies, or the URL path to customize responses per route. ## Triggering an Error The main handler throws an error to demonstrate the custom error handler: ```ts [server.ts] import { defineHandler, HTTPError } from "nitro"; export default defineHandler(() => { throw new HTTPError("Example Error!", { status: 500 }); }); ``` When you visit the page, instead of seeing a generic error page, you'll see "Custom Error Handler: Example Error!" because the error handler intercepts the thrown error. ================================================ FILE: examples/custom-error-handler/error.ts ================================================ import { defineErrorHandler } from "nitro"; export default defineErrorHandler((error, _event) => { return new Response(`Custom Error Handler: ${error.message}`, { status: 500, headers: { "Content-Type": "text/plain" }, }); }); ================================================ FILE: examples/custom-error-handler/nitro.config.ts ================================================ import { defineConfig } from "nitro"; // import errorHandler from "./error"; export default defineConfig({ errorHandler: "./error.ts", // devErrorHandler: errorHandler, }); ================================================ FILE: examples/custom-error-handler/package.json ================================================ { "type": "module", "scripts": { "dev": "nitro dev", "build": "nitro build" }, "devDependencies": { "nitro": "latest" } } ================================================ FILE: examples/custom-error-handler/server.ts ================================================ import { defineHandler, HTTPError } from "nitro"; export default defineHandler(() => { throw new HTTPError("Example Error!", { status: 500 }); }); ================================================ FILE: examples/custom-error-handler/tsconfig.json ================================================ { "extends": "nitro/tsconfig" } ================================================ FILE: examples/custom-error-handler/vite.config.ts ================================================ import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ================================================ FILE: examples/database/README.md ================================================ Nitro provides a built-in database layer that uses SQL template literals for safe, parameterized queries. This example creates a users table, inserts a record, and queries it back. ## Querying the Database ```ts [server.ts] import { defineHandler } from "nitro"; import { useDatabase } from "nitro/database"; export default defineHandler(async () => { const db = useDatabase(); // Create users table await db.sql`DROP TABLE IF EXISTS users`; await db.sql`CREATE TABLE IF NOT EXISTS users ("id" TEXT PRIMARY KEY, "firstName" TEXT, "lastName" TEXT, "email" TEXT)`; // Add a new user const userId = String(Math.round(Math.random() * 10_000)); await db.sql`INSERT INTO users VALUES (${userId}, 'John', 'Doe', '')`; // Query for users const { rows } = await db.sql`SELECT * FROM users WHERE id = ${userId}`; return { rows, }; }); ``` Retrieve the database instance using `useDatabase()`. The database can be queried using `db.sql`, and variables like `${userId}` are automatically escaped to prevent SQL injection. ## Running Migrations with Tasks Nitro tasks let you run operations outside of request handlers. For database migrations, create a task file in `tasks/` and run it via the CLI. This keeps schema changes separate from your application code. ```ts [tasks/db/migrate.ts] import { defineTask } from "nitro/task"; import { useDatabase } from "nitro/database"; export default defineTask({ meta: { description: "Run database migrations", }, async run() { const db = useDatabase(); console.log("Running database migrations..."); // Create users table await db.sql`DROP TABLE IF EXISTS users`; await db.sql`CREATE TABLE IF NOT EXISTS users ("id" TEXT PRIMARY KEY, "firstName" TEXT, "lastName" TEXT, "email" TEXT)`; return { result: "Database migrations complete!", }; }, }); ``` ================================================ FILE: examples/database/nitro.config.ts ================================================ import { defineConfig } from "nitro"; export default defineConfig({ experimental: { database: true, tasks: true, }, database: { default: { connector: "sqlite" }, }, }); ================================================ FILE: examples/database/package.json ================================================ { "type": "module", "scripts": { "dev": "nitro dev", "build": "nitro build" }, "devDependencies": { "nitro": "latest" } } ================================================ FILE: examples/database/server.ts ================================================ import { defineHandler } from "nitro"; import { useDatabase } from "nitro/database"; export default defineHandler(async () => { const db = useDatabase(); // Create users table await db.sql`DROP TABLE IF EXISTS users`; await db.sql`CREATE TABLE IF NOT EXISTS users ("id" TEXT PRIMARY KEY, "firstName" TEXT, "lastName" TEXT, "email" TEXT)`; // Add a new user const userId = String(Math.round(Math.random() * 10_000)); await db.sql`INSERT INTO users VALUES (${userId}, 'John', 'Doe', '')`; // Query for users const { rows } = await db.sql`SELECT * FROM users WHERE id = ${userId}`; return { rows, }; }); ================================================ FILE: examples/database/tasks/db/migrate.ts ================================================ import { defineTask } from "nitro/task"; import { useDatabase } from "nitro/database"; export default defineTask({ meta: { description: "Run database migrations", }, async run() { const db = useDatabase(); console.log("Running database migrations..."); // Create users table await db.sql`DROP TABLE IF EXISTS users`; await db.sql`CREATE TABLE IF NOT EXISTS users ("id" TEXT PRIMARY KEY, "firstName" TEXT, "lastName" TEXT, "email" TEXT)`; return { result: "Database migrations complete!", }; }, }); ================================================ FILE: examples/database/tsconfig.json ================================================ { "extends": "nitro/tsconfig" } ================================================ FILE: examples/database/vite.config.ts ================================================ import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ================================================ FILE: examples/elysia/README.md ================================================ ## Server Entry ```ts [server.ts] import { Elysia } from "elysia"; const app = new Elysia(); app.get("/", () => "Hello, Elysia with Nitro!"); export default app.compile(); ``` Nitro auto-detects `server.ts` in your project root and uses it as the server entry. The Elysia app handles all incoming requests, giving you full control over routing and middleware. Call `app.compile()` before exporting to optimize the router for production. ================================================ FILE: examples/elysia/nitro.config.ts ================================================ import { defineConfig } from "nitro"; export default defineConfig({}); ================================================ FILE: examples/elysia/package.json ================================================ { "type": "module", "scripts": { "build": "nitro build", "dev": "nitro dev" }, "devDependencies": { "elysia": "^1.4.28", "nitro": "latest" } } ================================================ FILE: examples/elysia/server.ts ================================================ import { Elysia } from "elysia"; const app = new Elysia(); app.get("/", () => "Hello, Elysia with Nitro!"); export default app.compile(); ================================================ FILE: examples/elysia/tsconfig.json ================================================ { "extends": "nitro/tsconfig" } ================================================ FILE: examples/elysia/vite.config.ts ================================================ import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ================================================ FILE: examples/express/README.md ================================================ ## Server Entry ```ts [server.node.ts] import Express from "express"; const app = Express(); app.use("/", (_req, res) => { res.send("Hello from Express with Nitro!"); }); export default app; ``` Nitro auto-detects `server.node.ts` in your project root and uses it as the server entry. The Express app handles all incoming requests, giving you full control over routing and middleware. ::note The `.node.ts` suffix indicates this entry is Node.js specific and won't work in other runtimes like Cloudflare Workers or Deno. :: ================================================ FILE: examples/express/nitro.config.ts ================================================ import { defineConfig } from "nitro"; export default defineConfig({}); ================================================ FILE: examples/express/package.json ================================================ { "type": "module", "scripts": { "build": "nitro build", "dev": "nitro dev" }, "devDependencies": { "@types/express": "^5.0.6", "express": "^5.2.1", "nitro": "latest" } } ================================================ FILE: examples/express/server.node.ts ================================================ import Express from "express"; const app = Express(); app.use("/", (_req, res) => { res.send("Hello from Express with Nitro!"); }); export default app; ================================================ FILE: examples/express/tsconfig.json ================================================ { "extends": "nitro/tsconfig" } ================================================ FILE: examples/express/vite.config.ts ================================================ import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ================================================ FILE: examples/fastify/README.md ================================================ ## Server Entry ```ts [server.node.ts] import Fastify from "fastify"; const app = Fastify(); app.get("/", () => "Hello, Fastify with Nitro!"); await app.ready(); export default app.routing; ``` Nitro auto-detects `server.node.ts` in your project root and uses it as the server entry. Call `await app.ready()` to initialize all registered plugins before exporting. Export `app.routing` (not `app`) to provide Nitro with the request handler function. ::note The `.node.ts` suffix indicates this entry is Node.js specific and won't work in other runtimes like Cloudflare Workers or Deno. :: ================================================ FILE: examples/fastify/nitro.config.ts ================================================ import { defineConfig } from "nitro"; export default defineConfig({}); ================================================ FILE: examples/fastify/package.json ================================================ { "type": "module", "scripts": { "build": "nitro build", "dev": "nitro dev" }, "devDependencies": { "fastify": "^5.8.2", "nitro": "latest" } } ================================================ FILE: examples/fastify/server.node.ts ================================================ import Fastify from "fastify"; const app = Fastify(); app.get("/", () => "Hello, Fastify with Nitro!"); await app.ready(); export default app.routing; ================================================ FILE: examples/fastify/tsconfig.json ================================================ { "extends": "nitro/tsconfig" } ================================================ FILE: examples/fastify/vite.config.ts ================================================ import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ================================================ FILE: examples/hello-world/README.md ================================================ The simplest Nitro server. Export an object with a `fetch` method that receives a standard `Request` and returns a `Response`. No frameworks, no abstractions, just the web platform. ## Server Entry ```ts [server.ts] export default { fetch(req: Request) { return new Response("Nitro Works!"); }, }; ``` The `fetch` method follows the same signature as Service Workers and Cloudflare Workers. This pattern works across all deployment targets because it uses web standards. Add the Nitro plugin to Vite and it handles the rest: dev server, hot reloading, and production builds. ================================================ FILE: examples/hello-world/nitro.config.ts ================================================ import { defineConfig } from "nitro"; export default defineConfig({}); ================================================ FILE: examples/hello-world/package.json ================================================ { "type": "module", "scripts": { "build": "nitro build", "dev": "nitro dev", "preview": "node .output/server/index.mjs" }, "devDependencies": { "nitro": "latest" } } ================================================ FILE: examples/hello-world/server.ts ================================================ export default { fetch(req: Request) { return new Response("Nitro Works!"); }, }; ================================================ FILE: examples/hello-world/tsconfig.json ================================================ { "extends": "nitro/tsconfig" } ================================================ FILE: examples/hello-world/vite.config.ts ================================================ import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ================================================ FILE: examples/hono/README.md ================================================ ## Server Entry ```ts [server.ts] import { Hono } from "hono"; const app = new Hono(); app.get("/", (c) => { return c.text("Hello, Hono with Nitro!"); }); export default app; ``` Nitro auto-detects `server.ts` in your project root and uses it as the server entry. The Hono app handles all incoming requests, giving you full control over routing and middleware. Hono is cross-runtime compatible, so this server entry works across all Nitro deployment targets including Node.js, Deno, Bun, and Cloudflare Workers. ================================================ FILE: examples/hono/nitro.config.ts ================================================ import { defineConfig } from "nitro"; export default defineConfig({}); ================================================ FILE: examples/hono/package.json ================================================ { "type": "module", "scripts": { "build": "nitro build", "dev": "nitro dev" }, "devDependencies": { "hono": "^4.12.8", "nitro": "latest" } } ================================================ FILE: examples/hono/server.ts ================================================ import { Hono } from "hono"; const app = new Hono(); app.get("/", (c) => { return c.text("Hello, Hono with Nitro!"); }); export default app; ================================================ FILE: examples/hono/tsconfig.json ================================================ { "extends": "nitro/tsconfig" } ================================================ FILE: examples/hono/vite.config.ts ================================================ import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ================================================ FILE: examples/import-alias/README.md ================================================ Import aliases like `~` and `#` let you reference modules with shorter paths instead of relative imports. ## Importing Using Aliases ```ts [server/routes/index.ts] import { sum } from "~server/utils/math.ts"; import { rand } from "#server/utils/math.ts"; export default () => { const [a, b] = [rand(1, 10), rand(1, 10)]; const result = sum(a, b); return `The sum of ${a} + ${b} = ${result}`; }; ``` The route imports the `sum` function using `~server/` and `rand` using `#server/`. Both resolve to the same `server/utils/math.ts` file. The handler generates two random numbers and returns their sum. ## Configuration Aliases can be configured in `package.json` imports field or `nitro.config.ts`. ================================================ FILE: examples/import-alias/nitro.config.ts ================================================ import { defineConfig } from "nitro"; export default defineConfig({ serverDir: true, }); ================================================ FILE: examples/import-alias/package.json ================================================ { "type": "module", "imports": { "#server/*": "./server/*" }, "scripts": { "build": "nitro build", "dev": "nitro dev", "preview": "node .output/server/index.mjs" }, "devDependencies": { "nitro": "latest" } } ================================================ FILE: examples/import-alias/server/routes/index.ts ================================================ import { sum } from "~server/utils/math.ts"; import { rand } from "#server/utils/math.ts"; export default () => { const [a, b] = [rand(1, 10), rand(1, 10)]; const result = sum(a, b); return `The sum of ${a} + ${b} = ${result}`; }; ================================================ FILE: examples/import-alias/server/utils/math.ts ================================================ export function rand(min: number, max: number): number { return Math.floor(Math.random() * (max - min + 1)) + min; } export function sum(a: number, b: number): number { return a + b; } ================================================ FILE: examples/import-alias/tsconfig.json ================================================ { "extends": "nitro/tsconfig", "compilerOptions": { "paths": { "~server/*": ["./server/*"] } } } ================================================ FILE: examples/import-alias/vite.config.ts ================================================ import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()], resolve: { tsconfigPaths: true } }); ================================================ FILE: examples/middleware/README.md ================================================ Middleware functions run before route handlers on every request. They can modify the request, add context, or return early responses. ## Defining Middleware Create files in `server/middleware/`. They run in alphabetical order: ```ts [server/middleware/auth.ts] import { defineMiddleware } from "nitro"; export default defineMiddleware((event) => { event.context.auth = { name: "User " + Math.round(Math.random() * 100) }; }); ``` Middleware can: - Add data to `event.context` for use in handlers - Return a response early to short-circuit the request - Modify request headers or other properties ## Accessing Context in Handlers Data added to `event.context` in middleware is available in all subsequent handlers: ```ts [server.ts] import { defineHandler } from "nitro"; export default defineHandler((event) => ({ auth: event.context.auth, })); ``` ================================================ FILE: examples/middleware/nitro.config.ts ================================================ import { defineConfig } from "nitro"; export default defineConfig({ serverDir: true, }); ================================================ FILE: examples/middleware/package.json ================================================ { "type": "module", "scripts": { "dev": "nitro dev", "build": "nitro build" }, "devDependencies": { "nitro": "latest" } } ================================================ FILE: examples/middleware/server/middleware/auth.ts ================================================ import { defineMiddleware } from "nitro"; export default defineMiddleware((event) => { event.context.auth = { name: "User " + Math.round(Math.random() * 100) }; }); ================================================ FILE: examples/middleware/server.ts ================================================ import { defineHandler } from "nitro"; export default defineHandler((event) => ({ auth: event.context.auth, })); ================================================ FILE: examples/middleware/tsconfig.json ================================================ { "extends": "nitro/tsconfig" } ================================================ FILE: examples/middleware/vite.config.ts ================================================ import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ================================================ FILE: examples/mono-jsx/README.md ================================================ ## Server Entry ```tsx [server.tsx] export default () => (

Nitro + mono-jsx works!

); ``` Nitro auto-detects `server.tsx` and uses mono-jsx to transform JSX into HTML. Export a function that returns JSX, and Nitro sends the rendered HTML as the response. ================================================ FILE: examples/mono-jsx/nitro.config.ts ================================================ import { defineConfig } from "nitro"; export default defineConfig({}); ================================================ FILE: examples/mono-jsx/package.json ================================================ { "type": "module", "scripts": { "dev": "nitro dev", "build": "nitro build" }, "devDependencies": { "mono-jsx": "latest", "nitro": "latest" } } ================================================ FILE: examples/mono-jsx/server.tsx ================================================ export default () => (

Nitro + mono-jsx works!

); ================================================ FILE: examples/mono-jsx/tsconfig.json ================================================ { "extends": "nitro/tsconfig", "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "mono-jsx" } } ================================================ FILE: examples/mono-jsx/vite.config.ts ================================================ import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ================================================ FILE: examples/nano-jsx/README.md ================================================ ## Server Entry ```tsx [server.tsx] import { defineHandler, html } from "nitro"; import { renderSSR } from "nano-jsx"; export default defineHandler(() => { return html(renderSSR(() =>

Nitro + nano-jsx works!

)); }); ``` Nitro auto-detects `server.tsx` and uses it as the server entry. Use `renderSSR` from nano-jsx to convert JSX into an HTML string. The `html` helper from H3 sets the correct content type header. ================================================ FILE: examples/nano-jsx/nitro.config.ts ================================================ import { defineConfig } from "nitro"; export default defineConfig({}); ================================================ FILE: examples/nano-jsx/package.json ================================================ { "type": "module", "scripts": { "dev": "nitro dev", "build": "nitro build" }, "devDependencies": { "nano-jsx": "^0.2.1", "nitro": "latest" } } ================================================ FILE: examples/nano-jsx/server.tsx ================================================ import { defineHandler, html } from "nitro"; import { renderSSR } from "nano-jsx"; export default defineHandler(() => { return html(renderSSR(() =>

Nitro + nano-jsx works!

)); }); ================================================ FILE: examples/nano-jsx/tsconfig.json ================================================ { "extends": "nitro/tsconfig", "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "nano-jsx/esm" } } ================================================ FILE: examples/nano-jsx/vite.config.ts ================================================ import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ================================================ FILE: examples/plugins/README.md ================================================ Plugins let you hook into Nitro's runtime lifecycle. This example shows a plugin that modifies the `Content-Type` header on every response. Create files in `server/plugins/` and they're automatically loaded at startup. ## Defining a Plugin ```ts [server/plugins/test.ts] import { definePlugin } from "nitro"; import { useNitroHooks } from "nitro/app"; export default definePlugin((nitroApp) => { const hooks = useNitroHooks(); hooks.hook("response", (event) => { event.headers.set("content-type", "html; charset=utf-8"); }); }); ``` The plugin uses `useNitroHooks()` to access the hooks system, then registers a `response` hook that runs after every request. Here it sets the content type to HTML, but you could log requests, add security headers, or modify responses in any way. ## Main Handler ```ts [server.ts] import { eventHandler } from "h3"; export default eventHandler(() => "

Hello Nitro!

"); ``` The handler returns HTML without setting a content type. The plugin automatically adds the correct `Content-Type: html; charset=utf-8` header to the response. ================================================ FILE: examples/plugins/nitro.config.ts ================================================ import { defineConfig } from "nitro"; export default defineConfig({ serverDir: true, }); ================================================ FILE: examples/plugins/package.json ================================================ { "type": "module", "scripts": { "dev": "nitro dev", "build": "nitro build" }, "devDependencies": { "nitro": "latest" } } ================================================ FILE: examples/plugins/server/plugins/test.ts ================================================ import { definePlugin } from "nitro"; import { useNitroHooks } from "nitro/app"; export default definePlugin((nitroApp) => { const hooks = useNitroHooks(); hooks.hook("response", (event) => { event.headers.set("content-type", "html; charset=utf-8"); }); }); ================================================ FILE: examples/plugins/server.ts ================================================ import { eventHandler } from "h3"; export default eventHandler(() => "

Hello Nitro!

"); ================================================ FILE: examples/plugins/tsconfig.json ================================================ { "extends": "nitro/tsconfig" } ================================================ FILE: examples/plugins/vite.config.ts ================================================ import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ================================================ FILE: examples/renderer/README.md ================================================ Create a custom renderer that generates HTML responses with data from API routes. Use Nitro's internal `fetch` to call routes without network overhead. ## Renderer ```ts [renderer.ts] import { fetch } from "nitro"; export default async function renderer({ url }: { req: Request; url: URL }) { const apiRes = await fetch("/api/hello").then((res) => res.text()); return new Response( /* html */ ` Custom Renderer

Hello from custom renderer!

Current path: ${url.pathname}

API says: ${apiRes}

`, { headers: { "content-type": "text/html; charset=utf-8" } } ); } ``` Nitro auto-detects `renderer.ts` in your project root and uses it for all non-API routes. The renderer function receives the request URL and returns a `Response`. Use `fetch` from `nitro` to call API routes without network overhead—these requests stay in-process. ## API Route ```ts [api/hello.ts] import { defineHandler } from "nitro"; export default defineHandler(() => "Nitro is amazing!"); ``` Define API routes in the `api/` directory. When the renderer calls `fetch("/api/hello")`, this handler runs and returns its response. ================================================ FILE: examples/renderer/api/hello.ts ================================================ import { defineHandler } from "nitro"; export default defineHandler(() => "Nitro is amazing!"); ================================================ FILE: examples/renderer/nitro.config.ts ================================================ import { defineConfig } from "nitro"; export default defineConfig({ serverDir: "./", renderer: { handler: "./renderer" }, }); ================================================ FILE: examples/renderer/package.json ================================================ { "type": "module", "scripts": { "dev": "nitro dev", "build": "nitro build" }, "devDependencies": { "nitro": "latest" } } ================================================ FILE: examples/renderer/renderer.ts ================================================ import { fetch } from "nitro"; export default async function renderer({ url }: { req: Request; url: URL }) { const apiRes = await fetch("/api/hello").then((res) => res.text()); return new Response( /* html */ ` Custom Renderer

Hello from custom renderer!

Current path: ${url.pathname}

API says: ${apiRes}

`, { headers: { "content-type": "text/html; charset=utf-8" } } ); } ================================================ FILE: examples/renderer/tsconfig.json ================================================ { "extends": "nitro/tsconfig" } ================================================ FILE: examples/renderer/vite.config.ts ================================================ import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ================================================ FILE: examples/runtime-config/.env ================================================ # NEVER COMMIT SENSITIVE DATA. THIS IS ONLY FOR DEMO PURPOSES. NITRO_API_KEY=secret-api-key ================================================ FILE: examples/runtime-config/.gitignore ================================================ # THIS IS ONLY FOR DEMO. DO NOT COMMIT SENSITIVE DATA IN REAL PROJECTS !.env ================================================ FILE: examples/runtime-config/README.md ================================================ Runtime config lets you define configuration values that can be overridden by environment variables at runtime. ## Define Config Schema Declare your runtime config with default values in `nitro.config.ts`: ```ts [nitro.config.ts] import { defineConfig } from "nitro"; export default defineConfig({ serverDir: "./", runtimeConfig: { apiKey: "", }, }); ``` ## Access at Runtime Use `useRuntimeConfig` to access configuration values in your handlers: ```ts [server.ts] import { defineHandler } from "nitro"; import { useRuntimeConfig } from "nitro/runtime-config"; export default defineHandler((event) => { const runtimeConfig = useRuntimeConfig(); return { runtimeConfig }; }); ``` ## Environment Variables Override config values via environment variables prefixed with `NITRO_`: ```sh [.env] # NEVER COMMIT SENSITIVE DATA. THIS IS ONLY FOR DEMO PURPOSES. NITRO_API_KEY=secret-api-key ``` ================================================ FILE: examples/runtime-config/nitro.config.ts ================================================ import { defineConfig } from "nitro"; export default defineConfig({ serverDir: "./", runtimeConfig: { apiKey: "", }, }); ================================================ FILE: examples/runtime-config/package.json ================================================ { "type": "module", "scripts": { "dev": "nitro dev", "build": "nitro build" }, "devDependencies": { "nitro": "latest" } } ================================================ FILE: examples/runtime-config/server.ts ================================================ import { defineHandler } from "nitro"; import { useRuntimeConfig } from "nitro/runtime-config"; export default defineHandler((event) => { const runtimeConfig = useRuntimeConfig(); return { runtimeConfig }; }); ================================================ FILE: examples/runtime-config/tsconfig.json ================================================ { "extends": "nitro/tsconfig" } ================================================ FILE: examples/runtime-config/vite.config.ts ================================================ import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ================================================ FILE: examples/server-fetch/README.md ================================================ When you need one route to call another, use Nitro's `fetch` function instead of the global fetch. It makes internal requests that stay in-process, avoiding network round-trips. The request never leaves the server. ## Main Route ```ts [routes/index.ts] import { defineHandler } from "nitro"; import { fetch } from "nitro"; export default defineHandler(() => fetch("/hello")); ``` The index route imports `fetch` from `nitro` (not the global fetch) and calls the `/hello` route. This request is handled internally without going through the network stack. ## Internal API Route ```ts [routes/hello.ts] import { defineHandler } from "nitro"; export default defineHandler(() => "Hello!"); ``` A simple route that returns "Hello!". When the index route calls `fetch("/hello")`, this handler runs and its response is returned directly. ================================================ FILE: examples/server-fetch/nitro.config.ts ================================================ import { defineConfig, serverFetch } from "nitro"; export default defineConfig({ serverDir: "./", hooks: { "dev:start": async () => { const res = await serverFetch("/hello"); const text = await res.text(); console.log("Fetched /hello in nitro module:", res.status, text); }, }, }); ================================================ FILE: examples/server-fetch/package.json ================================================ { "type": "module", "scripts": { "dev": "nitro dev", "build": "nitro build" }, "devDependencies": { "nitro": "latest" } } ================================================ FILE: examples/server-fetch/routes/hello.ts ================================================ import { defineHandler } from "nitro"; export default defineHandler(() => "Hello!"); ================================================ FILE: examples/server-fetch/routes/index.ts ================================================ import { defineHandler } from "nitro"; import { fetch } from "nitro"; export default defineHandler(() => fetch("/hello")); ================================================ FILE: examples/server-fetch/tsconfig.json ================================================ { "extends": "nitro/tsconfig" } ================================================ FILE: examples/server-fetch/vite.config.ts ================================================ import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ================================================ FILE: examples/shiki/README.md ================================================ Use Shiki for syntax highlighting with TextMate grammars. This example highlights code on the server using Nitro's server scripts feature, which runs JavaScript inside HTML files before sending the response. ## API Route ```ts [api/highlight.ts] import { createHighlighterCore } from "shiki/core"; import { createOnigurumaEngine } from "shiki/engine/oniguruma"; const highlighter = await createHighlighterCore({ engine: createOnigurumaEngine(import("shiki/wasm")), themes: [await import("shiki/themes/vitesse-dark.mjs")], langs: [await import("shiki/langs/ts.mjs")], }); export default async ({ req }: { req: Request }) => { const code = await req.text(); const html = await highlighter.codeToHtml(code, { lang: "ts", theme: "vitesse-dark", }); return new Response(html, { headers: { "Content-Type": "text/html; charset=utf-8" }, }); }; ``` Create a Shiki highlighter with the Vitesse Dark theme and TypeScript language support. When the API receives a POST request, it reads the code from the request body and returns highlighted HTML. ## Server-Side Rendering ```html [index.html] Hello World Snippet
JavaScript
{{{ hl(`console.log("💚 Simple is beautiful!");`) }}}
``` The `
{{{ hl(`console.log("💚 Simple is beautiful!");`) }}}
================================================ FILE: examples/shiki/nitro.config.ts ================================================ import { defineConfig } from "nitro"; export default defineConfig({ serverDir: "./", }); ================================================ FILE: examples/shiki/package.json ================================================ { "type": "module", "scripts": { "dev": "vite dev", "build": "vite build" }, "devDependencies": { "nitro": "latest", "shiki": "^3.23.0" } } ================================================ FILE: examples/shiki/styles.css ================================================ html, body { height: 100%; margin: 0; } body { display: flex; align-items: center; justify-content: center; background: #f6f8fa; font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif; } .card { text-align: left; background: #0b1220; color: #e6edf3; padding: 1rem; border-radius: 8px; box-shadow: 0 8px 24px rgba(2, 6, 23, 0.2); max-width: 90%; width: 520px; } .label { font-size: 12px; color: #9aa7b2; margin-bottom: 8px; } pre { margin: 0; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, "Courier New", monospace; font-size: 14px; background: transparent; white-space: pre; overflow: auto; } ================================================ FILE: examples/shiki/tsconfig.json ================================================ { "extends": "nitro/tsconfig" } ================================================ FILE: examples/shiki/vite.config.ts ================================================ import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()], }); ================================================ FILE: examples/virtual-routes/README.md ================================================ Virtual routes let you define handlers as strings in your config instead of creating separate files. This is useful when generating routes dynamically, building plugins, or keeping simple routes inline. ## Configuration ```ts [nitro.config.ts] import { defineConfig } from "nitro"; export default defineConfig({ routes: { "/": "#virtual-route", }, virtual: { "#virtual-route": () => /* js */ `export default () => new Response("Hello from virtual entry!")`, }, }); ``` The `routes` option maps URL paths to virtual module identifiers (prefixed with `#`). The `virtual` option defines the module content as a string or function returning a string. At build time, Nitro resolves these virtual modules to actual handlers. There are no route files in this project. The entire handler is defined inline in the config, and Nitro generates the route at build time. ================================================ FILE: examples/virtual-routes/nitro.config.ts ================================================ import { defineConfig } from "nitro"; export default defineConfig({ routes: { "/": "#virtual-route", }, virtual: { "#virtual-route": () => /* js */ `export default () => new Response("Hello from virtual entry!")`, }, }); ================================================ FILE: examples/virtual-routes/package.json ================================================ { "type": "module", "scripts": { "build": "nitro build", "dev": "nitro dev", "preview": "node .output/server/index.mjs" }, "devDependencies": { "nitro": "latest" } } ================================================ FILE: examples/virtual-routes/tsconfig.json ================================================ { "extends": "nitro/tsconfig" } ================================================ FILE: examples/virtual-routes/vite.config.ts ================================================ import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ================================================ FILE: examples/vite-nitro-plugin/README.md ================================================ Instead of using a separate `nitro.config.ts`, you can configure Nitro directly in your Vite config. This gives you access to Nitro's setup hook where you can register routes and virtual modules programmatically. ## Vite Configuration ```js [vite.config.mjs] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [ nitro(), { name: "my-nitro-plugin", nitro: { setup: (nitro) => { nitro.options.routes["/"] = "#virtual-by-plugin"; nitro.options.virtual["#virtual-by-plugin"] = `export default () => new Response("Hello from virtual entry!")`; }, }, }, ], }); ``` The config adds two plugins: the `nitro()` plugin and a custom plugin that uses the `nitro.setup` hook. Inside the setup function, you have access to Nitro's options object. This example registers a virtual route at `/` that maps to a virtual module `#virtual-by-plugin`, then defines that module inline. ================================================ FILE: examples/vite-nitro-plugin/package.json ================================================ { "type": "module", "scripts": { "build": "vite build", "preview": "vite preview", "dev": "vite dev" }, "devDependencies": { "nitro": "latest", "vite": "latest" } } ================================================ FILE: examples/vite-nitro-plugin/tsconfig.json ================================================ { "extends": "nitro/tsconfig" } ================================================ FILE: examples/vite-nitro-plugin/vite.config.mjs ================================================ import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [ nitro(), { name: "my-nitro-plugin", nitro: { setup: (nitro) => { nitro.options.routes["/"] = "#virtual-by-plugin"; nitro.options.virtual["#virtual-by-plugin"] = `export default () => new Response("Hello from virtual entry!")`; }, }, }, ], }); ================================================ FILE: examples/vite-rsc/.gitignore ================================================ node_modules dist ================================================ FILE: examples/vite-rsc/README.md ================================================ This example demonstrates React Server Components (RSC) using Vite's experimental RSC plugin with Nitro. It includes server components, client components, server actions, and streaming SSR. ## Overview 1. **SSR Entry** handles incoming requests and renders React components to HTML 2. **Root Component** defines the page structure as a server component 3. **Client Components** use the `"use client"` directive for interactive parts ## 1. SSR Entry ```tsx [app/framework/entry.ssr.tsx] import { createFromReadableStream } from "@vitejs/plugin-rsc/ssr"; import React from "react"; import type { ReactFormState } from "react-dom/client"; import { renderToReadableStream } from "react-dom/server.edge"; import { injectRSCPayload } from "rsc-html-stream/server"; import type { RscPayload } from "./entry.rsc"; export default { fetch: async (request: Request) => { const rscEntryModule = await import.meta.viteRsc.loadModule( "rsc", "index" ); return rscEntryModule.default(request); }, }; export async function renderHTML( rscStream: ReadableStream, options: { formState?: ReactFormState; nonce?: string; debugNoJS?: boolean; } ): Promise<{ stream: ReadableStream; status?: number }> { // Duplicate one RSC stream into two. // - one for SSR (ReactClient.createFromReadableStream below) // - another for browser hydration payload by injecting . const [rscStream1, rscStream2] = rscStream.tee(); // Deserialize RSC stream back to React VDOM let payload: Promise | undefined; function SsrRoot() { // Deserialization needs to be kicked off inside ReactDOMServer context // for ReactDOMServer preinit/preloading to work payload ??= createFromReadableStream(rscStream1); return React.use(payload).root; } // Render HTML (traditional SSR) const bootstrapScriptContent = await import.meta.viteRsc.loadBootstrapScriptContent("index"); let htmlStream: ReadableStream; let status: number | undefined; try { htmlStream = await renderToReadableStream(, { bootstrapScriptContent: options?.debugNoJS ? undefined : bootstrapScriptContent, nonce: options?.nonce, formState: options?.formState, }); } catch { // fallback to render an empty shell and run pure CSR on browser, // which can replay server component error and trigger error boundary. status = 500; htmlStream = await renderToReadableStream( , { bootstrapScriptContent: `self.__NO_HYDRATE=1;` + (options?.debugNoJS ? "" : bootstrapScriptContent), nonce: options?.nonce, } ); } let responseStream: ReadableStream = htmlStream; if (!options?.debugNoJS) { // Initial RSC stream is injected in HTML stream as // using utility made by devongovett https://github.com/devongovett/rsc-html-stream responseStream = responseStream.pipeThrough( injectRSCPayload(rscStream2, { nonce: options?.nonce, }) ); } return { stream: responseStream, status }; } ``` The SSR entry handles the rendering pipeline. It loads the RSC entry module, duplicates the RSC stream (one for SSR, one for hydration), deserializes the stream back to React VDOM, and renders it to HTML. The RSC payload is injected into the HTML for client hydration. ## 2. Root Server Component ```tsx [app/root.tsx] import "./index.css"; // css import is automatically injected in exported server components import viteLogo from "./assets/vite.svg"; import { getServerCounter, updateServerCounter } from "./action.tsx"; import reactLogo from "./assets/react.svg"; import nitroLogo from "./assets/nitro.svg"; import { ClientCounter } from "./client.tsx"; export function Root(props: { url: URL }) { return ( {/* eslint-disable-next-line unicorn/text-encoding-identifier-case */} Nitro + Vite + RSC ); } function App(props: { url: URL }) { return (

Vite + RSC + Nitro

Request URL: {props.url?.href}
  • Edit src/client.tsx to test client HMR.
  • Edit src/root.tsx to test server HMR.
  • Visit{" "} _.rsc {" "} to view RSC stream payload.
  • Visit{" "} ?__nojs {" "} to test server action without js enabled.
); } ``` Server components run only on the server. They can import CSS directly, use server-side data, and call server actions. The `ClientCounter` component is imported but runs on the client because it has the `"use client"` directive. ## 3. Client Component ```tsx [app/client.tsx] "use client"; import React from "react"; export function ClientCounter() { const [count, setCount] = React.useState(0); return ; } ``` The `"use client"` directive marks this as a client component. It hydrates on the browser and handles interactive state. Server components can import and render client components, but client components cannot import server components. ================================================ FILE: examples/vite-rsc/app/action.tsx ================================================ "use server"; let serverCounter = 0; export async function getServerCounter() { return serverCounter; } export async function updateServerCounter(change: number) { serverCounter += change; } ================================================ FILE: examples/vite-rsc/app/client.tsx ================================================ "use client"; import React from "react"; export function ClientCounter() { const [count, setCount] = React.useState(0); return ; } ================================================ FILE: examples/vite-rsc/app/framework/entry.browser.tsx ================================================ import { createFromReadableStream, createFromFetch, setServerCallback, createTemporaryReferenceSet, encodeReply, } from "@vitejs/plugin-rsc/browser"; import React from "react"; import { createRoot, hydrateRoot } from "react-dom/client"; import { rscStream } from "rsc-html-stream/client"; import { GlobalErrorBoundary } from "./error-boundary"; import type { RscPayload } from "./entry.rsc"; import { createRscRenderRequest } from "./request"; async function main() { // Stash `setPayload` function to trigger re-rendering // from outside of `BrowserRoot` component (e.g. server function call, navigation, hmr) let setPayload: (v: RscPayload) => void; // Deserialize RSC stream back to React VDOM for CSR const initialPayload = await createFromReadableStream( // Initial RSC stream is injected in SSR stream as rscStream ); // Browser root component to (re-)render RSC payload as state function BrowserRoot() { const [payload, setPayload_] = React.useState(initialPayload); React.useEffect(() => { setPayload = (v) => React.startTransition(() => setPayload_(v)); }, [setPayload_]); // Re-fetch/render on client side navigation React.useEffect(() => { return listenNavigation(() => fetchRscPayload()); }, []); return payload.root; } // Re-fetch RSC and trigger re-rendering async function fetchRscPayload() { const renderRequest = createRscRenderRequest(globalThis.location.href); const payload = await createFromFetch(fetch(renderRequest)); setPayload(payload); } // Register a handler which will be internally called by React // on server function request after hydration. setServerCallback(async (id, args) => { const temporaryReferences = createTemporaryReferenceSet(); const renderRequest = createRscRenderRequest(globalThis.location.href, { id, body: await encodeReply(args, { temporaryReferences }), }); const payload = await createFromFetch(fetch(renderRequest), { temporaryReferences, }); setPayload(payload); const { ok, data } = payload.returnValue!; if (!ok) throw data; return data; }); // Hydration const browserRoot = ( ); if ("__NO_HYDRATE" in globalThis) { createRoot(document).render(browserRoot); } else { hydrateRoot(document, browserRoot, { formState: initialPayload.formState, }); } // Implement server HMR by triggering re-fetch/render of RSC upon server code change if (import.meta.hot) { import.meta.hot.on("rsc:update", () => { fetchRscPayload(); }); } } // A little helper to setup events interception for client side navigation function listenNavigation(onNavigation: () => void) { globalThis.addEventListener("popstate", onNavigation); const oldPushState = globalThis.history.pushState; globalThis.history.pushState = function (...args) { const res = oldPushState.apply(this, args); onNavigation(); return res; }; const oldReplaceState = globalThis.history.replaceState; globalThis.history.replaceState = function (...args) { const res = oldReplaceState.apply(this, args); onNavigation(); return res; }; function onClick(e: MouseEvent) { const link = (e.target as Element).closest("a"); if ( link && link instanceof HTMLAnchorElement && link.href && (!link.target || link.target === "_self") && link.origin === location.origin && !link.hasAttribute("download") && e.button === 0 && // left clicks only !e.metaKey && // open in new tab (mac) !e.ctrlKey && // open in new tab (windows) !e.altKey && // download !e.shiftKey && !e.defaultPrevented ) { e.preventDefault(); history.pushState(null, "", link.href); } } document.addEventListener("click", onClick); return () => { document.removeEventListener("click", onClick); globalThis.removeEventListener("popstate", onNavigation); globalThis.history.pushState = oldPushState; globalThis.history.replaceState = oldReplaceState; }; } // eslint-disable-next-line unicorn/prefer-top-level-await main(); ================================================ FILE: examples/vite-rsc/app/framework/entry.rsc.tsx ================================================ import { renderToReadableStream, createTemporaryReferenceSet, decodeReply, loadServerAction, decodeAction, decodeFormState, } from "@vitejs/plugin-rsc/rsc"; import type { ReactFormState } from "react-dom/client"; import { Root } from "../root.tsx"; import { parseRenderRequest } from "./request.tsx"; // The schema of payload which is serialized into RSC stream on rsc environment // and deserialized on ssr/client environments. export type RscPayload = { // this demo renders/serializes/deserializes entire root html element // but this mechanism can be changed to render/fetch different parts of components // based on your own route conventions. root: React.ReactNode; // Server action return value of non-progressive enhancement case returnValue?: { ok: boolean; data: unknown }; // Server action form state (e.g. useActionState) of progressive enhancement case formState?: ReactFormState; }; // The plugin by default assumes `rsc` entry having default export of request handler. // however, how server entries are executed can be customized by registering own server handler. export default async function handler(request: Request): Promise { // Differentiate RSC, SSR, action, etc. const renderRequest = parseRenderRequest(request); request = renderRequest.request; // Handle server function request let returnValue: RscPayload["returnValue"] | undefined; let formState: ReactFormState | undefined; let temporaryReferences: unknown | undefined; let actionStatus: number | undefined; if (renderRequest.isAction === true) { if (renderRequest.actionId) { // Action is called via `ReactClient.setServerCallback`. const contentType = request.headers.get("content-type"); const body = contentType?.startsWith("multipart/form-data") ? await request.formData() : await request.text(); temporaryReferences = createTemporaryReferenceSet(); const args = await decodeReply(body, { temporaryReferences }); const action = await loadServerAction(renderRequest.actionId); try { // eslint-disable-next-line prefer-spread const data = await action.apply(null, args); returnValue = { ok: true, data }; } catch (error_) { returnValue = { ok: false, data: error_ }; actionStatus = 500; } } else { // Otherwise server function is called via `
` // before hydration (e.g. when JavaScript is disabled). // aka progressive enhancement. const formData = await request.formData(); const decodedAction = await decodeAction(formData); try { const result = await decodedAction(); formState = await decodeFormState(result, formData); } catch { // there's no single general obvious way to surface this error, // so explicitly return classic 500 response. return new Response("Internal Server Error: server action failed", { status: 500, }); } } } // Serialization from React VDOM tree to RSC stream. // We render RSC stream after handling server function request // so that new render reflects updated state from server function call // to achieve single round trip to mutate and fetch from server. const rscPayload: RscPayload = { root: , formState, returnValue, }; const rscOptions = { temporaryReferences }; const rscStream = renderToReadableStream(rscPayload, rscOptions); // Respond RSC stream without HTML rendering as decided by `RenderRequest` if (renderRequest.isRsc) { return new Response(rscStream, { status: actionStatus, headers: { "content-type": "text/x-component;charset=utf-8", }, }); } // Delegate to SSR environment for HTML rendering. // The plugin provides `loadModule` helper to allow loading SSR environment entry module // in RSC environment. however this can be customized by implementing own runtime communication // e.g. `@cloudflare/vite-plugin`'s service binding. const ssrEntryModule = await import.meta.viteRsc.loadModule( "ssr", "index" ); const ssrResult = await ssrEntryModule.renderHTML(rscStream, { formState, // Allow quick simulation of JavaScript disabled browser debugNoJS: renderRequest.url.searchParams.has("__nojs"), }); // Respond HTML return new Response(ssrResult.stream, { status: ssrResult.status, headers: { "Content-Type": "text/html", }, }); } if (import.meta.hot) { import.meta.hot.accept(); } ================================================ FILE: examples/vite-rsc/app/framework/entry.ssr.tsx ================================================ import { createFromReadableStream } from "@vitejs/plugin-rsc/ssr"; import React from "react"; import type { ReactFormState } from "react-dom/client"; import { renderToReadableStream } from "react-dom/server.edge"; import { injectRSCPayload } from "rsc-html-stream/server"; import type { RscPayload } from "./entry.rsc"; export default { fetch: async (request: Request) => { const rscEntryModule = await import.meta.viteRsc.loadModule( "rsc", "index" ); return rscEntryModule.default(request); }, }; export async function renderHTML( rscStream: ReadableStream, options: { formState?: ReactFormState; nonce?: string; debugNoJS?: boolean; } ): Promise<{ stream: ReadableStream; status?: number }> { // Duplicate one RSC stream into two. // - one for SSR (ReactClient.createFromReadableStream below) // - another for browser hydration payload by injecting . const [rscStream1, rscStream2] = rscStream.tee(); // Deserialize RSC stream back to React VDOM let payload: Promise | undefined; function SsrRoot() { // Deserialization needs to be kicked off inside ReactDOMServer context // for ReactDOMServer preinit/preloading to work payload ??= createFromReadableStream(rscStream1); return React.use(payload).root; } // Render HTML (traditional SSR) const bootstrapScriptContent = await import.meta.viteRsc.loadBootstrapScriptContent("index"); let htmlStream: ReadableStream; let status: number | undefined; try { htmlStream = await renderToReadableStream(, { bootstrapScriptContent: options?.debugNoJS ? undefined : bootstrapScriptContent, nonce: options?.nonce, formState: options?.formState, }); } catch { // fallback to render an empty shell and run pure CSR on browser, // which can replay server component error and trigger error boundary. status = 500; htmlStream = await renderToReadableStream( , { bootstrapScriptContent: `self.__NO_HYDRATE=1;` + (options?.debugNoJS ? "" : bootstrapScriptContent), nonce: options?.nonce, } ); } let responseStream: ReadableStream = htmlStream; if (!options?.debugNoJS) { // Initial RSC stream is injected in HTML stream as // using utility made by devongovett https://github.com/devongovett/rsc-html-stream responseStream = responseStream.pipeThrough( injectRSCPayload(rscStream2, { nonce: options?.nonce, }) ); } return { stream: responseStream, status }; } ================================================ FILE: examples/vite-rsc/app/framework/error-boundary.tsx ================================================ "use client"; import React from "react"; // Minimal ErrorBoundary example to handle errors globally on browser export function GlobalErrorBoundary(props: { children?: React.ReactNode }) { return {props.children}; } // https://github.com/vercel/next.js/blob/33f8428f7066bf8b2ec61f025427ceb2a54c4bdf/packages/next/src/client/components/error-boundary.tsx // https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary class ErrorBoundary extends React.Component<{ children?: React.ReactNode; errorComponent: React.FC<{ error: Error; reset: () => void; }>; }> { override state: { error?: Error } = {}; static getDerivedStateFromError(error: Error) { return { error }; } reset = () => { this.setState({ error: null }); }; override render() { const error = this.state.error; if (error) { return ; } return this.props.children; } } // https://github.com/vercel/next.js/blob/677c9b372faef680d17e9ba224743f44e1107661/packages/next/src/build/webpack/loaders/next-app-loader.ts#L73 // https://github.com/vercel/next.js/blob/677c9b372faef680d17e9ba224743f44e1107661/packages/next/src/client/components/error-boundary.tsx#L145 function DefaultGlobalErrorPage(props: { error: Error; reset: () => void }) { return ( Unexpected Error

Caught an unexpected error

          Error:{" "}
          {import.meta.env.DEV && "message" in props.error ? props.error.message : "(Unknown)"}
        
); } ================================================ FILE: examples/vite-rsc/app/framework/request.tsx ================================================ // Framework conventions (arbitrary choices for this demo): // - Use `_.rsc` URL suffix to differentiate RSC requests from SSR requests // - Use `x-rsc-action` header to pass server action ID const URL_POSTFIX = "_.rsc"; const HEADER_ACTION_ID = "x-rsc-action"; // Parsed request information used to route between RSC/SSR rendering and action handling. // Created by parseRenderRequest() from incoming HTTP requests. type RenderRequest = { isRsc: boolean; // true if request should return RSC payload (via _.rsc suffix) isAction: boolean; // true if this is a server action call (POST request) actionId?: string; // server action ID from x-rsc-action header request: Request; // normalized Request with _.rsc suffix removed from URL url: URL; // normalized URL with _.rsc suffix removed }; export function createRscRenderRequest( urlString: string, action?: { id: string; body: BodyInit } ): Request { const url = new URL(urlString); url.pathname += URL_POSTFIX; const headers = new Headers(); if (action) { headers.set(HEADER_ACTION_ID, action.id); } return new Request(url.toString(), { method: action ? "POST" : "GET", headers, body: action?.body, }); } export function parseRenderRequest(request: Request): RenderRequest { const url = new URL(request.url); const isAction = request.method === "POST"; if (url.pathname.endsWith(URL_POSTFIX)) { url.pathname = url.pathname.slice(0, -URL_POSTFIX.length); const actionId = request.headers.get(HEADER_ACTION_ID) || undefined; if (request.method === "POST" && !actionId) { throw new Error("Missing action id header for RSC action request"); } return { isRsc: true, isAction, actionId, request: new Request(url, request), url, }; } else { return { isRsc: false, isAction, request, url, }; } } ================================================ FILE: examples/vite-rsc/app/index.css ================================================ :root { font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; line-height: 1.5; font-weight: 400; color-scheme: light dark; color: rgba(255, 255, 255, 0.87); background-color: #242424; font-synthesis: none; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } a { font-weight: 500; color: #646cff; text-decoration: inherit; } a:hover { color: #535bf2; } body { margin: 0; display: flex; place-items: center; min-width: 320px; min-height: 100vh; } h1 { font-size: 3.2em; line-height: 1.1; } button { border-radius: 8px; border: 1px solid transparent; padding: 0.6em 1.2em; font-size: 1em; font-weight: 500; font-family: inherit; background-color: #1a1a1a; cursor: pointer; transition: border-color 0.25s; } button:hover { border-color: #646cff; } button:focus, button:focus-visible { outline: 4px auto -webkit-focus-ring-color; } @media (prefers-color-scheme: light) { :root { color: #213547; background-color: #ffffff; } a:hover { color: #747bff; } button { background-color: #f9f9f9; } } #root { max-width: 1280px; margin: 0 auto; padding: 2rem; text-align: center; } .logo { height: 6em; padding: 1.5em; will-change: filter; transition: filter 300ms; } .logo:hover { filter: drop-shadow(0 0 2em #646cffaa); } .logo.react:hover { filter: drop-shadow(0 0 2em #61dafbaa); } @keyframes logo-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } @media (prefers-reduced-motion: no-preference) { a:nth-of-type(2) .logo { animation: logo-spin infinite 20s linear; } } .card { padding: 1rem; } .read-the-docs { color: #888; text-align: left; } ================================================ FILE: examples/vite-rsc/app/root.tsx ================================================ import "./index.css"; // css import is automatically injected in exported server components import viteLogo from "./assets/vite.svg"; import { getServerCounter, updateServerCounter } from "./action.tsx"; import reactLogo from "./assets/react.svg"; import nitroLogo from "./assets/nitro.svg"; import { ClientCounter } from "./client.tsx"; export function Root(props: { url: URL }) { return ( {/* eslint-disable-next-line unicorn/text-encoding-identifier-case */} Nitro + Vite + RSC ); } function App(props: { url: URL }) { return (

Vite + RSC + Nitro

Request URL: {props.url?.href}
  • Edit src/client.tsx to test client HMR.
  • Edit src/root.tsx to test server HMR.
  • Visit{" "} _.rsc {" "} to view RSC stream payload.
  • Visit{" "} ?__nojs {" "} to test server action without js enabled.
); } ================================================ FILE: examples/vite-rsc/package.json ================================================ { "name": "@vitejs/plugin-rsc-examples-starter", "version": "0.0.0", "private": true, "license": "MIT", "type": "module", "scripts": { "dev": "vite", "build": "vite build", "preview": "vite preview" }, "dependencies": { "react": "^19.2.4", "react-dom": "^19.2.4" }, "devDependencies": { "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^5.2.0", "@vitejs/plugin-rsc": "^0.5.21", "nitro": "latest", "rsc-html-stream": "^0.0.7", "vite": "latest" } } ================================================ FILE: examples/vite-rsc/tsconfig.json ================================================ { "extends": "nitro/tsconfig", "compilerOptions": { "lib": ["ESNext", "DOM", "DOM.Iterable"], "types": ["vite/client", "@vitejs/plugin-rsc/types"], "jsx": "react-jsx" } } ================================================ FILE: examples/vite-rsc/vite.config.ts ================================================ import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; import rsc from "@vitejs/plugin-rsc"; import react from "@vitejs/plugin-react"; export default defineConfig({ plugins: [ nitro(), rsc({ serverHandler: false, entries: { ssr: "./app/framework/entry.ssr.tsx", rsc: "./app/framework/entry.rsc.tsx", }, }), react(), ], environments: { client: { build: { rollupOptions: { input: { index: "./app/framework/entry.browser.tsx" }, }, }, }, }, }); ================================================ FILE: examples/vite-ssr-html/README.md ================================================ This example renders an HTML template with server-side data and streams the response word by word. It demonstrates how to use Nitro's Vite SSR integration without a framework. ## Overview 1. **Add the Nitro Vite plugin** to enable SSR 2. **Create an HTML template** with a `` comment where server content goes 3. **Create a server entry** that fetches data and returns a stream 4. **Add API routes** for server-side data ## How It Works The `index.html` file contains an `` comment that marks where server-rendered content will be inserted. Nitro replaces this comment with the output from your server entry. The server entry exports an object with a `fetch` method. It calls the `/quote` API route using Nitro's internal fetch, then returns a `ReadableStream` that emits the quote text word by word with a 50ms delay between each word. The quote route fetches a JSON file of quotes from GitHub, caches the result, and returns a random quote. The server entry calls this route to get content for the page. ================================================ FILE: examples/vite-ssr-html/app/entry-server.ts ================================================ import { fetch } from "nitro"; export default { async fetch() { const quote = (await fetch("/quote").then((res) => res.json())) as { text: string; }; return tokenizedStream(quote.text, 50); }, }; function tokenizedStream(text: string, delay: number): ReadableStream { const tokens = text.split(" "); return new ReadableStream({ start(controller) { let index = 0; function push() { if (index < tokens.length) { const word = tokens[index++] + (index < tokens.length ? " " : ""); controller.enqueue(new TextEncoder().encode(word)); setTimeout(push, delay); } else { controller.close(); } } push(); }, }); } ================================================ FILE: examples/vite-ssr-html/index.html ================================================ Nitro Quotes
Powered by Vite and Nitro v3.
================================================ FILE: examples/vite-ssr-html/package.json ================================================ { "type": "module", "scripts": { "build": "vite build", "dev": "vite dev", "preview": "vite preview" }, "devDependencies": { "@tailwindcss/vite": "^4.2.2", "nitro": "latest", "tailwindcss": "^4.2.2", "vite": "latest" } } ================================================ FILE: examples/vite-ssr-html/routes/quote.ts ================================================ const QUOTES_URL = "https://github.com/JamesFT/Database-Quotes-JSON/raw/refs/heads/master/quotes.json"; let _quotes: Promise | undefined; function getQuotes() { return (_quotes ??= fetch(QUOTES_URL).then((res) => res.json())) as Promise< { quoteText: string; quoteAuthor: string }[] >; } export default async function quotesHandler() { const quotes = await getQuotes(); const randomQuote = quotes[Math.floor(Math.random() * quotes.length)]; return Response.json({ text: randomQuote.quoteText, author: randomQuote.quoteAuthor, }); } ================================================ FILE: examples/vite-ssr-html/tsconfig.json ================================================ { "extends": "nitro/tsconfig" } ================================================ FILE: examples/vite-ssr-html/vite.config.ts ================================================ import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; import tailwindcss from "@tailwindcss/vite"; export default defineConfig({ plugins: [ nitro({ serverDir: "./", }), tailwindcss(), ], }); ================================================ FILE: examples/vite-ssr-preact/README.md ================================================ Set up server-side rendering (SSR) with Preact, Vite, and Nitro. This setup enables streaming HTML responses, automatic asset management, and client hydration. ## Overview 1. Add the Nitro Vite plugin to your Vite config 2. Configure client and server entry points 3. Create a server entry that renders your app to HTML 4. Create a client entry that hydrates the server-rendered HTML ## 1. Configure Vite Add the Nitro and Preact plugins to your Vite config. Define the `client` environment with your client entry point: ```js [vite.config.mjs] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; import preact from "@preact/preset-vite"; export default defineConfig({ plugins: [nitro(), preact()], environments: { client: { build: { rollupOptions: { input: "./src/entry-client.tsx", }, }, }, }, }); ``` The `environments.client` configuration tells Vite which file to use as the browser entry point. Nitro automatically detects the server entry from files named `entry-server` or `server` in common directories. ## 2. Create the App Component Create a shared Preact component that runs on both server and client: ```tsx [src/app.tsx] import { useState } from "preact/hooks"; export function App() { const [count, setCount] = useState(0); return ; } ``` ## 3. Create the Server Entry The server entry renders your Preact app to a streaming HTML response using `preact-render-to-string/stream`: ```tsx [src/entry-server.tsx] import "./styles.css"; import { renderToReadableStream } from "preact-render-to-string/stream"; import { App } from "./app.jsx"; import clientAssets from "./entry-client?assets=client"; import serverAssets from "./entry-server?assets=ssr"; export default { async fetch(request: Request) { const url = new URL(request.url); const htmlStream = renderToReadableStream(); return new Response(htmlStream, { headers: { "Content-Type": "text/html;charset=utf-8" }, }); }, }; function Root(props: { url: URL }) { const assets = clientAssets.merge(serverAssets); return ( {assets.css.map((attr: any) => ( ))} {assets.js.map((attr: any) => ( ))} ``` ## 3. Create the App Entry Create the main entry that initializes TanStack Router: ```tsx [src/main.tsx] import { StrictMode } from "react"; import ReactDOM from "react-dom/client"; import { RouterProvider, createRouter } from "@tanstack/react-router"; // Import the generated route tree import { routeTree } from "./routeTree.gen.ts"; // Create a new router instance const router = createRouter({ routeTree }); // Register the router instance for type safety declare module "@tanstack/react-router" { interface Register { router: typeof router; } } // Render the app const rootElement = document.querySelector("#root")!; if (!rootElement.innerHTML) { const root = ReactDOM.createRoot(rootElement); root.render( ); } ``` The `routeTree.gen.ts` file is auto-generated from your `routes/` directory structure. The `Register` interface declaration provides full type inference for route paths and params. The `!rootElement.innerHTML` check prevents re-rendering during hot module replacement. ## 4. Create the Root Route The root route (`__root.tsx`) defines your app's layout: ```tsx [src/routes/__root.tsx] import { createRootRoute, Link, Outlet } from "@tanstack/react-router"; import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"; const RootLayout = () => ( <>
Home

); export const Route = createRootRoute({ component: RootLayout }); ``` Use `Link` for type-safe navigation with active state styling. The `Outlet` component renders child routes. Include `TanStackRouterDevtools` for development tools (automatically removed in production). ## 5. Create Page Routes Page routes use `createFileRoute` and can include loaders: ```tsx [src/routes/index.tsx] import { createFileRoute } from "@tanstack/react-router"; export const Route = createFileRoute("/")({ loader: async () => { const r = await fetch("/api/hello"); return r.json(); }, component: Index, }); function Index() { const r = Route.useLoaderData(); return (

{JSON.stringify(r)}

); } ``` Fetch data before rendering with the `loader` function—data is available via `Route.useLoaderData()`. File paths determine URL paths: `routes/index.tsx` maps to `/`, `routes/about.tsx` to `/about`, and `routes/users/$id.tsx` to `/users/:id`. ================================================ FILE: examples/vite-ssr-tsr-react/index.html ================================================ Nitro + TanStack Router + React
================================================ FILE: examples/vite-ssr-tsr-react/package.json ================================================ { "type": "module", "scripts": { "build": "vite build", "dev": "vite dev", "preview": "vite preview" }, "devDependencies": { "@tanstack/react-router": "^1.168.1", "@tanstack/react-router-devtools": "^1.166.10", "@tanstack/router-plugin": "^1.167.1", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^5.2.0", "nitro": "latest", "react": "^19.2.4", "react-dom": "^19.2.4", "vite": "latest" } } ================================================ FILE: examples/vite-ssr-tsr-react/src/assets/main.css ================================================ :root { font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; line-height: 1.5; font-weight: 400; color-scheme: light dark; color: rgba(255, 255, 255, 0.87); background-color: #242424; font-synthesis: none; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } a { font-weight: 500; color: #ff2056; text-decoration: inherit; } a:hover { color: #ff637e; } body { margin: 0; display: flex; flex-direction: column; place-items: center; justify-content: center; min-width: 320px; min-height: 100vh; } h1 { font-size: 3.2em; line-height: 1.1; } #app { max-width: 1280px; margin: 0 auto; padding: 2rem; text-align: center; } .logo { height: 6em; padding: 1.5em; will-change: filter; transition: filter 300ms; transition: transform 300ms; } .logo:hover { transform: scale(1.1); } .card { padding: 2em; } .read-the-docs { color: #888; } button { border-radius: 8px; border: 1px solid transparent; padding: 0.6em 1.2em; font-size: 1em; font-weight: 500; font-family: inherit; background-color: #1a1a1a; cursor: pointer; transition: border-color 0.25s; } button:hover { border-color: #646cff; } button:focus, button:focus-visible { outline: 4px auto -webkit-focus-ring-color; } @media (prefers-color-scheme: light) { :root { color: #213547; background-color: #ffffff; } a:hover { color: #747bff; } button { background-color: #f9f9f9; } } ================================================ FILE: examples/vite-ssr-tsr-react/src/main.tsx ================================================ import { StrictMode } from "react"; import ReactDOM from "react-dom/client"; import { RouterProvider, createRouter } from "@tanstack/react-router"; // Import the generated route tree import { routeTree } from "./routeTree.gen.ts"; // Create a new router instance const router = createRouter({ routeTree }); // Register the router instance for type safety declare module "@tanstack/react-router" { interface Register { router: typeof router; } } // Render the app const rootElement = document.querySelector("#root")!; if (!rootElement.innerHTML) { const root = ReactDOM.createRoot(rootElement); root.render( ); } ================================================ FILE: examples/vite-ssr-tsr-react/src/routeTree.gen.ts ================================================ /* eslint-disable */ // @ts-nocheck // noinspection JSUnusedGlobalSymbols // This file was automatically generated by TanStack Router. // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. import { Route as rootRouteImport } from './routes/__root' import { Route as IndexRouteImport } from './routes/index' const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute } export interface FileRoutesByTo { '/': typeof IndexRoute } export interface FileRoutesById { __root__: typeof rootRouteImport '/': typeof IndexRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath fullPaths: '/' fileRoutesByTo: FileRoutesByTo to: '/' id: '__root__' | '/' fileRoutesById: FileRoutesById } export interface RootRouteChildren { IndexRoute: typeof IndexRoute } declare module '@tanstack/react-router' { interface FileRoutesByPath { '/': { id: '/' path: '/' fullPath: '/' preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } } } const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() ================================================ FILE: examples/vite-ssr-tsr-react/src/routes/__root.tsx ================================================ import { createRootRoute, Link, Outlet } from "@tanstack/react-router"; import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"; const RootLayout = () => ( <>
Home

); export const Route = createRootRoute({ component: RootLayout }); ================================================ FILE: examples/vite-ssr-tsr-react/src/routes/index.tsx ================================================ import { createFileRoute } from "@tanstack/react-router"; export const Route = createFileRoute("/")({ loader: async () => { const r = await fetch("/api/hello"); return r.json(); }, component: Index, }); function Index() { const r = Route.useLoaderData(); return (

{JSON.stringify(r)}

); } ================================================ FILE: examples/vite-ssr-tsr-react/tsconfig.json ================================================ { "extends": "nitro/tsconfig", "compilerOptions": { "baseUrl": ".", "jsx": "react-jsx", "paths": { "@/*": ["sec/*"] } } } ================================================ FILE: examples/vite-ssr-tsr-react/vite.config.mjs ================================================ import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; import react from "@vitejs/plugin-react"; import { tanstackRouter } from "@tanstack/router-plugin/vite"; export default defineConfig({ plugins: [tanstackRouter({ target: "react", autoCodeSplitting: true }), react(), nitro()], }); ================================================ FILE: examples/vite-ssr-tss-react/.gitignore ================================================ node_modules package-lock.json yarn.lock .DS_Store .cache .env .vercel .output .nitro /build/ /api/ /server/build /public/build# Sentry Config File .env.sentry-build-plugin /test-results/ /playwright-report/ /blob-report/ /playwright/.cache/ .tanstack ================================================ FILE: examples/vite-ssr-tss-react/README.md ================================================ Set up TanStack Start with Nitro for a full-stack React framework experience with server-side rendering, file-based routing, and integrated API routes. ## Overview 1. Add the Nitro Vite plugin to your Vite config 2. Create a server entry using TanStack Start's server handler 3. Configure the router with default components 4. Define routes and API endpoints using file-based routing ## 1. Configure Vite Add the Nitro, React, TanStack Start, and Tailwind plugins to your Vite config: ```js [vite.config.mjs] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; import { tanstackStart } from "@tanstack/react-start/plugin/vite"; import viteReact from "@vitejs/plugin-react"; import viteTsConfigPaths from "vite-tsconfig-paths"; import tailwindcss from "@tailwindcss/vite"; export default defineConfig({ plugins: [ viteTsConfigPaths({ projects: ["./tsconfig.json"] }), tanstackStart(), viteReact(), tailwindcss(), nitro(), ], environments: { ssr: { build: { rollupOptions: { input: "./server.ts" } } }, }, }); ``` The `tanstackStart()` plugin provides full SSR integration with automatic client entry handling. Use `viteTsConfigPaths()` to enable path aliases like `~/` from tsconfig. The `environments.ssr` option points to the server entry file. ## 2. Create the Server Entry Create a server entry that uses TanStack Start's handler: ```ts [server.ts] import handler, { createServerEntry } from "@tanstack/react-start/server-entry"; export default createServerEntry({ fetch(request) { return handler.fetch(request); }, }); ``` TanStack Start handles SSR automatically. The `createServerEntry` wrapper integrates with Nitro's server entry format, and the `handler.fetch` processes all incoming requests. ## 3. Configure the Router Create a router factory function with default error and not-found components: ```tsx [src/router.tsx] import { createRouter } from "@tanstack/react-router"; import { routeTree } from "./routeTree.gen.ts"; export function getRouter() { const router = createRouter({ routeTree, defaultPreload: "intent", defaultErrorComponent: () =>
Internal Server Error
, defaultNotFoundComponent: () =>
Not Found
, scrollRestoration: true, }); return router; } ``` The router factory configures preloading behavior, scroll restoration, and default error/not-found components. ## 4. Create the Root Route The root route defines your HTML shell with head management and scripts: ```tsx [src/routes/__root.tsx] /// import { HeadContent, Link, Scripts, createRootRoute } from "@tanstack/react-router"; import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"; import * as React from "react"; import appCss from "~/styles/app.css?url"; export const Route = createRootRoute({ head: () => ({ meta: [ { charSet: "utf8" }, { name: "viewport", content: "width=device-width, initial-scale=1" }, ], links: [{ rel: "stylesheet", href: appCss }], scripts: [{ src: "/customScript.js", type: "text/javascript" }], }), errorComponent: () =>

500: Internal Server Error

, notFoundComponent: () =>

404: Page Not Found

, shellComponent: RootDocument, }); function RootDocument({ children }: { children: React.ReactNode }) { return (
Home {" "} 404

{children} ); } ``` Define meta tags, stylesheets, and scripts in the `head()` function. The `shellComponent` provides the HTML document shell that wraps all pages. Use `HeadContent` to render the head configuration and `Scripts` to inject the client-side JavaScript for hydration. ## 5. Create Page Routes Page routes define your application pages: ```tsx [src/routes/index.tsx] import { createFileRoute } from "@tanstack/react-router"; export const Route = createFileRoute("/")({ component: Home }); function Home() { return (

Welcome Home!

/api/test
); } ``` ## API Routes TanStack Start supports API routes alongside page routes. Create files in `src/routes/api/` to define server endpoints that Nitro serves automatically. ================================================ FILE: examples/vite-ssr-tss-react/package.json ================================================ { "type": "module", "scripts": { "build": "vite build", "dev": "vite dev", "start": "node .output/server/index.mjs" }, "dependencies": { "@tanstack/react-router": "^1.168.1", "@tanstack/react-router-devtools": "^1.166.10", "@tanstack/react-start": "^1.167.1", "nitro": "latest", "react": "^19.2.4", "react-dom": "^19.2.4", "tailwind-merge": "^3.5.0", "zod": "^4.3.6" }, "devDependencies": { "@tailwindcss/vite": "^4.2.2", "@types/node": "latest", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^5.2.0", "tailwindcss": "^4.2.2", "typescript": "^5.9.3", "vite": "latest", "vite-tsconfig-paths": "^6.1.1" } } ================================================ FILE: examples/vite-ssr-tss-react/server.ts ================================================ import handler, { createServerEntry } from "@tanstack/react-start/server-entry"; export default createServerEntry({ fetch(request) { return handler.fetch(request); }, }); ================================================ FILE: examples/vite-ssr-tss-react/src/routeTree.gen.ts ================================================ /* eslint-disable */ // @ts-nocheck // noinspection JSUnusedGlobalSymbols // This file was automatically generated by TanStack Router. // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. import { Route as rootRouteImport } from './routes/__root' import { Route as IndexRouteImport } from './routes/index' import { Route as ApiTestRouteImport } from './routes/api/test' const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) const ApiTestRoute = ApiTestRouteImport.update({ id: '/api/test', path: '/api/test', getParentRoute: () => rootRouteImport, } as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute '/api/test': typeof ApiTestRoute } export interface FileRoutesByTo { '/': typeof IndexRoute '/api/test': typeof ApiTestRoute } export interface FileRoutesById { __root__: typeof rootRouteImport '/': typeof IndexRoute '/api/test': typeof ApiTestRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath fullPaths: '/' | '/api/test' fileRoutesByTo: FileRoutesByTo to: '/' | '/api/test' id: '__root__' | '/' | '/api/test' fileRoutesById: FileRoutesById } export interface RootRouteChildren { IndexRoute: typeof IndexRoute ApiTestRoute: typeof ApiTestRoute } declare module '@tanstack/react-router' { interface FileRoutesByPath { '/': { id: '/' path: '/' fullPath: '/' preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } '/api/test': { id: '/api/test' path: '/api/test' fullPath: '/api/test' preLoaderRoute: typeof ApiTestRouteImport parentRoute: typeof rootRouteImport } } } const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, ApiTestRoute: ApiTestRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() import type { getRouter } from './router.tsx' import type { createStart } from '@tanstack/react-start' declare module '@tanstack/react-start' { interface Register { ssr: true router: Awaited> } } ================================================ FILE: examples/vite-ssr-tss-react/src/router.tsx ================================================ import { createRouter } from "@tanstack/react-router"; import { routeTree } from "./routeTree.gen.ts"; export function getRouter() { const router = createRouter({ routeTree, defaultPreload: "intent", defaultErrorComponent: () =>
Internal Server Error
, defaultNotFoundComponent: () =>
Not Found
, scrollRestoration: true, }); return router; } ================================================ FILE: examples/vite-ssr-tss-react/src/routes/__root.tsx ================================================ /// import { HeadContent, Link, Scripts, createRootRoute } from "@tanstack/react-router"; import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"; import * as React from "react"; import appCss from "~/styles/app.css?url"; export const Route = createRootRoute({ head: () => ({ meta: [ { charSet: "utf8" }, { name: "viewport", content: "width=device-width, initial-scale=1" }, ], links: [{ rel: "stylesheet", href: appCss }], scripts: [{ src: "/customScript.js", type: "text/javascript" }], }), errorComponent: () =>

500: Internal Server Error

, notFoundComponent: () =>

404: Page Not Found

, shellComponent: RootDocument, }); function RootDocument({ children }: { children: React.ReactNode }) { return (
Home {" "} 404

{children} ); } ================================================ FILE: examples/vite-ssr-tss-react/src/routes/api/test.ts ================================================ import { createFileRoute } from "@tanstack/react-router"; import { createMiddleware, json } from "@tanstack/react-start"; const testMiddleware = createMiddleware().server(async ({ next }) => { const result = await next(); result.response.headers.set("x-test", "true"); return result; }); export const Route = createFileRoute("/api/test")({ server: { middleware: [testMiddleware], handlers: { GET: async ({ request }) => { return json({ api: "works!" }); }, }, }, }); ================================================ FILE: examples/vite-ssr-tss-react/src/routes/index.tsx ================================================ import { createFileRoute } from "@tanstack/react-router"; export const Route = createFileRoute("/")({ component: Home }); function Home() { return (

Welcome Home!

/api/test
); } ================================================ FILE: examples/vite-ssr-tss-react/src/styles/app.css ================================================ @import "tailwindcss"; @layer base { *, ::after, ::before, ::backdrop, ::file-selector-button { border-color: var(--color-gray-200, currentcolor); } } @layer base { html { color-scheme: light dark; } * { @apply border-gray-200 dark:border-gray-800; } html, body { @apply text-gray-900 bg-gray-50 dark:bg-gray-950 dark:text-gray-200; } .using-mouse * { outline: none !important; } } ================================================ FILE: examples/vite-ssr-tss-react/tsconfig.json ================================================ { "extends": "nitro/tsconfig", "compilerOptions": { "baseUrl": ".", "jsx": "react-jsx", "paths": { "~/*": ["./src/*"] } } } ================================================ FILE: examples/vite-ssr-tss-react/vite.config.mjs ================================================ import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; import { tanstackStart } from "@tanstack/react-start/plugin/vite"; import viteReact from "@vitejs/plugin-react"; import viteTsConfigPaths from "vite-tsconfig-paths"; import tailwindcss from "@tailwindcss/vite"; export default defineConfig({ plugins: [ viteTsConfigPaths({ projects: ["./tsconfig.json"] }), tanstackStart(), viteReact(), tailwindcss(), nitro(), ], environments: { ssr: { build: { rollupOptions: { input: "./server.ts" } } }, }, }); ================================================ FILE: examples/vite-ssr-vue-router/README.md ================================================ Set up server-side rendering (SSR) with Vue, Vue Router, Vite, and Nitro. This setup enables per-route code splitting, head management with unhead, and client hydration. ## Overview 1. Add the Nitro Vite plugin to your Vite config 2. Define routes with lazy-loaded components 3. Create a server entry that renders your app with router support 4. Create a client entry that hydrates and takes over routing 5. Create page components ## 1. Configure Vite Add the Nitro and Vue plugins to your Vite config. Define both `client` and `ssr` environments: ```js [vite.config.mjs] import vue from "@vitejs/plugin-vue"; import { defineConfig } from "vite"; import devtoolsJson from "vite-plugin-devtools-json"; import { nitro } from "nitro/vite"; export default defineConfig((_env) => ({ plugins: [patchVueExclude(vue(), /\?assets/), devtoolsJson(), nitro()], environments: { client: { build: { rollupOptions: { input: "./app/entry-client.ts" } } }, ssr: { build: { rollupOptions: { input: "./app/entry-server.ts" } } }, }, })); // Workaround https://github.com/vitejs/vite-plugin-vue/issues/677 function patchVueExclude(plugin, exclude) { const original = plugin.transform.handler; plugin.transform.handler = function (...args) { if (exclude.test(args[1])) return; return original.call(this, ...args); }; return plugin; } ``` The `patchVueExclude` helper prevents the Vue plugin from processing asset imports (files with `?assets` query parameter). ## 2. Define Routes Create route definitions with lazy-loaded components and asset metadata: ```ts [app/routes.ts] import type { RouteRecordRaw } from "vue-router"; export const routes: RouteRecordRaw[] = [ { path: "/", name: "app", component: () => import("./app.vue"), meta: { assets: () => import("./app.vue?assets"), }, children: [ { path: "/", name: "home", component: () => import("./pages/index.vue"), meta: { assets: () => import("./pages/index.vue?assets"), }, }, { path: "/about", name: "about", component: () => import("./pages/about.vue"), meta: { assets: () => import("./pages/about.vue?assets"), }, }, { path: "/:catchAll(.*)", name: "not-found", component: () => import("./pages/not-found.vue"), meta: { assets: () => import("./pages/not-found.vue?assets"), }, }, ], }, ]; ``` Use dynamic imports for lazy-loaded components to enable code splitting. The `meta.assets` function loads route-specific CSS and JS chunks. Define child routes under a root layout component for nested routing. ## 3. Create the Server Entry The server entry renders your Vue app with router support and head management: ```ts [app/entry-server.ts] import { createSSRApp } from "vue"; import { renderToString } from "vue/server-renderer"; import { RouterView, createMemoryHistory, createRouter } from "vue-router"; import { createHead, transformHtmlTemplate } from "unhead/server"; import { routes } from "./routes.ts"; import clientAssets from "./entry-client.ts?assets=client"; async function handler(request: Request): Promise { const app = createSSRApp(RouterView); const router = createRouter({ history: createMemoryHistory(), routes }); app.use(router); const url = new URL(request.url); const href = url.href.slice(url.origin.length); await router.push(href); await router.isReady(); const assets = clientAssets.merge( ...(await Promise.all( router.currentRoute.value.matched .map((to) => to.meta.assets) .filter(Boolean) .map((fn) => (fn as any)().then((m: any) => m.default)) )) ); const head = createHead(); head.push({ link: [ ...assets.css.map((attrs: any) => ({ rel: "stylesheet", ...attrs })), ...assets.js.map((attrs: any) => ({ rel: "modulepreload", ...attrs })), ], script: [{ type: "module", src: clientAssets.entry }], }); const renderedApp = await renderToString(app); const html = await transformHtmlTemplate(head, htmlTemplate(renderedApp)); return new Response(html, { headers: { "Content-Type": "text/html;charset=utf-8" }, }); } function htmlTemplate(body: string): string { return /* html */ ` Vue Router Custom Framework
${body}
`; } export default { fetch: handler, }; ``` The server uses `createMemoryHistory()` since there's no browser URL bar—the router navigates to the requested URL before rendering. Assets are loaded dynamically based on matched routes, ensuring only the CSS and JS needed for the current page are included. The `unhead` library manages `` elements, injecting stylesheets and scripts via `transformHtmlTemplate`. ## 4. Create the Client Entry The client entry hydrates the server-rendered HTML and takes over routing: ```ts [app/entry-client.ts] import { createSSRApp } from "vue"; import { RouterView, createRouter, createWebHistory } from "vue-router"; import { routes } from "./routes.ts"; async function main() { const app = createSSRApp(RouterView); const router = createRouter({ history: createWebHistory(), routes }); app.use(router); await router.isReady(); app.mount("#root"); } // eslint-disable-next-line unicorn/prefer-top-level-await main(); ``` The client entry creates a Vue app with `createWebHistory()` for browser-based routing. After the router is ready, it mounts to the `#root` element and hydrates the server-rendered HTML. ## 5. Create the Root Component The root component provides navigation and renders child routes: ```vue [app/app.vue] ``` ================================================ FILE: examples/vite-ssr-vue-router/app/app.vue ================================================ ================================================ FILE: examples/vite-ssr-vue-router/app/entry-client.ts ================================================ import { createSSRApp } from "vue"; import { RouterView, createRouter, createWebHistory } from "vue-router"; import { routes } from "./routes.ts"; async function main() { const app = createSSRApp(RouterView); const router = createRouter({ history: createWebHistory(), routes }); app.use(router); await router.isReady(); app.mount("#root"); } // eslint-disable-next-line unicorn/prefer-top-level-await main(); ================================================ FILE: examples/vite-ssr-vue-router/app/entry-server.ts ================================================ import { createSSRApp } from "vue"; import { renderToString } from "vue/server-renderer"; import { RouterView, createMemoryHistory, createRouter } from "vue-router"; import { createHead, transformHtmlTemplate } from "unhead/server"; import { routes } from "./routes.ts"; import clientAssets from "./entry-client.ts?assets=client"; async function handler(request: Request): Promise { const app = createSSRApp(RouterView); const router = createRouter({ history: createMemoryHistory(), routes }); app.use(router); const url = new URL(request.url); const href = url.href.slice(url.origin.length); await router.push(href); await router.isReady(); const assets = clientAssets.merge( ...(await Promise.all( router.currentRoute.value.matched .map((to) => to.meta.assets) .filter(Boolean) .map((fn) => (fn as any)().then((m: any) => m.default)) )) ); const head = createHead(); head.push({ link: [ ...assets.css.map((attrs: any) => ({ rel: "stylesheet", ...attrs })), ...assets.js.map((attrs: any) => ({ rel: "modulepreload", ...attrs })), ], script: [{ type: "module", src: clientAssets.entry }], }); const renderedApp = await renderToString(app); const html = await transformHtmlTemplate(head, htmlTemplate(renderedApp)); return new Response(html, { headers: { "Content-Type": "text/html;charset=utf-8" }, }); } function htmlTemplate(body: string): string { return /* html */ ` Vue Router Custom Framework
${body}
`; } export default { fetch: handler, }; ================================================ FILE: examples/vite-ssr-vue-router/app/pages/about.vue ================================================ ================================================ FILE: examples/vite-ssr-vue-router/app/pages/index.vue ================================================ ================================================ FILE: examples/vite-ssr-vue-router/app/pages/not-found.vue ================================================ ================================================ FILE: examples/vite-ssr-vue-router/app/routes.ts ================================================ import type { RouteRecordRaw } from "vue-router"; export const routes: RouteRecordRaw[] = [ { path: "/", name: "app", component: () => import("./app.vue"), meta: { assets: () => import("./app.vue?assets"), }, children: [ { path: "/", name: "home", component: () => import("./pages/index.vue"), meta: { assets: () => import("./pages/index.vue?assets"), }, }, { path: "/about", name: "about", component: () => import("./pages/about.vue"), meta: { assets: () => import("./pages/about.vue?assets"), }, }, { path: "/:catchAll(.*)", name: "not-found", component: () => import("./pages/not-found.vue"), meta: { assets: () => import("./pages/not-found.vue?assets"), }, }, ], }, ]; ================================================ FILE: examples/vite-ssr-vue-router/app/shims.d.ts ================================================ declare module "*.vue" { import type { DefineComponent } from "vue"; const component: DefineComponent<{}, {}, any>; export default component; } ================================================ FILE: examples/vite-ssr-vue-router/app/styles.css ================================================ * { box-sizing: border-box; } body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: #f5f5f5; color: #333; } main { max-width: 800px; margin: 0 auto; padding: 2rem; } h1 { font-size: 2.5rem; margin-bottom: 0.5rem; } .card { background: white; border-radius: 8px; padding: 2rem; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); margin: 2rem 0; } button { background: rgb(83, 91, 242); color: white; border: none; padding: 0.5rem 1rem; border-radius: 4px; font-size: 1rem; cursor: pointer; } button:hover { background: #535bf2; } .subtitle { color: #666; font-size: 1.1rem; margin-bottom: 2rem; } ================================================ FILE: examples/vite-ssr-vue-router/package.json ================================================ { "type": "module", "scripts": { "build": "vite build", "dev": "vite dev", "preview": "vite preview" }, "devDependencies": { "@vitejs/plugin-vue": "^6.0.5", "nitro": "latest", "unhead": "^2.1.12", "vite": "latest", "vite-plugin-devtools-json": "^1.0.0", "vue": "^3.5.30", "vue-router": "^4.6.4" } } ================================================ FILE: examples/vite-ssr-vue-router/tsconfig.json ================================================ { "extends": "nitro/tsconfig" } ================================================ FILE: examples/vite-ssr-vue-router/vite.config.mjs ================================================ import vue from "@vitejs/plugin-vue"; import { defineConfig } from "vite"; import devtoolsJson from "vite-plugin-devtools-json"; import { nitro } from "nitro/vite"; export default defineConfig((_env) => ({ plugins: [patchVueExclude(vue(), /\?assets/), devtoolsJson(), nitro()], environments: { client: { build: { rollupOptions: { input: "./app/entry-client.ts" } } }, ssr: { build: { rollupOptions: { input: "./app/entry-server.ts" } } }, }, })); // Workaround https://github.com/vitejs/vite-plugin-vue/issues/677 function patchVueExclude(plugin, exclude) { const original = plugin.transform.handler; plugin.transform.handler = function (...args) { if (exclude.test(args[1])) return; return original.call(this, ...args); }; return plugin; } ================================================ FILE: examples/vite-trpc/.gitignore ================================================ node_modules dist ================================================ FILE: examples/vite-trpc/README.md ================================================ Set up tRPC with Vite and Nitro for end-to-end typesafe APIs without code generation. This example builds a counter with server-side rendering for the initial value and client-side updates. ## Overview 1. Configure Vite with the Nitro plugin and route tRPC requests 2. Create a tRPC router with procedures 3. Create an HTML page with server-side rendering and client interactivity ## 1. Configure Vite Add the Nitro plugin and configure the `/trpc/**` route to point to your tRPC handler: ```ts [vite.config.ts] import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [ nitro({ routes: { "/trpc/**": "./server/trpc.ts", }, }), ], }); ``` The `routes` option maps URL patterns to handler files. All requests to `/trpc/*` are handled by the tRPC router. ## 2. Create the tRPC Router Define your tRPC router with procedures and export it as a fetch handler: ```ts [server/trpc.ts] import { initTRPC } from "@trpc/server"; import { fetchRequestHandler } from "@trpc/server/adapters/fetch"; let counter = 0; const t = initTRPC.create(); export const appRouter = t.router({ get: t.procedure.query(() => { return { value: counter }; }), inc: t.procedure.mutation(() => { counter++; return { value: counter }; }), }); export type AppRouter = typeof appRouter; export default { async fetch(request: Request): Promise { return fetchRequestHandler({ endpoint: "/trpc", req: request, router: appRouter, }); }, }; ``` Define procedures using `t.procedure.query()` for read operations and `t.procedure.mutation()` for write operations. Export the `AppRouter` type so clients get full type inference. The default export uses tRPC's fetch adapter to handle incoming requests. ## 3. Create the HTML Page Create an HTML page with server-side rendering and client-side interactivity: ```html [index.html] tRPC Counter
Counter
``` The ` ================================================ FILE: examples/vite-trpc/package.json ================================================ { "type": "module", "scripts": { "dev": "vite", "build": "vite build", "preview": "vite preview" }, "devDependencies": { "@trpc/client": "^11.13.4", "@trpc/server": "^11.13.4", "nitro": "latest", "vite": "latest", "zod": "^4.3.6" } } ================================================ FILE: examples/vite-trpc/server/trpc.ts ================================================ import { initTRPC } from "@trpc/server"; import { fetchRequestHandler } from "@trpc/server/adapters/fetch"; let counter = 0; const t = initTRPC.create(); export const appRouter = t.router({ get: t.procedure.query(() => { return { value: counter }; }), inc: t.procedure.mutation(() => { counter++; return { value: counter }; }), }); export type AppRouter = typeof appRouter; export default { async fetch(request: Request): Promise { return fetchRequestHandler({ endpoint: "/trpc", req: request, router: appRouter, }); }, }; ================================================ FILE: examples/vite-trpc/tsconfig.json ================================================ { "extends": "nitro/tsconfig", "compilerOptions": {} } ================================================ FILE: examples/vite-trpc/vite.config.ts ================================================ import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [ nitro({ routes: { "/trpc/**": "./server/trpc.ts", }, }), ], }); ================================================ FILE: examples/websocket/README.md ================================================ This example implements a simple chat room using WebSockets. Clients connect, send messages, and receive messages from other users in real-time. The server broadcasts messages to all connected clients using pub/sub channels. ## WebSocket Handler Create a WebSocket route using `defineWebSocketHandler`. ```ts [routes/_ws.ts] import { defineWebSocketHandler } from "nitro"; export default defineWebSocketHandler({ open(peer) { peer.send({ user: "server", message: `Welcome ${peer}!` }); peer.publish("chat", { user: "server", message: `${peer} joined!` }); peer.subscribe("chat"); }, message(peer, message) { if (message.text().includes("ping")) { peer.send({ user: "server", message: "pong" }); } else { const msg = { user: peer.toString(), message: message.toString(), }; peer.send(msg); // echo peer.publish("chat", msg); } }, close(peer) { peer.publish("chat", { user: "server", message: `${peer} left!` }); }, }); ``` Different hooks are exposed by `defineWebSocketHandler()` to integrate with different parts of the websocket lifecycle. ================================================ FILE: examples/websocket/index.html ================================================ CrossWS Test Page

{{ message.user }}

Avatar

{{ message.text }}

{{ message.date }}

` ================================================ FILE: examples/websocket/nitro.config.ts ================================================ import { defineConfig } from "nitro"; export default defineConfig({ serverDir: "./", renderer: { static: true }, features: { websocket: true }, }); ================================================ FILE: examples/websocket/package.json ================================================ { "type": "module", "scripts": { "dev": "nitro dev", "build": "nitro build" }, "devDependencies": { "nitro": "latest" } } ================================================ FILE: examples/websocket/routes/_ws.ts ================================================ import { defineWebSocketHandler } from "nitro"; export default defineWebSocketHandler({ open(peer) { peer.send({ user: "server", message: `Welcome ${peer}!` }); peer.publish("chat", { user: "server", message: `${peer} joined!` }); peer.subscribe("chat"); }, message(peer, message) { if (message.text().includes("ping")) { peer.send({ user: "server", message: "pong" }); } else { const msg = { user: peer.toString(), message: message.toString(), }; peer.send(msg); // echo peer.publish("chat", msg); } }, close(peer) { peer.publish("chat", { user: "server", message: `${peer} left!` }); }, }); ================================================ FILE: examples/websocket/tsconfig.json ================================================ { "extends": "nitro/tsconfig" } ================================================ FILE: examples/websocket/vite.config.ts ================================================ import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] }); ================================================ FILE: lib/h3.d.mts ================================================ export * from "h3"; ================================================ FILE: lib/h3.mjs ================================================ export * from "h3"; ================================================ FILE: lib/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "lib": ["ESNext", "DOM"], "module": "ESNext", "moduleResolution": "Bundler", "moduleDetection": "force", "isolatedModules": true, "verbatimModuleSyntax": true, "allowJs": true, "allowImportingTsExtensions": true, "strict": true, "noEmit": true, "skipLibCheck": true, "resolveJsonModule": true, "allowSyntheticDefaultImports": true, "noImplicitOverride": true, "resolvePackageJsonImports": true, "forceConsistentCasingInFileNames": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true } } ================================================ FILE: lib/vite.types.d.mts ================================================ // Based on https://github.com/hi-ogawa/vite-plugin-fullstack/blob/main/types/query.d.ts type ImportAssetsResult = ImportAssetsResultRaw & { merge(...args: ImportAssetsResultRaw[]): ImportAssetsResult; }; type ImportAssetsResultRaw = { entry?: string; js: { href: string }[]; css: { href: string; "data-vite-dev-id"?: string }[]; }; declare module "*?assets" { const assets: ImportAssetsResult; export default assets; } declare module "*?assets=client" { const assets: ImportAssetsResult; export default assets; } declare module "*?assets=ssr" { const assets: ImportAssetsResult; export default assets; } ================================================ FILE: lib/vite.types.mjs ================================================ // eslint-disable-next-line unicorn/require-module-specifiers export {}; ================================================ FILE: package.json ================================================ { "name": "nitro", "version": "3.0.260311-beta", "description": "Build and Deploy Universal JavaScript Servers", "keywords": [ "api-routes", "full-stack", "h3", "nitro", "server", "typescript", "vite", "vite-plugin", "web" ], "homepage": "https://nitro.build", "license": "MIT", "repository": "nitrojs/nitro", "bin": { "nitro": "./dist/cli/index.mjs" }, "files": [ "dist", "lib", "skills" ], "type": "module", "types": "./lib/index.d.mts", "imports": { "#nitro/runtime/*": "./dist/runtime/internal/*.mjs", "#nitro/virtual/*": "./dist/runtime/virtual/*.mjs" }, "exports": { ".": "./dist/runtime/nitro.mjs", "./app": "./dist/runtime/app.mjs", "./builder": "./dist/builder.mjs", "./cache": "./dist/runtime/cache.mjs", "./config": "./dist/runtime/config.mjs", "./context": "./dist/runtime/context.mjs", "./database": "./dist/runtime/database.mjs", "./h3": "./lib/h3.mjs", "./meta": "./dist/runtime/meta.mjs", "./package.json": "./package.json", "./runtime-config": "./dist/runtime/runtime-config.mjs", "./storage": "./dist/runtime/storage.mjs", "./task": "./dist/runtime/task.mjs", "./tsconfig": "./lib/tsconfig.json", "./types": "./dist/types/index.mjs", "./vite": "./dist/vite.mjs", "./vite/runtime": "./dist/runtime/vite.mjs", "./vite/types": "./lib/vite.types.mjs" }, "scripts": { "build": "pnpm gen-presets && obuild", "dev": "pnpm -C playground dev", "dev:build": "pnpm -C playground build", "dev:start": "node playground/.output/server/index.mjs", "gen-node-compat": "node scripts/gen-node-compat.ts", "gen-presets": "obuild --stub && node ./scripts/gen-presets.ts", "lint": "oxlint . && oxfmt --check .", "format": "automd && oxlint --fix . && oxfmt .", "nitro": "node ./src/cli/index.ts", "release": "node ./scripts/release.js", "stub": "obuild --stub", "test": "pnpm lint && pnpm typecheck && pnpm test:rollup && pnpm test:rolldown", "test:rolldown": "NITRO_BUILDER=rolldown pnpm vitest", "test:rollup": "NITRO_BUILDER=rollup pnpm vitest", "typecheck": "tsgo --noEmit --skipLibCheck" }, "dependencies": { "consola": "^3.4.2", "crossws": "^0.4.4", "db0": "^0.3.4", "env-runner": "^0.1.6", "h3": "^2.0.1-rc.17", "hookable": "^6.1.0", "nf3": "^0.3.13", "ocache": "^0.1.4", "ofetch": "^2.0.0-alpha.3", "ohash": "^2.0.11", "rolldown": "^1.0.0-rc.10", "srvx": "^0.11.12", "unenv": "^2.0.0-rc.24", "unstorage": "^2.0.0-alpha.7" }, "devDependencies": { "@azure/functions": "^3.5.1", "@azure/static-web-apps-cli": "^2.0.8", "@cloudflare/workers-types": "^4.20260317.1", "@deno/types": "^0.0.1", "@hiogawa/vite-plugin-fullstack": "^0.0.11", "@netlify/edge-functions": "^3.0.6", "@netlify/functions": "^5.1.5", "@rollup/plugin-alias": "^6.0.0", "@rollup/plugin-commonjs": "^29.0.2", "@rollup/plugin-inject": "^5.0.5", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.3", "@rollup/plugin-replace": "^6.0.3", "@scalar/api-reference": "^1.49.3", "@types/aws-lambda": "^8.10.161", "@types/estree": "^1.0.8", "@types/etag": "^1.8.4", "@types/http-proxy": "^1.17.17", "@types/node": "^25.5.0", "@types/node-fetch": "^2.6.13", "@types/semver": "^7.7.1", "@types/xml2js": "^0.4.14", "@typescript/native-preview": "latest", "@vitest/coverage-v8": "^4.1.0", "automd": "^0.4.3", "c12": "^4.0.0-beta.4", "changelogen": "^0.6.2", "chokidar": "^5.0.0", "citty": "^0.2.1", "compatx": "^0.2.0", "confbox": "^0.2.4", "cookie-es": "^2.0.0", "croner": "^10.0.1", "defu": "^6.1.4", "destr": "^2.0.5", "dot-prop": "^10.1.0", "edge-runtime": "^4.0.1", "escape-string-regexp": "^5.0.0", "etag": "^1.8.1", "execa": "^9.6.1", "expect-type": "^1.3.0", "exsolve": "^1.0.8", "get-port-please": "^3.2.0", "giget": "^3.1.2", "gzip-size": "^7.0.0", "httpxy": "^0.3.1", "klona": "^2.0.6", "knitwork": "^1.3.0", "magic-string": "^0.30.21", "mdzilla": "^0.0.6", "mime": "^4.1.0", "miniflare": "^4.20260317.1", "mlly": "^1.8.2", "nypm": "^0.6.5", "obuild": "^0.4.32", "oxfmt": "^0.40.0", "oxlint": "^1.56.0", "pathe": "^2.0.3", "perfect-debounce": "^2.1.0", "pkg-types": "^2.3.0", "pretty-bytes": "^7.1.0", "react": "^19.2.4", "rendu": "^0.0.7", "rollup": "^4.59.0", "rou3": "^0.8.1", "scule": "^1.3.0", "semver": "^7.7.4", "serve-placeholder": "^2.0.2", "source-map": "^0.7.6", "std-env": "^4.0.0", "tinyglobby": "^0.2.15", "tsconfck": "^3.1.6", "typescript": "^5.9.3", "ufo": "^1.6.3", "ultrahtml": "^1.6.0", "uncrypto": "^0.1.3", "unctx": "^2.5.0", "unimport": "^6.0.2", "untyped": "^2.0.0", "unwasm": "^0.5.3", "vite": "^8.0.1", "vite7": "npm:vite@^7.3.1", "vitest": "^4.1.0", "wrangler": "^4.76.0", "xml2js": "^0.6.2", "youch": "^4.1.0", "youch-core": "^0.3.3", "zephyr-agent": "^0.1.16" }, "peerDependencies": { "dotenv": "*", "giget": "*", "jiti": "^2.6.1", "rollup": "^4.59.0", "vite": "^7 || ^8", "xml2js": "^0.6.2", "zephyr-agent": "^0.1.15" }, "peerDependenciesMeta": { "dotenv": { "optional": true }, "rollup": { "optional": true }, "vite": { "optional": true }, "xml2js": { "optional": true }, "giget": { "optional": true }, "jiti": { "optional": true }, "zephyr-agent": { "optional": true } }, "resolutions": { "nitro": "link:." }, "engines": { "node": "^20.19.0 || >=22.12.0" }, "packageManager": "pnpm@10.32.0", "compatiblePackages": { "schemaVersion": 1, "vite": { "type": "compatible", "versions": "^7 || ^8" }, "rollup": { "type": "compatible", "versions": "^4" }, "rolldown": { "type": "compatible", "versions": ">=1.0.0-beta.0" } } } ================================================ FILE: playground/nitro.config.ts ================================================ import { defineConfig } from "nitro"; export default defineConfig({ serverDir: "./server", }); ================================================ FILE: playground/package.json ================================================ { "name": "nitro-playground", "version": "1.0.0", "type": "module", "scripts": { "build": "vite build", "dev": "vite dev", "preview": "vite preview" }, "devDependencies": { "nitro": "link:..", "vite": "latest" } } ================================================ FILE: playground/server.ts ================================================ export default { fetch(req: Request) { return new Response("Hello from Nitro playground!"); }, }; ================================================ FILE: playground/tsconfig.json ================================================ { "extends": "nitro/tsconfig" } ================================================ FILE: playground/vite.config.ts ================================================ import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()], }); ================================================ FILE: pnpm-workspace.yaml ================================================ packages: - examples/** - playground - test/fixture - test/minimal ignoreWorkspaceRootCheck: true ignoredBuiltDependencies: - keytar - protobufjs - vue-demi onlyBuiltDependencies: - '@parcel/watcher' - esbuild - ocache - sharp - workerd peerDependencyRules: ignoreMissing: - react - '@types/react' - react-dom - '@algolia/client-search' shellEmulator: true # typescript-eslint is affected by # https://github.com/pnpm/pnpm/issues/10329 # trustPolicy: no-downgrade trustPolicyExclude: - chokidar@4.0.3 - tailwind-merge@2.6.0 - '@headlessui/vue@1.7.23' - undici-types@6.21.0 - rxjs@7.8.2 - semver@6.3.1 ================================================ FILE: renovate.json ================================================ { "extends": ["github>unjs/renovate-config"], "baseBranchPatterns": ["main"] } ================================================ FILE: scripts/bump-nightly.ts ================================================ import { promises as fsp } from "node:fs"; import { execaCommand } from "execa"; import { glob } from "tinyglobby"; import { resolve } from "pathe"; const nightlyPackages = { // h3: "h3-nightly", } as Record; async function loadPackage(dir: string) { const pkgPath = resolve(dir, "package.json"); const data = JSON.parse(await fsp.readFile(pkgPath, "utf8").catch(() => "{}")); const save = () => fsp.writeFile(pkgPath, JSON.stringify(data, null, 2) + "\n"); const updateDeps = (reviver: (dep: any) => any) => { for (const type of [ "dependencies", "devDependencies", "optionalDependencies", "peerDependencies", ]) { if (!data[type]) { continue; } for (const e of Object.entries(data[type])) { const dep = { name: e[0], range: e[1], type }; delete data[type][dep.name]; const updated = reviver(dep) || dep; data[updated.type] = data[updated.type] || {}; data[updated.type][updated.name] = updated.range; } } }; return { dir, data, save, updateDeps, }; } type ThenArg = T extends PromiseLike ? U : T; type Package = ThenArg>; async function loadWorkspace(dir: string) { const workspacePkg = await loadPackage(dir); const pkgDirs = await glob(workspacePkg.data.workspaces || [], { onlyDirectories: true, }); const packages: Package[] = [workspacePkg]; for (const pkgDir of pkgDirs) { const pkg = await loadPackage(pkgDir); if (!pkg.data.name) { continue; } packages.push(pkg); } const find = (name: string) => { const pkg = packages.find((pkg) => pkg.data.name === name); if (!pkg) { throw new Error("Workspace package not found: " + name); } return pkg; }; const rename = (from: string, to: string) => { find(from).data.name = to; for (const pkg of packages) { pkg.updateDeps((dep) => { if (dep.name === from && !dep.range.startsWith("npm:")) { dep.range = "npm:" + to + "@" + dep.range; } }); } }; const setVersion = (name: string, newVersion: string) => { find(name).data.version = newVersion; for (const pkg of packages) { pkg.updateDeps((dep) => { if (dep.name === name) { dep.range = newVersion; } }); } }; const save = () => Promise.all(packages.map((pkg) => pkg.save())); return { dir, workspacePkg, packages, save, find, rename, setVersion, }; } function fmtDate(d: Date): string { // YYMMDD-HHMMSS: 20240919-140954 const date = joinNumbers([d.getFullYear(), d.getMonth() + 1, d.getDate()]); const time = joinNumbers([d.getHours(), d.getMinutes(), d.getSeconds()]); return `${date}-${time}`; } function joinNumbers(items: number[]): string { return items.map((i) => (i + "").padStart(2, "0")).join(""); } async function main() { const workspace = await loadWorkspace(process.cwd()); const commit = await execaCommand("git rev-parse --short HEAD").then((r) => r.stdout.trim()); for (const pkg of workspace.packages.filter((p) => !p.data.private)) { workspace.setVersion(pkg.data.name, `${pkg.data.version}-${fmtDate(new Date())}.${commit}`); workspace.rename(pkg.data.name, pkg.data.name + "-nightly"); pkg.updateDeps((dep) => { if (nightlyPackages[dep.name]) { dep.range = "npm:" + nightlyPackages[dep.name] + "@latest"; } }); } await workspace.save(); } // eslint-disable-next-line unicorn/prefer-top-level-await main().catch((error) => { console.error(error); // eslint-disable-next-line unicorn/no-process-exit process.exit(1); }); ================================================ FILE: scripts/bump-version.ts ================================================ #!/bin/env node import { promises as fsp } from "node:fs"; import { resolve } from "pathe"; const c = { cyan: (s: string) => `\x1B[36m${s}\x1B[0m`, green: (s: string) => `\x1B[32m${s}\x1B[0m`, yellow: (s: string) => `\x1B[33m${s}\x1B[0m`, gray: (s: string) => `\x1B[90m${s}\x1B[0m`, red: (s: string) => `\x1B[31m${s}\x1B[0m`, bold: (s: string) => `\x1B[1m${s}\x1B[0m`, }; export function fmtDate(d: Date): string { const y = d.getFullYear() % 100; const m = (d.getMonth() + 1).toString().padStart(2, "0"); const day = d.getDate().toString().padStart(2, "0"); return `${y}${m}${day}`; } async function fetchExistingVersions(pkgName: string): Promise { const url = `https://registry.npmjs.org/${pkgName}`; console.log(c.gray(`Fetching versions from npm registry for ${c.cyan(pkgName)}...`)); try { const res = await fetch(url); if (!res.ok) { console.log(c.yellow(` Registry returned ${res.status}, assuming first release`)); return []; } const data = (await res.json()) as { versions?: Record }; const versions = Object.keys(data.versions || {}); return versions; } catch (err) { console.log(c.yellow(` Failed to fetch registry: ${err}`)); return []; } } export async function resolveVersion( pkgName: string, dateStr: string, prerelease = "beta" ): Promise { const versions = await fetchExistingVersions(pkgName); const base = `3.0.${dateStr}`; const prefix = prerelease ? `${base}-${prerelease}` : base; const matching = versions.filter((v) => v.startsWith(prefix)); const sep = prerelease ? "." : "-"; let max = 0; for (const v of matching) { const rest = v.slice(prefix.length); if (rest === "") { max = Math.max(max, 1); } else if (rest.startsWith(sep)) { const n = Number.parseInt(rest.slice(1), 10); if (!Number.isNaN(n)) { max = Math.max(max, n); } } } const version = max === 0 ? prefix : `${prefix}${sep}${max + 1}`; console.log(c.gray(` Resolved version: ${c.cyan(version)}`)); return version; } async function main() { console.log(c.bold("\nBump version to beta\n")); const pkgPath = resolve(process.cwd(), "package.json"); const pkg = JSON.parse(await fsp.readFile(pkgPath, "utf8")); const oldVersion = pkg.version; const dateStr = fmtDate(new Date()); console.log(c.gray(`Date: ${c.cyan(dateStr)}`)); const newVersion = await resolveVersion(pkg.name, dateStr); console.log(); console.log(` ${c.cyan(pkg.name)} ${c.gray(oldVersion)} → ${c.green(newVersion)}`); pkg.version = newVersion; await fsp.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n"); console.log(c.green(`\nDone!\n`)); } if (process.argv[1] && import.meta.url.endsWith(process.argv[1])) { main().catch((error) => { console.error(c.red(`\nError: ${error.message}\n`)); process.exit(1); }); } ================================================ FILE: scripts/gen-node-compat.ts ================================================ import { writeFile } from "node:fs/promises"; const platforms = { cloudflare: "https://platform-node-compat.pi0.workers.dev/?ts", deno: "https://platform-node-compat.deno.dev/?ts", }; for (const platform in platforms) { const url = platforms[platform as keyof typeof platforms]; const code = await fetch(url).then((res) => res.text()); console.log(`Fetching Node.js compatibility data for ${platform} from ${url}`); await writeFile( new URL(`../src/presets/${platform}/unenv/node-compat.ts`, import.meta.url), code ); } ================================================ FILE: scripts/gen-presets.ts ================================================ import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs"; import { resolve } from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; import { consola } from "consola"; import { findTypeExports } from "mlly"; import type { NitroPreset, NitroPresetMeta } from "nitro/types"; import { camelCase, kebabCase, pascalCase, snakeCase } from "scule"; const autoGenHeader = /* ts */ `// Auto-generated using gen-presets script\n`; // --- Scan presets/ directory --- const presetsDir = fileURLToPath(new URL("../src/presets", import.meta.url)); const presetDirs: string[] = readdirSync(presetsDir, { withFileTypes: true }) .filter( (dir) => dir.name !== "_utils" && dir.isDirectory() && existsSync(resolve(presetsDir, dir.name, "preset.ts")) ) .map((dir) => dir.name); // --- Load presets --- const allPresets: (NitroPreset & { _meta?: NitroPresetMeta })[] = []; for (const preset of presetDirs) { const presetPath = resolve(presetsDir, preset, "preset.ts"); const _presets = await import(pathToFileURL(presetPath).href).then( (mod) => (mod as any).default || mod ); if (!Array.isArray(_presets)) { throw new TypeError(`Preset ${preset} does not export an array`); } allPresets.push(..._presets); } // --- Validate names --- const _names = new Set(); for (const preset of allPresets) { if (!preset._meta?.name) { consola.warn(`Preset ${preset} does not have a name`); continue; } const names = [preset._meta.name, ...(preset._meta.aliases || [])]; for (const name of names) { if (_names.has(name)) { if (!preset._meta.compatibilityDate && !preset._meta.dev) { consola.warn(`Preset ${name} is duplicated`); } continue; } if (kebabCase(name) !== name) { consola.warn(`Preset ${name} is not kebab-case`); } _names.add(name); } } const names = [..._names].sort(); consola.log(names.join(", ")); // --- Generate presets/_all.gen.ts --- writeFileSync( resolve(presetsDir, "_all.gen.ts"), /* ts */ `${autoGenHeader} ${presetDirs .map((preset) => `import _${camelCase(preset)} from "./${preset}/preset.ts";`) .join("\n")} export default [ ${presetDirs.map((preset) => ` ..._${camelCase(preset)},`).join("\n")} ] as const; ` ); // --- Generate presets/_types.gen.ts --- const presetsWithType = presetDirs.filter((presetDir) => { const presetPath = resolve(presetsDir, presetDir, "preset.ts"); const content = readFileSync(presetPath, "utf8"); const typeExports = findTypeExports(content); return typeExports.some((type) => type.name === "PresetOptions"); }); writeFileSync( resolve(presetsDir, "_types.gen.ts"), /* ts */ `${autoGenHeader} ${presetsWithType .map( (preset) => `import type { PresetOptions as ${pascalCase(preset)}Options } from "./${preset}/preset.ts";` ) .join("\n")} export interface PresetOptions { ${presetsWithType .map((preset) => ` ${camelCase(preset)}?: ${pascalCase(preset)}Options;`) .join("\n")} } export const presetsWithConfig = ${JSON.stringify(presetsWithType.map((p) => camelCase(p)))} as const; export type PresetName = ${names.map((name) => `"${name}"`).join(" | ")}; export type PresetNameInput = ${names .flatMap((name) => [...new Set([kebabCase(name), camelCase(name), snakeCase(name)])].map((n) => `"${n}"`) ) .join(" | ")} | (string & {}); ` ); ================================================ FILE: scripts/release.ts ================================================ #!/bin/env node import { execSync } from "node:child_process"; import { readFile } from "node:fs/promises"; import { parseArgs } from "node:util"; // import { setTimeout as sleep } from "node:timers/promises"; const { values: args } = parseArgs({ allowNegative: true, options: { precheck: { type: "boolean", default: true }, }, }); const c = { cyan: (s: string) => `\x1B[36m${s}\x1B[0m`, green: (s: string) => `\x1B[32m${s}\x1B[0m`, red: (s: string) => `\x1B[31m${s}\x1B[0m`, bold: (s: string) => `\x1B[1m${s}\x1B[0m`, gray: (s: string) => `\x1B[90m${s}\x1B[0m`, }; main().catch((error) => { console.error(c.red(`\nError: ${error.message}\n`)); process.exit(1); }); async function main() { console.log(c.bold("\n🚀 Nitro Release\n")); // 1. Prechecks if (!args.precheck) { console.log(c.gray("→ Skipping prechecks (--no-precheck)\n")); } else { await precheck(); } // 2. Bump version console.log(c.cyan("\n→ Bumping version...\n")); run("node scripts/bump-version.ts"); // 3. Read new version const pkg = JSON.parse(await readFile("package.json", "utf8")); const version = `v${pkg.version}`; console.log(c.cyan(`\n→ Version: ${c.bold(version)}\n`)); // 4. Generate release notes console.log(c.cyan("→ Generating release notes...\n")); run("pnpx changelogen --output CHANGELOG.md"); console.log(c.green(" Written to CHANGELOG.md")); // 5. Commit and tag console.log(c.cyan("\n→ Creating release commit and tag...\n")); run("git add package.json"); run(`git commit -m "${version}"`); run(`git tag -a ${version} -m "${version}"`); console.log(c.green(c.bold(`\n✅ Release ${version} prepared!\n`))); // Prompt to push process.stdout.write(c.cyan(` Push with ${c.bold("git push --follow-tags")}? (yes/no) `)); const answer = await new Promise((resolve) => { process.stdin.setEncoding("utf8"); process.stdin.once("data", (data) => resolve(data.toString().trim())); }); process.stdin.destroy(); if (answer === "yes") { run("git push --follow-tags"); console.log(c.green(c.bold("\n🎉 Released!\n"))); } else { console.log(c.cyan(`\n Run ${c.bold("git push --follow-tags")} manually to publish.\n`)); } } function run(cmd: string, opts?: { silent?: boolean; quiet?: boolean }) { if (!opts?.quiet) { console.log(c.gray(`$ ${cmd}`)); } return execSync(cmd, { stdio: opts?.silent ? "pipe" : "inherit", encoding: "utf8", }); } async function precheck() { console.log(c.cyan("→ Running prechecks...\n")); // Check we are on main branch const branch = run("git rev-parse --abbrev-ref HEAD", { silent: true }).trim(); if (branch !== "main") { throw new Error(`Must be on "main" branch (currently on "${branch}")`); } // Check no dirty state const status = run("git status --porcelain", { silent: true }).trim(); if (status) { throw new Error(`Working tree is dirty:\n${status}`); } // Check local HEAD matches remote main run("git fetch origin main", { silent: true }); const localHead = run("git rev-parse HEAD", { silent: true }).trim(); const remoteHead = run("git rev-parse origin/main", { silent: true }).trim(); if (localHead !== remoteHead) { throw new Error( `Local HEAD (${localHead.slice(0, 8)}) does not match origin/main (${remoteHead.slice(0, 8)})` ); } // Check all GitHub Actions completed successfully for HEAD commit // type CIRun = { status: string; conclusion: string; name: string; databaseId: number }; // type CIJob = { status: string; conclusion: string; name: string }; // const pollInterval = 15_000; // let ciRuns: CIRun[] = []; // // eslint-disable-next-line no-constant-condition // while (true) { // ciRuns = JSON.parse( // run( // `gh run list --branch main --commit ${remoteHead} --json status,conclusion,name,databaseId`, // { silent: true, quiet: true } // ) // ) as CIRun[]; // if (ciRuns.length === 0) { // throw new Error("No GitHub Actions runs found for HEAD"); // } // const pending = ciRuns.filter((r) => r.status !== "completed"); // if (pending.length === 0) break; // console.log(c.gray(` Waiting for ${pending.map((r) => r.name).join(", ")}...`)); // await sleep(pollInterval); // } // // Fetch jobs for each run and print summary // const allFailed: string[] = []; // for (const r of ciRuns) { // const jobs = JSON.parse( // run(`gh run view ${r.databaseId} --json jobs -q ".jobs"`, { silent: true, quiet: true }) // ) as CIJob[]; // const runIcon = r.conclusion === "success" ? c.green("✓") : c.red("✗"); // console.log(` ${runIcon} ${c.bold(r.name)}`); // for (const job of jobs) { // const jobIcon = job.conclusion === "success" ? c.green("✓") : c.red("✗"); // console.log(` ${jobIcon} ${job.name}`); // if (job.conclusion !== "success") { // allFailed.push(`${r.name} > ${job.name} (${job.conclusion})`); // } // } // } // if (allFailed.length > 0) { // throw new Error(`GitHub Actions failed:\n ${allFailed.join("\n ")}`); // } console.log(c.green(" All prechecks passed!\n")); } ================================================ FILE: scripts/vite7.ts ================================================ import { execSync } from "node:child_process"; import { readFileSync, writeFileSync } from "node:fs"; import { resolve } from "node:path"; const pkgPath = resolve(import.meta.dirname, "../package.json"); const pkg = JSON.parse(readFileSync(pkgPath, "utf8")); pkg.resolutions = pkg.resolutions || {}; pkg.resolutions.vite = "^7"; writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n"); console.log("Added vite: ^7 to resolutions"); execSync("pnpm install --no-frozen-lockfile", { stdio: "inherit", cwd: resolve(import.meta.dirname, ".."), }); ================================================ FILE: skills/nitro/SKILL.md ================================================ --- name: nitro description: Build and deploy universal JavaScript servers with Nitro --- @docs/TOC.md You can use `npx nitro docs [--page ] [...args]` to explore the documentation locally. For example, `npx nitro docs --page /docs/routing` will open the routing page of the guide section. If not available, fallback to https://nitro.build/llms.txt ================================================ FILE: src/build/assets.ts ================================================ import { existsSync, promises as fsp } from "node:fs"; import { glob } from "tinyglobby"; import { isDirectory, prettyPath } from "../utils/fs.ts"; import type { Nitro } from "nitro/types"; import { join, relative, resolve } from "pathe"; import { compressPublicAssets } from "../utils/compress.ts"; const NEGATION_RE = /^(!?)(.*)$/; const PARENT_DIR_GLOB_RE = /!?\.\.\//; export async function scanUnprefixedPublicAssets(nitro: Nitro) { const scannedPaths: string[] = []; for (const asset of nitro.options.publicAssets) { if (asset.baseURL && asset.baseURL !== "/" && !asset.fallthrough) { // we can statically detect these without scanning continue; } if (!(await isDirectory(asset.dir))) { continue; } const includePatterns = getIncludePatterns(nitro, asset.dir, asset.ignore); const publicAssets = await glob(includePatterns, { cwd: asset.dir, absolute: false, dot: true, }); scannedPaths.push(...publicAssets.map((file) => join(asset.baseURL || "/", file))); } return scannedPaths; } export async function copyPublicAssets(nitro: Nitro) { if (nitro.options.noPublicDir) { return; } for (const asset of nitro.options.publicAssets) { const assetDir = asset.dir; const dstDir = join(nitro.options.output.publicDir, asset.baseURL!); if (await isDirectory(assetDir)) { const includePatterns = getIncludePatterns(nitro, assetDir, asset.ignore); const publicAssets = await glob(includePatterns, { cwd: assetDir, absolute: false, dot: true, }); await Promise.all( publicAssets.map(async (file) => { const src = join(assetDir, file); const dst = join(dstDir, file); if (!existsSync(dst)) { await fsp.cp(src, dst); } }) ); } } if (nitro.options.compressPublicAssets) { await compressPublicAssets(nitro); } nitro.logger.success("Generated public " + prettyPath(nitro.options.output.publicDir)); } function getIncludePatterns( nitro: Nitro, assetDir: string, ignorePatterns: string[] | false = nitro.options.ignore ) { return [ "**", ...(ignorePatterns || []).map((p) => { const [_, negation, pattern] = p.match(NEGATION_RE) || []; return ( // Convert ignore to include patterns (negation ? "" : "!") + // Make non-glob patterns relative to publicAssetDir (pattern.startsWith("*") ? pattern : relative(assetDir, resolve(nitro.options.rootDir, pattern))) ); }), ].filter((p) => !PARENT_DIR_GLOB_RE.test(p)); } ================================================ FILE: src/build/build.ts ================================================ import type { Nitro } from "nitro/types"; export async function build(nitro: Nitro) { switch (nitro.options.builder) { case "rollup": { const { rollupBuild } = await import("./rollup/build.ts"); return rollupBuild(nitro); } case "rolldown": { const { rolldownBuild } = await import("./rolldown/build.ts"); return rolldownBuild(nitro); } case "vite": { const { viteBuild } = await import("./vite/build.ts"); return viteBuild(nitro); } default: { throw new Error(`Unknown builder: ${nitro.options.builder}`); } } } ================================================ FILE: src/build/chunks.ts ================================================ import type { Nitro } from "nitro/types"; // Tests in @test/unit/chunks.test.ts const virtualRe = /^(?:\0|#|virtual:)/; export const NODE_MODULES_RE = /node_modules[/\\](?!(?:nitro|nitro-nightly)[/\\])[^.]/; export function libChunkName(id: string) { const pkgName = pathToPkgName(id); return pkgName ? `_libs/${pkgName}` : undefined; } export function pathToPkgName(path: string): string | undefined { let pkgName = path.match( /.*(?:[/\\])node_modules(?:[/\\])(?@[^/\\]+[/\\][^/\\]+|[^/\\.][^/\\]*)/ )?.groups?.name; if (pkgName?.endsWith("-nightly")) { pkgName = pkgName.slice(0, -8); } return pkgName; } export function getChunkName(chunk: { name: string; moduleIds: string[] }, nitro: Nitro) { // Known groups if (chunk.name === "rolldown-runtime") { return "_runtime.mjs"; } // Library chunks if (chunk.moduleIds.every((id) => NODE_MODULES_RE.test(id))) { const chunkName = joinPkgNames(chunk.moduleIds); if (chunkName.length > 30) { return `${chunk.name}+[...].mjs`; } return `_libs/${chunkName || "_"}.mjs`; } // _ chunks are preserved (should be after library normalization) if (chunk.name.startsWith("_")) { return `${chunk.name}.mjs`; } // No moduleIds if (chunk.moduleIds.length === 0) { return `_chunks/${chunk.name}.mjs`; } const ids = chunk.moduleIds.filter((id) => !virtualRe.test(id)); // All virtual if (ids.length === 0) { if (chunk.moduleIds.every((id) => id.includes("virtual:raw"))) { return `_raw/[name].mjs`; } return `_virtual/[name].mjs`; } // WASM chunk if (ids.every((id) => id.endsWith(".wasm"))) { return `_wasm/[name].mjs`; } // Chunks generate by other vite environments (we assume SSR for simplicity) if (ids.every((id) => id.includes("vite/services"))) { return `_ssr/[name].mjs`; } // Chunks from generated code if (ids.every((id) => id.startsWith(nitro.options.buildDir))) { return `_build/[name].mjs`; } // Try to match user defined routes or tasks const mainId = ids.at(-1); if (mainId) { const routeHandler = nitro.routing.routes.routes .flatMap((h) => h.data) .find((h) => h.handler === mainId); if (routeHandler?.route) { return `_routes/${routeToFsPath(routeHandler.route)}.mjs`; } const taskHandler = Object.entries(nitro.options.tasks).find( ([_, task]) => task.handler === mainId ); if (taskHandler) { return `_tasks/[name].mjs`; } } return `_chunks/[name].mjs`; } function joinPkgNames(moduleIds: string[]): string { const names = [ ...new Set( moduleIds .map((id) => pathToPkgName(id)) .filter(Boolean) .map((name) => name!.replace(/^@/, "").replace(/[/\\]/g, "__")) ), ].sort(); return names.join("+"); } export function routeToFsPath(route: string) { return ( route .split("/") .slice(1) .map((s) => s .replace(/:(\w+)/g, "[$1]") .replace(/\*+/g, "[...]") .replace(/[^a-zA-Z0-9_.[\]]/g, "_") ) .join("/") || "index" ); } ================================================ FILE: src/build/config.ts ================================================ import type { Nitro, NitroImportMeta } from "nitro/types"; import { defineEnv } from "unenv"; import { pkgDir } from "nitro/meta"; import { pathRegExp, toPathRegExp } from "../utils/regex.ts"; export type BaseBuildConfig = ReturnType; export function baseBuildConfig(nitro: Nitro) { // prettier-ignore const extensions: string[] = [".ts", ".mjs", ".js", ".json", ".node", ".tsx", ".jsx" ]; const isNodeless = nitro.options.node === false; const importMetaInjections: NitroImportMeta = { dev: nitro.options.dev, preset: nitro.options.preset, prerender: nitro.options.preset === "nitro-prerender", nitro: true, server: true, client: false, baseURL: nitro.options.baseURL, _asyncContext: nitro.options.experimental.asyncContext, _tasks: nitro.options.experimental.tasks, _websocket: nitro.options.features.websocket ?? nitro.options.experimental.websocket, }; const replacements = { ...Object.fromEntries( Object.entries(importMetaInjections).map(([key, val]) => [ `import.meta.${key}`, JSON.stringify(val), ]) ), ...nitro.options.replace, }; const { env } = defineEnv({ nodeCompat: isNodeless, resolve: true, presets: nitro.options.unenv, overrides: { alias: nitro.options.alias, }, }); const aliases = resolveAliases({ ...env.alias }); const noExternal: RegExp[] = getNoExternals(nitro); const ignoreWarningCodes = new Set([ "EVAL", "CIRCULAR_DEPENDENCY", "THIS_IS_UNDEFINED", "EMPTY_BUNDLE", ]); return { extensions, isNodeless, replacements, env, aliases, noExternal, ignoreWarningCodes, }; } function getNoExternals(nitro: Nitro): RegExp[] { const noExternal: RegExp[] = [ /\.[mc]?tsx?$/, /^(?:[\0#~.]|virtual:)/, new RegExp("^" + pathRegExp(pkgDir) + "(?!.*node_modules)"), ...[ nitro.options.rootDir, ...nitro.options.scanDirs.filter( (dir) => dir.includes("node_modules") || !dir.startsWith(nitro.options.rootDir) ), ].map((dir) => new RegExp("^" + pathRegExp(dir) + "(?!.*node_modules)")), ]; if (nitro.options.wasm !== false) { noExternal.push(/\.wasm$/); } if (Array.isArray(nitro.options.noExternals)) { noExternal.push( ...nitro.options.noExternals .filter(Boolean) .map((item) => toPathRegExp(item as string | RegExp)) ); } return noExternal.sort((a, b) => a.source.length - b.source.length); } export function resolveAliases(_aliases: Record) { // Sort aliases from specific to general (ie. fs/promises before fs) const aliases = Object.fromEntries( Object.entries(_aliases).sort( ([a], [b]) => b.split("/").length - a.split("/").length || b.length - a.length ) ); // Resolve alias values in relation to each other for (const key in aliases) { for (const alias in aliases) { if (!["~", "@", "#"].includes(alias[0])) { continue; } if (alias === "@" && !aliases[key].startsWith("@/")) { continue; } // Don't resolve @foo/bar if (aliases[key].startsWith(alias)) { aliases[key] = aliases[alias] + aliases[key].slice(alias.length); } } } return aliases; } ================================================ FILE: src/build/info.ts ================================================ import type { Nitro, NitroBuildInfo, WorkerAddress } from "nitro/types"; import { join, relative, resolve } from "pathe"; import { version as nitroVersion } from "nitro/meta"; import { presetsWithConfig } from "../presets/_types.gen.ts"; import { writeFile } from "../utils/fs.ts"; import { mkdir, readFile, stat } from "node:fs/promises"; import { dirname } from "node:path"; import type { RolldownOutput } from "rolldown"; import type { RollupOutput } from "rollup"; const NITRO_WELLKNOWN_DIR = "node_modules/.nitro"; export async function getBuildInfo( root: string ): Promise< | { outputDir?: undefined; buildInfo?: undefined } | { outputDir: string; buildInfo?: NitroBuildInfo } > { const outputDir = await findLastBuildDir(root); const isDir = await stat(outputDir) .then((s) => s.isDirectory()) .catch(() => false); if (!isDir) { return {}; } const buildInfo = (await readFile(resolve(outputDir, "nitro.json"), "utf8") .then(JSON.parse) .catch(() => undefined)) as NitroBuildInfo | undefined; return { outputDir, buildInfo, }; } export async function findLastBuildDir(root: string): Promise { const lastBuildLink = join(root, NITRO_WELLKNOWN_DIR, "last-build.json"); const outputDir = await readFile(lastBuildLink, "utf8") .then(JSON.parse) .then((data) => resolve(lastBuildLink, data.outputDir || "../../../.output")) .catch(() => resolve(root, ".output")); return outputDir; } export async function writeBuildInfo( nitro: Nitro, output: RolldownOutput | RollupOutput | undefined ): Promise { const serverEntryName = output?.output?.find((o) => o.type === "chunk" && o.isEntry)?.fileName; const buildInfoPath = resolve(nitro.options.output.dir, "nitro.json"); const buildInfo: NitroBuildInfo = { date: new Date().toJSON(), preset: nitro.options.preset, framework: nitro.options.framework, versions: { nitro: nitroVersion, }, serverEntry: serverEntryName ? relative(nitro.options.output.dir, join(nitro.options.output.serverDir, serverEntryName)) : undefined, publicDir: relative( nitro.options.output.dir, resolve(nitro.options.output.dir, nitro.options.output.publicDir) ), commands: { preview: nitro.options.commands.preview, deploy: nitro.options.commands.deploy, }, config: { ...Object.fromEntries(presetsWithConfig.map((key) => [key, nitro.options[key]])), }, }; await writeFile(buildInfoPath, JSON.stringify(buildInfo, null, 2), true); const lastBuild = join(nitro.options.rootDir, NITRO_WELLKNOWN_DIR, "last-build.json"); await mkdir(dirname(lastBuild), { recursive: true }); await writeFile( lastBuild, JSON.stringify({ outputDir: relative(lastBuild, nitro.options.output.dir), }) ); return buildInfo; } export async function writeDevBuildInfo(nitro: Nitro, addr?: WorkerAddress): Promise { const buildInfoPath = join(nitro.options.rootDir, NITRO_WELLKNOWN_DIR, "nitro.dev.json"); const buildInfo: NitroBuildInfo = { date: new Date().toJSON(), preset: nitro.options.preset, framework: nitro.options.framework, versions: { nitro: nitroVersion, }, dev: { pid: process.pid, workerAddress: addr, }, }; await writeFile(buildInfoPath, JSON.stringify(buildInfo, null, 2)); } ================================================ FILE: src/build/plugins/externals.ts ================================================ import type { Plugin } from "rollup"; import type { PackageJson } from "pkg-types"; import type { ExternalsTraceOptions } from "nf3"; import { pathToFileURL } from "node:url"; import { builtinModules, createRequire } from "node:module"; import { isAbsolute, join } from "pathe"; import { resolveModulePath } from "exsolve"; import { escapeRegExp, toPathRegExp } from "../../utils/regex.ts"; import consola from "consola"; export type ExternalsOptions = { rootDir: string; conditions: string[]; exclude?: (string | RegExp)[]; include?: (string | RegExp)[]; trace?: false | Omit; }; const PLUGIN_NAME = "nitro:externals"; export function externals(opts: ExternalsOptions): Plugin { const include: RegExp[] | undefined = opts?.include ? opts.include.map((p) => toPathRegExp(p)) : undefined; const exclude: RegExp[] = [ /^(?:[\0#~.]|[a-z0-9]{2,}:)|\?/, ...(opts?.exclude || []).map((p) => toPathRegExp(p)), ]; const filter = (id: string) => { // Most match at least one include (if specified) if (include && !include.some((r) => r.test(id))) { return false; } // Most not match any exclude if (exclude.some((r) => r.test(id))) { return false; } return true; }; const tryResolve = (id: string, from: string | undefined) => resolveModulePath(id, { try: true, from: from && isAbsolute(from) ? from : opts.rootDir, conditions: opts.conditions, }); const tracedPaths = new Set(); if (include && include.length === 0) { return { name: PLUGIN_NAME, }; } return { name: PLUGIN_NAME, resolveId: { order: "pre", filter: { id: { exclude, include } }, async handler(id, importer, rOpts) { // Externalize built-in modules with normalized prefix if (builtinModules.includes(id)) { return { resolvedBy: PLUGIN_NAME, external: true, id: id.includes(":") ? id : `node:${id}`, }; } // Skip nested rollup-node resolutions if (rOpts.custom?.["node-resolve"]) { return null; } // Resolve by other resolvers let resolved = await this.resolve(id, importer, rOpts); // Skip rolldown-plugin-commonjs resolver for externals const cjsResolved = resolved?.meta?.commonjs?.resolved; if (cjsResolved) { if (!filter(cjsResolved.id)) { return resolved; // Bundled and wrapped by CJS plugin } resolved = cjsResolved /* non-wrapped */; } // Check if not resolved or explicitly marked as excluded if (!resolved?.id || !filter(resolved!.id)) { return resolved; } // Normalize to absolute path let resolvedPath = resolved.id; if (!isAbsolute(resolvedPath)) { resolvedPath = tryResolve(resolvedPath, importer) || resolvedPath; } // Tracing mode if (opts.trace) { let importId = toImport(id) || toImport(resolvedPath); if (!importId) { return resolved; } if (!tryResolve(importId, importer)) { const guessed = await guessSubpath(resolvedPath, opts.conditions); if (!guessed) { return resolved; } importId = guessed; } tracedPaths.add(resolvedPath); return { ...resolved, resolvedBy: PLUGIN_NAME, external: true, id: importId, }; } // Resolve as absolute path external return { ...resolved, resolvedBy: PLUGIN_NAME, external: true, id: isAbsolute(resolvedPath) ? pathToFileURL(resolvedPath).href // windows compat : resolvedPath, }; }, }, buildEnd: { order: "post", async handler() { if (!opts.trace || tracedPaths.size === 0) { return; } const { traceNodeModules } = await import("nf3"); const traceTime = Date.now(); let traceFilesCount = 0; let tracedPkgsCount = 0; await traceNodeModules([...tracedPaths], { ...opts.trace, conditions: opts.conditions, rootDir: opts.rootDir, writePackageJson: true, // deno compat hooks: { tracedFiles(result) { traceFilesCount = Object.keys(result).length; }, tracedPackages: (pkgs) => { tracedPkgsCount = Object.keys(pkgs).length; consola.info( `Tracing dependencies:\n${Object.entries(pkgs) .map( ([name, versions]) => `- \`${name}\` (${Object.keys(versions.versions).join(", ")})` ) .join("\n")}` ); }, }, }); consola.success( `Traced ${tracedPkgsCount} dependencies (${traceFilesCount} files) in ${Date.now() - traceTime}ms.` ); consola.info( `Ensure your production environment matches the builder OS and architecture (\`${process.platform}-${process.arch}\`) to avoid native module issues.` ); }, }, }; } // ---- Internal utils ---- const NODE_MODULES_RE = /^(?.+[\\/]node_modules[\\/])(?[^@\\/]+|@[^\\/]+[\\/][^\\/]+)(?:[\\/](?.+))?$/; const IMPORT_RE = /^(?!\.)(?[^@/\\]+|@[^/\\]+[/\\][^/\\]+)(?:[/\\](?.+))?$/; function toImport(id: string): string | undefined { if (isAbsolute(id)) { const { name, subpath } = NODE_MODULES_RE.exec(id)?.groups || ({} as Record); if (name && subpath) { return join(name, subpath); } } else if (IMPORT_RE.test(id)) { return id; } } function guessSubpath(path: string, conditions: string[]): string | undefined { const { dir, name, subpath } = NODE_MODULES_RE.exec(path)?.groups || {}; if (!dir || !name || !subpath) { return; } const pkgDir = join(dir, name) + "/"; const exports = getPkgJSON(pkgDir)?.exports; if (!exports || typeof exports !== "object") { return; } for (const e of flattenExports(exports)) { if (!conditions.includes(e.condition || "default")) { continue; } if (e.fsPath === subpath) { return join(name, e.subpath); } if (e.fsPath.includes("*")) { const fsPathRe = new RegExp( "^" + escapeRegExp(e.fsPath).replace(String.raw`\*`, "(.+?)") + "$" ); if (fsPathRe.test(subpath)) { const matched = fsPathRe.exec(subpath)?.[1]; if (matched) { return join(name, e.subpath.replace("*", matched)); } } } } } function getPkgJSON(dir: string): PackageJson | undefined { const cache = ((getPkgJSON as any)._cache ||= new Map()); if (cache.has(dir)) { return cache.get(dir); } try { const pkg = createRequire(dir)("./package.json"); cache.set(dir, pkg); return pkg; } catch { /* ignore */ } } // Based on mlly function flattenExports( exports: Exclude = {}, parentSubpath = "./" ): { subpath: string; fsPath: string; condition?: string }[] { return Object.entries(exports).flatMap(([key, value]) => { const [subpath, condition] = key.startsWith(".") ? [key.slice(1)] : [undefined, key]; const _subPath = join(parentSubpath, subpath || ""); if (typeof value === "string") { return [{ subpath: _subPath, fsPath: value.replace(/^\.\//, ""), condition }]; } return typeof value === "object" ? flattenExports(value, _subPath) : []; }); } ================================================ FILE: src/build/plugins/oxc.ts ================================================ import type { MinifyOptions } from "rolldown/experimental"; import type { OXCOptions } from "nitro/types"; import type { Plugin } from "rollup"; export async function oxc( options: OXCOptions & { sourcemap: boolean; minify: boolean | MinifyOptions } ): Promise { const { minifySync, transformSync } = await import("rolldown/utils"); return { name: "nitro:oxc", transform: { filter: { id: /^(?!.*\/node_modules\/).*\.m?[jt]sx?$/, }, handler(code, id) { const res = transformSync(id, code, { sourcemap: options.sourcemap, tsconfig: false, ...options.transform, }); if (res.errors?.length > 0) { this.error(res.errors.join("\n")); } return res; }, }, renderChunk(code, chunk) { if (options.minify) { return minifySync(chunk.fileName, code, { sourcemap: options.sourcemap, ...(typeof options.minify === "object" ? options.minify : {}), }); } }, }; } ================================================ FILE: src/build/plugins/raw.ts ================================================ import { promises as fsp } from "node:fs"; import mime from "mime"; import type { Plugin } from "rollup"; const HELPER_ID = "virtual:nitro-raw-helpers"; const RESOLVED_PREFIX = "virtual:nitro:raw:"; const PREFIX = "raw:"; export function raw(): Plugin { return { name: "nitro:raw", resolveId: { order: "pre", filter: { id: [new RegExp(`^${HELPER_ID}$`), new RegExp(`^${PREFIX}`)], }, async handler(id, importer, resolveOpts) { if (id === HELPER_ID) { return id; } if (id.startsWith(PREFIX)) { const resolvedId = (await this.resolve(id.slice(PREFIX.length), importer, resolveOpts)) ?.id; if (!resolvedId) { return null; } return { id: RESOLVED_PREFIX + resolvedId }; } }, }, load: { order: "pre", filter: { id: [new RegExp(`^${HELPER_ID}$`), new RegExp(`^${RESOLVED_PREFIX}`)], }, handler(id) { if (id === HELPER_ID) { return getHelpers(); } if (id.startsWith(RESOLVED_PREFIX)) { // this.addWatchFile(id.substring(RESOLVED_PREFIX.length)); return fsp.readFile(id.slice(RESOLVED_PREFIX.length), isBinary(id) ? "binary" : "utf8"); } }, }, transform: { order: "pre", filter: { id: new RegExp(`^${RESOLVED_PREFIX}`), }, handler(code, id) { const path = id.slice(RESOLVED_PREFIX.length); if (isBinary(id)) { const serialized = Buffer.from(code, "binary").toString("base64"); return { code: `import {base64ToUint8Array } from "${HELPER_ID}" \n export default base64ToUint8Array("${serialized}")`, map: rawAssetMap(path), }; } return { code: `export default ${JSON.stringify(code)}`, map: rawAssetMap(path), moduleType: "js", }; }, }, }; } function isBinary(id: string) { const idMime = mime.getType(id) || ""; if (idMime.startsWith("text/")) { return false; } if (/application\/(json|sql|xml|yaml)/.test(idMime)) { return false; } return true; } function getHelpers() { return /* js */ ` export function base64ToUint8Array(str) { const data = atob(str); const size = data.length; const bytes = new Uint8Array(size); for (let i = 0; i < size; i++) { bytes[i] = data.charCodeAt(i); } return bytes; } `; } function rawAssetMap(id: string) { return { version: 3, file: id, sources: [id], sourcesContent: [], names: [], mappings: "", }; } ================================================ FILE: src/build/plugins/route-meta.ts ================================================ import { readFile } from "node:fs/promises"; import { isAbsolute } from "pathe"; import type { Expression, Literal } from "estree"; import type { Nitro, NitroEventHandler } from "nitro/types"; import type { Plugin } from "rollup"; import { escapeRegExp } from "../../utils/regex.ts"; const PREFIX = "\0nitro:route-meta:"; export async function routeMeta(nitro: Nitro) { const { transformSync } = await import("rolldown/utils"); return { name: "nitro:route-meta", resolveId: { // eslint-disable-next-line no-control-regex filter: { id: /^(?!\u0000)(.+)\?meta$/ }, async handler(id, importer, resolveOpts) { if (id.endsWith("?meta")) { const resolved = await this.resolve(id.replace("?meta", ""), importer, resolveOpts); if (!resolved) { return; } return PREFIX + resolved.id; } }, }, load: { filter: { id: new RegExp(`^${escapeRegExp(PREFIX)}`), }, handler(id) { if (id.startsWith(PREFIX)) { const fullPath = id.slice(PREFIX.length); if (isAbsolute(fullPath)) { return readFile(fullPath, { encoding: "utf8" }); } else { return "export default undefined;"; } } }, }, transform: { filter: { id: new RegExp(`^${escapeRegExp(PREFIX)}`), }, async handler(code, id) { let meta: NitroEventHandler["meta"] | null = null; try { const transformRes = transformSync(id, code, { tsconfig: false }); if (transformRes.errors?.length > 0) { for (const error of transformRes.errors) { this.warn(error); } return { code: `export default {};`, map: null, }; } const ast = this.parse(transformRes.code); for (const node of ast.body) { if ( node.type === "ExpressionStatement" && node.expression.type === "CallExpression" && node.expression.callee.type === "Identifier" && node.expression.callee.name === "defineRouteMeta" && node.expression.arguments.length === 1 ) { meta = astToObject(node.expression.arguments[0] as any); break; } } } catch (error) { nitro.logger.warn(`[handlers-meta] Cannot extra route meta for: ${id}: ${error}`); } return { code: `export default ${JSON.stringify(meta)};`, map: null, }; }, }, } satisfies Plugin; } function astToObject(node: Expression | Literal): any { switch (node.type) { case "ObjectExpression": { const obj: Record = {}; for (const prop of node.properties) { if (prop.type === "Property") { const key = (prop.key as any).name ?? (prop.key as any).value; obj[key] = astToObject(prop.value as any); } } return obj; } case "ArrayExpression": { return node.elements.map((el) => astToObject(el as any)).filter((obj) => obj !== undefined); } case "Literal": { return node.value; } // No default } } ================================================ FILE: src/build/plugins/server-main.ts ================================================ import type { Nitro } from "nitro/types"; import type { Plugin } from "rollup"; export function serverMain(nitro: Nitro): Plugin { return { name: "nitro:server-main", renderChunk(code, chunk) { if (chunk.isEntry) { return { code: `globalThis.__nitro_main__ = import.meta.url; ${code}`, map: null, }; } }, }; } ================================================ FILE: src/build/plugins/sourcemap-min.ts ================================================ import type { ExistingRawSourceMap, Plugin } from "rollup"; export function sourcemapMinify() { return { name: "nitro:sourcemap-minify", generateBundle(_options, bundle) { for (const [key, asset] of Object.entries(bundle)) { // Only process sourcemaps if (!key.endsWith(".map") || !("source" in asset) || typeof asset.source !== "string") { continue; } // Parse sourcemap const sourcemap: ExistingRawSourceMap = JSON.parse(asset.source); // Remove sourcesContent delete sourcemap.sourcesContent; // Remove x_google_ignoreList delete sourcemap.x_google_ignoreList; if ((sourcemap.sources || []).every((s) => s.includes("node_modules"))) { sourcemap.mappings = ""; // required key } asset.source = JSON.stringify(sourcemap); } }, } satisfies Plugin; } ================================================ FILE: src/build/plugins/virtual.ts ================================================ import type { Plugin, ResolvedId } from "rollup"; import { pathRegExp } from "../../utils/regex.ts"; import { runtimeDependencies, runtimeDir } from "nitro/meta"; export type VirtualModule = { id: string; moduleSideEffects?: boolean; template: string | (() => string | Promise); }; export function virtual(input: VirtualModule[]): Plugin { const modules = new Map< string, { module: VirtualModule; render: () => string | Promise } >(); for (const mod of input) { const render = () => (typeof mod.template === "function" ? mod.template() : mod.template); modules.set(mod.id, { module: mod, render }); } const include: RegExp[] = [/^#nitro\/virtual/]; const extraIds = [...modules.keys()].filter((key) => !key.startsWith("#nitro/virtual")); if (extraIds.length > 0) { include.push(new RegExp(`^(${extraIds.map((id) => pathRegExp(id)).join("|")})$`)); } return { name: "nitro:virtual", api: { modules, }, resolveId: { order: "pre", filter: { id: include }, handler: (id) => { const mod = modules.get(id); if (mod) { return { id, moduleSideEffects: mod.module.moduleSideEffects ?? false, }; } }, }, load: { order: "pre", filter: { id: include }, handler: async (id) => { const mod = modules.get(id); if (!mod) { throw new Error(`Virtual module ${id} not found.`); } return { code: await mod.render(), map: null, }; }, }, }; } export function virtualDeps(): Plugin { const cache = new Map>(); return { name: "nitro:virtual-deps", resolveId: { order: "pre", filter: { id: new RegExp(`^(#nitro|${runtimeDependencies.map((dep) => pathRegExp(dep)).join("|")})`), }, handler(id, importer) { // https://github.com/rolldown/rolldown/issues/7529 if (!importer || !importer.startsWith("#nitro/virtual")) { return; } let resolved = cache.get(id); if (!resolved) { resolved = this.resolve(id, runtimeDir) .then((_resolved) => { cache.set(id, _resolved); return _resolved; }) .catch((error) => { cache.delete(id); throw error; }); } return resolved; }, }, }; } ================================================ FILE: src/build/plugins.ts ================================================ import type { Nitro } from "nitro/types"; import type { Plugin } from "rollup"; import type { BaseBuildConfig } from "./config.ts"; import { virtualTemplates } from "./virtual/_all.ts"; import replace from "@rollup/plugin-replace"; import { unwasm } from "unwasm/plugin"; import { routeMeta } from "./plugins/route-meta.ts"; import { serverMain } from "./plugins/server-main.ts"; import { virtual, virtualDeps } from "./plugins/virtual.ts"; import { sourcemapMinify } from "./plugins/sourcemap-min.ts"; import { raw } from "./plugins/raw.ts"; import { externals } from "./plugins/externals.ts"; export async function baseBuildPlugins(nitro: Nitro, base: BaseBuildConfig) { const plugins: Plugin[] = []; // Virtual const virtualPlugin = virtual(virtualTemplates(nitro, [...base.env.polyfill])); nitro.vfs = virtualPlugin.api.modules; plugins.push(virtualPlugin, virtualDeps()); // Auto imports if (nitro.options.imports) { const unimportPlugin = await import("unimport/unplugin"); plugins.push(unimportPlugin.default.rollup(nitro.options.imports) as Plugin); } // WASM loader if (nitro.options.wasm !== false) { plugins.push(unwasm(nitro.options.wasm || {})); } // Inject globalThis.__server_main__ plugins.push(serverMain(nitro)); // Raw Imports plugins.push(raw()); // Route meta if (nitro.options.experimental.openAPI) { plugins.push(await routeMeta(nitro)); } // Replace plugins.push( (replace as unknown as typeof replace.default)({ preventAssignment: true, values: base.replacements, }) ); // Externals (require Node.js compatible resolution) if (nitro.options.node && nitro.options.noExternals !== true) { const isDevOrPrerender = nitro.options.dev || nitro.options.preset === "nitro-prerender"; const { NodeNativePackages, NonBundleablePackages } = await import("nf3/db"); const traceDeps = [ ...new Set([ ...NodeNativePackages, ...NonBundleablePackages, ...(nitro.options.traceDeps || []), ]), ]; plugins.push( externals({ rootDir: nitro.options.rootDir, conditions: nitro.options.exportConditions!, exclude: [...base.noExternal], include: isDevOrPrerender ? undefined : [ new RegExp( `^(?:${traceDeps.join("|")})|[/\\\\]node_modules[/\\\\](?:${traceDeps.join("|")})(?:[/\\\\])` ), ], trace: isDevOrPrerender ? false : { outDir: nitro.options.output.serverDir }, }) ); } // Sourcemap minify if ( nitro.options.sourcemap && !nitro.options.dev && nitro.options.experimental.sourcemapMinify !== false ) { plugins.push(sourcemapMinify()); } return plugins; } ================================================ FILE: src/build/prepare.ts ================================================ import fsp from "node:fs/promises"; import type { Nitro } from "nitro/types"; export async function prepare(nitro: Nitro) { await prepareDir(nitro.options.output.dir); if (!nitro.options.noPublicDir) { await prepareDir(nitro.options.output.publicDir); } if (!nitro.options.static) { await prepareDir(nitro.options.output.serverDir); } } async function prepareDir(dir: string) { await fsp.rm(dir, { recursive: true, force: true }); await fsp.mkdir(dir, { recursive: true }); } ================================================ FILE: src/build/rolldown/build.ts ================================================ import { getRolldownConfig } from "./config.ts"; import type { Nitro } from "nitro/types"; import { watchDev } from "./dev.ts"; import { buildProduction } from "./prod.ts"; export async function rolldownBuild(nitro: Nitro) { await nitro.hooks.callHook("build:before", nitro); const config = await getRolldownConfig(nitro); await nitro.hooks.callHook("rollup:before", nitro, config as any); return nitro.options.dev ? watchDev(nitro, config) : buildProduction(nitro, config); } ================================================ FILE: src/build/rolldown/config.ts ================================================ import type { Nitro } from "nitro/types"; import type { OutputOptions, RolldownOptions, RolldownPlugin } from "rolldown"; import { baseBuildConfig } from "../config.ts"; import { baseBuildPlugins } from "../plugins.ts"; import { builtinModules } from "node:module"; import { defu } from "defu"; import { getChunkName, libChunkName, NODE_MODULES_RE } from "../chunks.ts"; export const getRolldownConfig = async (nitro: Nitro): Promise => { const base = baseBuildConfig(nitro); const tsc = nitro.options.typescript.tsConfig?.compilerOptions; let config: RolldownOptions = { platform: nitro.options.node ? "node" : "neutral", cwd: nitro.options.rootDir, input: nitro.options.entry, external: [...base.env.external, ...builtinModules, ...builtinModules.map((m) => `node:${m}`)], plugins: [...((await baseBuildPlugins(nitro, base)) as RolldownPlugin[])], resolve: { alias: base.aliases, extensions: base.extensions, conditionNames: nitro.options.exportConditions, }, transform: { inject: base.env.inject as Record, jsx: { runtime: tsc?.jsx === "react" ? "classic" : "automatic", pragma: tsc?.jsxFactory, pragmaFrag: tsc?.jsxFragmentFactory, importSource: tsc?.jsxImportSource, development: nitro.options.dev, }, }, onwarn(warning, warn) { if (!base.ignoreWarningCodes.has(warning.code || "")) { console.log(warning.code); warn(warning); } }, treeshake: { moduleSideEffects(id) { return nitro.options.moduleSideEffects.some((p) => id.startsWith(p)); }, }, optimization: { inlineConst: true, }, output: { format: "esm", entryFileNames: "index.mjs", chunkFileNames: (chunk) => getChunkName(chunk, nitro), codeSplitting: { groups: [{ test: NODE_MODULES_RE, name: (id) => libChunkName(id) }], }, dir: nitro.options.output.serverDir, inlineDynamicImports: nitro.options.inlineDynamicImports, // https://github.com/rolldown/rolldown/issues/7235 minify: nitro.options.minify ? true : "dce-only", sourcemap: nitro.options.sourcemap, sourcemapIgnoreList(relativePath) { return relativePath.includes("node_modules"); }, }, } satisfies RolldownOptions; config = defu( nitro.options.rolldownConfig, nitro.options.rollupConfig as RolldownOptions, config ); const outputConfig = config.output as OutputOptions; if (outputConfig.inlineDynamicImports || outputConfig.format === "iife") { delete outputConfig.inlineDynamicImports; outputConfig.codeSplitting = false; } return config as RolldownOptions; }; ================================================ FILE: src/build/rolldown/dev.ts ================================================ import type { Nitro } from "nitro/types"; import type { RolldownWatcher, RolldownOptions } from "rolldown"; import { watch as chokidarWatch } from "chokidar"; import { basename, join } from "pathe"; import { debounce } from "perfect-debounce"; import { scanHandlers } from "../../scan.ts"; import { writeTypes } from "../types.ts"; import { formatCompatibilityDate } from "compatx"; export async function watchDev(nitro: Nitro, config: RolldownOptions) { const rolldown = await import("rolldown"); let watcher: RolldownWatcher; async function load() { if (watcher) { await watcher.close(); } await scanHandlers(nitro); nitro.routing.sync(); watcher = startWatcher(nitro, config); await writeTypes(nitro); } const reload = debounce(load); const scanDirs = nitro.options.scanDirs.flatMap((dir) => [ join(dir, nitro.options.apiDir || "api"), join(dir, nitro.options.routesDir || "routes"), join(dir, "middleware"), join(dir, "plugins"), join(dir, "modules"), ]); const watchReloadEvents = new Set(["add", "addDir", "unlink", "unlinkDir"]); const scanDirsWatcher = chokidarWatch(scanDirs, { ignoreInitial: true, }).on("all", (event) => { if (watchReloadEvents.has(event)) { reload(); } }); const serverEntryRe = /^server\.[mc]?[jt]sx?$/; const rootDirWatcher = chokidarWatch(nitro.options.rootDir, { ignoreInitial: true, depth: 0, }).on("all", (event, path) => { if (watchReloadEvents.has(event) && serverEntryRe.test(basename(path))) { reload(); } }); nitro.hooks.hook("close", () => { watcher.close(); scanDirsWatcher.close(); rootDirWatcher.close(); }); nitro.hooks.hook("rollup:reload", () => reload()); nitro.logger.info( `Starting dev watcher (builder: \`rolldown\`, preset: \`${nitro.options.preset}\`, compatibility date: \`${formatCompatibilityDate(nitro.options.compatibilityDate)}\`)` ); await load(); function startWatcher(nitro: Nitro, config: RolldownOptions) { const watcher = rolldown.watch(config); let start: number; watcher.on("event", (event) => { // START > BUNDLE_START > BUNDLE_END > END // START > BUNDLE_START > ERROR > END switch (event.code) { case "START": { start = Date.now(); nitro.hooks.callHook("dev:start"); break; } case "BUNDLE_END": { nitro.hooks.callHook("compiled", nitro); if (nitro.options.logging.buildSuccess) { nitro.logger.success(`Server built`, start ? `in ${Date.now() - start}ms` : ""); } nitro.hooks.callHook("dev:reload"); break; } case "ERROR": { nitro.logger.error(event.error); nitro.hooks.callHook("dev:error", event.error); } } }); return watcher; } } ================================================ FILE: src/build/rolldown/prod.ts ================================================ import type { Nitro } from "nitro/types"; import type { OutputOptions, RolldownOptions } from "rolldown"; import { formatCompatibilityDate } from "compatx"; import { relative } from "pathe"; import { scanHandlers } from "../../scan.ts"; import { generateFSTree } from "../../utils/fs-tree.ts"; import { writeTypes } from "../types.ts"; import { writeBuildInfo } from "../info.ts"; import type { RolldownOutput } from "rolldown"; export async function buildProduction(nitro: Nitro, config: RolldownOptions) { const rolldown = await import("rolldown"); const buildStartTime = Date.now(); await scanHandlers(nitro); await writeTypes(nitro); let output: RolldownOutput | undefined; if (!nitro.options.static) { nitro.logger.info( `Building server (builder: \`rolldown\`, preset: \`${nitro.options.preset}\`, compatibility date: \`${formatCompatibilityDate(nitro.options.compatibilityDate)}\`)` ); const build = await rolldown.rolldown(config); output = (await build.write(config.output as OutputOptions)) as RolldownOutput; } const buildInfo = await writeBuildInfo(nitro, output); if (!nitro.options.static) { if (nitro.options.logging.buildSuccess) { nitro.logger.success(`Server built in ${Date.now() - buildStartTime}ms`); } if (nitro.options.logLevel > 1) { process.stdout.write( (await generateFSTree(nitro.options.output.serverDir, { compressedSizes: nitro.options.logging.compressedSizes, })) || "" ); } } await nitro.hooks.callHook("compiled", nitro); // Show deploy and preview hints const rOutput = relative(process.cwd(), nitro.options.output.dir); const rewriteRelativePaths = (input: string) => { return input.replace(/([\s:])\.\/(\S*)/g, `$1${rOutput}/$2`); }; nitro.logger.success("You can preview this build using `npx nitro preview`"); if (buildInfo.commands!.deploy) { nitro.logger.success( rewriteRelativePaths("You can deploy this build using `npx nitro deploy --prebuilt`") ); } } ================================================ FILE: src/build/rollup/build.ts ================================================ import { getRollupConfig } from "./config.ts"; import type { Nitro } from "nitro/types"; import { watchDev } from "./dev.ts"; import { buildProduction } from "./prod.ts"; export async function rollupBuild(nitro: Nitro) { await nitro.hooks.callHook("build:before", nitro); const config = await getRollupConfig(nitro); await nitro.hooks.callHook("rollup:before", nitro, config); return nitro.options.dev ? watchDev(nitro, config) : buildProduction(nitro, config); } ================================================ FILE: src/build/rollup/config.ts ================================================ import type { Nitro, RollupConfig } from "nitro/types"; import { defu } from "defu"; import alias from "@rollup/plugin-alias"; import commonjs from "@rollup/plugin-commonjs"; import inject from "@rollup/plugin-inject"; import json from "@rollup/plugin-json"; import { nodeResolve } from "@rollup/plugin-node-resolve"; import { oxc } from "../plugins/oxc.ts"; import { baseBuildConfig } from "../config.ts"; import { baseBuildPlugins } from "../plugins.ts"; import { getChunkName, libChunkName, NODE_MODULES_RE } from "../chunks.ts"; export const getRollupConfig = async (nitro: Nitro): Promise => { const base = baseBuildConfig(nitro); const tsc = nitro.options.typescript.tsConfig?.compilerOptions; let config: RollupConfig = { input: nitro.options.entry, external: [...base.env.external], plugins: [ ...(await baseBuildPlugins(nitro, base)), await oxc({ sourcemap: !!nitro.options.sourcemap, minify: nitro.options.minify ? { ...nitro.options.oxc?.minify } : false, transform: { target: "esnext", // @ts-expect-error TODO: does option exists? cwd: nitro.options.rootDir, ...nitro.options.oxc?.transform, jsx: { runtime: tsc?.jsx === "react" ? "classic" : "automatic", pragma: tsc?.jsxFactory, pragmaFrag: tsc?.jsxFragmentFactory, importSource: tsc?.jsxImportSource, development: nitro.options.dev, ...nitro.options.oxc?.transform?.jsx, }, }, }), alias({ entries: base.aliases }), nodeResolve({ extensions: base.extensions, preferBuiltins: !!nitro.options.node, rootDir: nitro.options.rootDir, exportConditions: nitro.options.exportConditions, }), (commonjs as unknown as typeof commonjs.default)({ ...nitro.options.commonJS, }), (json as unknown as typeof json.default)(), (inject as unknown as typeof inject.default)(base.env.inject), ], onwarn(warning, rollupWarn) { if (!base.ignoreWarningCodes.has(warning.code || "")) { rollupWarn(warning); } }, treeshake: { moduleSideEffects(id) { return nitro.options.moduleSideEffects.some((p) => id.startsWith(p)); }, }, output: { format: "esm", entryFileNames: "index.mjs", chunkFileNames: (chunk) => getChunkName(chunk, nitro), dir: nitro.options.output.serverDir, inlineDynamicImports: nitro.options.inlineDynamicImports, generatedCode: { constBindings: true }, sourcemap: nitro.options.sourcemap, sourcemapExcludeSources: true, sourcemapIgnoreList: (id) => id.includes("node_modules"), manualChunks(id: string) { if (NODE_MODULES_RE.test(id)) { return libChunkName(id); } }, }, } satisfies RollupConfig; config = defu(nitro.options.rollupConfig as any, config); const outputConfig = config.output as NonNullable; if (outputConfig.inlineDynamicImports || outputConfig.format === "iife") { delete outputConfig.manualChunks; } return config; }; ================================================ FILE: src/build/rollup/dev.ts ================================================ import type { Nitro, RollupConfig } from "nitro/types"; import type { RollupWatcher } from "rollup"; import { watch as chokidarWatch } from "chokidar"; import { defu } from "defu"; import { basename, join } from "pathe"; import { debounce } from "perfect-debounce"; import { scanHandlers } from "../../scan.ts"; import { formatRollupError } from "./error.ts"; import { writeTypes } from "../types.ts"; import { formatCompatibilityDate } from "compatx"; export async function watchDev(nitro: Nitro, rollupConfig: RollupConfig) { const rollup = await import("rollup"); let rollupWatcher: RollupWatcher; async function load() { if (rollupWatcher) { await rollupWatcher.close(); } await scanHandlers(nitro); nitro.routing.sync(); rollupWatcher = startRollupWatcher(nitro, rollupConfig); await writeTypes(nitro); } const reload = debounce(load); const scanDirs = nitro.options.scanDirs.flatMap((dir) => [ join(dir, nitro.options.apiDir || "api"), join(dir, nitro.options.routesDir || "routes"), join(dir, "middleware"), join(dir, "plugins"), join(dir, "modules"), ]); const watchReloadEvents = new Set(["add", "addDir", "unlink", "unlinkDir"]); const scanDirsWatcher = chokidarWatch(scanDirs, { ignoreInitial: true, }).on("all", (event, path, stat) => { if (watchReloadEvents.has(event)) { reload(); } }); const serverEntryRe = /^server\.[mc]?[jt]sx?$/; const rootDirWatcher = chokidarWatch(nitro.options.rootDir, { ignoreInitial: true, depth: 0, }).on("all", (event, path) => { if (watchReloadEvents.has(event) && serverEntryRe.test(basename(path))) { reload(); } }); nitro.hooks.hook("close", () => { rollupWatcher.close(); scanDirsWatcher.close(); rootDirWatcher.close(); }); nitro.hooks.hook("rollup:reload", () => reload()); nitro.logger.info( `Starting dev watcher (builder: \`rollup\`, preset: \`${nitro.options.preset}\`, compatibility date: \`${formatCompatibilityDate(nitro.options.compatibilityDate)}\`)` ); await load(); function startRollupWatcher(nitro: Nitro, rollupConfig: RollupConfig) { const watcher = rollup.watch( defu(rollupConfig, { watch: { chokidar: nitro.options.watchOptions, }, }) ); let start: number; watcher.on("event", (event) => { // START > BUNDLE_START > BUNDLE_END > END // START > BUNDLE_START > ERROR > END switch (event.code) { case "START": { start = Date.now(); nitro.hooks.callHook("dev:start"); break; } case "BUNDLE_END": { nitro.hooks.callHook("compiled", nitro); if (nitro.options.logging.buildSuccess) { nitro.logger.success(`Server built`, start ? `in ${Date.now() - start}ms` : ""); } nitro.hooks.callHook("dev:reload"); break; } case "ERROR": { nitro.logger.error(formatRollupError(event.error)); nitro.hooks.callHook("dev:error", event.error); } } }); return watcher; } } ================================================ FILE: src/build/rollup/error.ts ================================================ import { isAbsolute, relative } from "pathe"; import type rollup from "rollup"; export function formatRollupError(_error: rollup.RollupError) { try { const logs: string[] = [_error.toString()]; const errors = (_error as any)?.errors || [_error as rollup.RollupError]; for (const error of errors) { const id = (error as any).path || error.id || (_error as rollup.RollupError).id; let path = isAbsolute(id) ? relative(process.cwd(), id) : id; const location = (error as rollup.RollupError).loc; if (location) { path += `:${location.line}:${location.column}`; } const text = (error as rollup.RollupError).frame; logs.push(`Rollup error while processing \`${path}\`` + text ? "\n\n" + text : ""); } return logs.join("\n"); } catch { return _error?.toString(); } } ================================================ FILE: src/build/rollup/prod.ts ================================================ import type { Nitro, RollupConfig } from "nitro/types"; import { formatCompatibilityDate } from "compatx"; import { scanHandlers } from "../../scan.ts"; import { generateFSTree } from "../../utils/fs-tree.ts"; import { writeTypes } from "../types.ts"; import { writeBuildInfo } from "../info.ts"; import { formatRollupError } from "./error.ts"; import type { RollupOutput } from "rollup"; export async function buildProduction(nitro: Nitro, rollupConfig: RollupConfig) { const rollup = await import("rollup"); const buildStartTime = Date.now(); await scanHandlers(nitro); await writeTypes(nitro); let output: RollupOutput | undefined; if (!nitro.options.static) { nitro.logger.info( `Building server (builder: \`rollup\`, preset: \`${nitro.options.preset}\`, compatibility date: \`${formatCompatibilityDate(nitro.options.compatibilityDate)}\`)` ); const build = await rollup.rollup(rollupConfig).catch((error) => { nitro.logger.error(formatRollupError(error)); throw error; }); output = await build.write(rollupConfig.output!); } const buildInfo = await writeBuildInfo(nitro, output); if (!nitro.options.static) { if (nitro.options.logging.buildSuccess) { nitro.logger.success(`Server built in ${Date.now() - buildStartTime}ms`); } if (nitro.options.logLevel > 1) { process.stdout.write( (await generateFSTree(nitro.options.output.serverDir, { compressedSizes: nitro.options.logging.compressedSizes, })) || "" ); } } await nitro.hooks.callHook("compiled", nitro); // Show deploy and preview hints nitro.logger.success("You can preview this build using `npx nitro preview`"); if (buildInfo.commands!.deploy) { nitro.logger.success("You can deploy this build using `npx nitro deploy --prebuilt`"); } } ================================================ FILE: src/build/types.ts ================================================ import { existsSync, promises as fsp } from "node:fs"; import { defu } from "defu"; import { lookupNodeModuleSubpath, parseNodeModulePath } from "mlly"; import { resolveModulePath } from "exsolve"; import { isDirectory, resolveNitroPath, writeFile } from "../utils/fs.ts"; import { runtimeDir } from "nitro/meta"; import type { Nitro, NitroTypes } from "nitro/types"; import { dirname, isAbsolute, join, resolve } from "pathe"; import { relative } from "pathe"; import { resolveAlias } from "pathe/utils"; import type { TSConfig } from "pkg-types"; import type { JSValue } from "untyped"; import { generateTypes, resolveSchema } from "untyped"; import { toExports } from "unimport"; export async function writeTypes(nitro: Nitro) { const types: NitroTypes = { routes: {}, }; const generatedTypesDir = resolve( nitro.options.rootDir, nitro.options.typescript.generatedTypesDir || "node_modules/.nitro/types" ); const middleware = [...nitro.scannedHandlers, ...nitro.options.handlers]; for (const mw of middleware) { if (typeof mw.handler !== "string" || !mw.route) { continue; } const relativePath = relative( generatedTypesDir, resolveNitroPath(mw.handler, nitro.options) ).replace(/\.(js|mjs|cjs|ts|mts|cts|tsx|jsx)$/, ""); const method = mw.method || "default"; types.routes[mw.route] ??= {}; types.routes[mw.route][method] ??= []; types.routes[mw.route][method]!.push( `Simplify>>>` ); } let autoImportedTypes: string[] = []; let autoImportExports = ""; if (nitro.unimport) { await nitro.unimport.init(); // TODO: fully resolve utils exported from `#imports` const allImports = await nitro.unimport.getImports(); autoImportExports = toExports(allImports).replace( /#internal\/nitro/g, relative(generatedTypesDir, runtimeDir) ); const resolvedImportPathMap = new Map(); for (const i of allImports) { const from = i.typeFrom || i.from; if (resolvedImportPathMap.has(from)) { continue; } let path = resolveAlias(from, nitro.options.alias); if (!isAbsolute(path)) { const resolvedPath = resolveModulePath(from, { try: true, from: nitro.options.rootDir, conditions: ["type", "node", "import"], suffixes: ["", "/index"], extensions: [".mjs", ".cjs", ".js", ".mts", ".cts", ".ts"], }); if (resolvedPath) { const { dir, name } = parseNodeModulePath(resolvedPath); if (!dir || !name) { path = resolvedPath; } else { const subpath = await lookupNodeModuleSubpath(resolvedPath); path = join(dir, name, subpath || ""); } } } if (existsSync(path) && !(await isDirectory(path))) { path = path.replace(/\.[a-z]+$/, ""); } if (isAbsolute(path)) { path = relative(generatedTypesDir, path); } resolvedImportPathMap.set(from, path); } autoImportedTypes = [ nitro.options.imports && nitro.options.imports.autoImport !== false ? ( await nitro.unimport.generateTypeDeclarations({ exportHelper: false, resolvePath: (i) => { const from = i.typeFrom || i.from; return resolvedImportPathMap.get(from) ?? from; }, }) ).trim() : "", ]; } const generateRoutes = () => [ "// Generated by nitro", 'import type { Serialize, Simplify } from "nitro/types";', 'declare module "nitro/types" {', " type Awaited = T extends PromiseLike ? Awaited : T", " interface InternalApi {", ...Object.entries(types.routes).map(([path, methods]) => [ ` '${path}': {`, ...Object.entries(methods).map( ([method, types]) => ` '${method}': ${types.join(" | ")}` ), " }", ].join("\n") ), " }", "}", // Makes this a module for augmentation purposes "export {}", ]; const config = [ "// Generated by nitro", /* ts */ `declare module "nitro/types" {`, nitro.options.typescript.generateRuntimeConfigTypes ? generateTypes( await resolveSchema( Object.fromEntries( Object.entries(nitro.options.runtimeConfig).filter( ([key]) => !["app", "nitro"].includes(key) ) ) as Record ), { interfaceName: "NitroRuntimeConfig", addExport: false, addDefaults: false, allowExtraKeys: false, indentation: 2, } ) : "", `}`, // Makes this a module for augmentation purposes "export {}", ]; const declarations = [ // local nitro augmentations '/// ', '/// ', // global server auto-imports '/// ', ]; const buildFiles: { path: string; contents: string | (() => string) }[] = []; buildFiles.push({ path: join(generatedTypesDir, "nitro-routes.d.ts"), contents: () => generateRoutes().join("\n"), }); buildFiles.push({ path: join(generatedTypesDir, "nitro-config.d.ts"), contents: config.join("\n"), }); buildFiles.push({ path: join(generatedTypesDir, "nitro-imports.d.ts"), contents: [...autoImportedTypes, autoImportExports || "export {}"].join("\n"), }); buildFiles.push({ path: join(generatedTypesDir, "nitro.d.ts"), contents: declarations.join("\n"), }); if (nitro.options.typescript.generateTsConfig) { const tsConfigPath = resolve( generatedTypesDir, nitro.options.typescript.tsconfigPath || "tsconfig.json" ); const tsconfigDir = dirname(tsConfigPath); const tsConfig: TSConfig = defu(nitro.options.typescript.tsConfig, { compilerOptions: { /* Base options: */ esModuleInterop: true, allowSyntheticDefaultImports: true, skipLibCheck: true, target: "ESNext", allowJs: true, resolveJsonModule: true, moduleDetection: "force", isolatedModules: true, verbatimModuleSyntax: true, allowImportingTsExtensions: true, /* Strictness */ strict: nitro.options.typescript.strict, noUncheckedIndexedAccess: true, noImplicitOverride: true, forceConsistentCasingInFileNames: true, /* If NOT transpiling with TypeScript: */ module: "Preserve", jsx: "preserve", jsxFactory: "h", jsxFragmentFactory: "Fragment", paths: { "#imports": [relativeWithDot(tsconfigDir, join(generatedTypesDir, "nitro-imports"))], }, }, include: [ relativeWithDot(tsconfigDir, join(generatedTypesDir, "nitro.d.ts")).replace( /^(?=[^.])/, "./" ), join(relativeWithDot(tsconfigDir, nitro.options.rootDir), "**/*"), ...(!nitro.options.serverDir || nitro.options.serverDir === nitro.options.rootDir ? [] : [join(relativeWithDot(tsconfigDir, nitro.options.serverDir), "**/*")]), ], }); for (const alias in tsConfig.compilerOptions!.paths) { const paths = await Promise.all( tsConfig.compilerOptions!.paths[alias].map(async (path: string) => { if (!isAbsolute(path)) { return path; } const stats = await fsp.stat(path).catch(() => null /* file does not exist */); return relativeWithDot( tsconfigDir, stats?.isFile() ? path.replace(/(?<=\w)\.\w+$/g, "") /* remove extension */ : path ); }) ); tsConfig.compilerOptions!.paths[alias] = [...new Set(paths)]; } tsConfig.include = [ ...new Set( tsConfig.include!.map((p) => (isAbsolute(p) ? relativeWithDot(tsconfigDir, p) : p)) ), ]; if (tsConfig.exclude) { tsConfig.exclude = [ ...new Set( tsConfig.exclude!.map((p) => (isAbsolute(p) ? relativeWithDot(tsconfigDir, p) : p)) ), ]; } types.tsConfig = tsConfig; buildFiles.push({ path: tsConfigPath, contents: () => JSON.stringify(tsConfig, null, 2), }); } await nitro.hooks.callHook("types:extend", types); await Promise.all( buildFiles.map(async (file) => { await writeFile( resolve(generatedTypesDir, file.path), typeof file.contents === "string" ? file.contents : file.contents() ); }) ); } const RELATIVE_RE = /^\.{1,2}\//; export function relativeWithDot(from: string, to: string) { const rel = relative(from, to); return RELATIVE_RE.test(rel) ? rel : "./" + rel; } ================================================ FILE: src/build/virtual/_all.ts ================================================ import type { Nitro } from "nitro/types"; import database from "./database.ts"; import errorHandler from "./error-handler.ts"; import featureFlags from "./feature-flags.ts"; import plugins from "./plugins.ts"; import polyfills from "./polyfills.ts"; import publicAssets from "./public-assets.ts"; import rendererTemplate from "./renderer-template.ts"; import routingMeta from "./routing-meta.ts"; import routing from "./routing.ts"; import runtimeConfig from "./runtime-config.ts"; import serverAssets from "./server-assets.ts"; import storage from "./storage.ts"; import tasks from "./tasks.ts"; type VirtualTemplate = { id: string; template: string | (() => string | Promise); }; export function virtualTemplates(nitro: Nitro, _polyfills: string[]): VirtualTemplate[] { const nitroTemplates = [ database, errorHandler, featureFlags, plugins, polyfills, publicAssets, rendererTemplate, routingMeta, routing, runtimeConfig, serverAssets, storage, tasks, ].flatMap((t) => t(nitro, _polyfills)); const customTemplates = Object.entries(nitro.options.virtual).map(([id, template]) => ({ id, template, })); return [...nitroTemplates, ...customTemplates]; } ================================================ FILE: src/build/virtual/database.ts ================================================ import { connectors } from "db0"; import type { Nitro } from "nitro/types"; import { camelCase } from "scule"; export default function database(nitro: Nitro) { return { id: "#nitro/virtual/database", template: () => { if (!nitro.options.experimental.database) { return /* js */ `export const connectionConfigs = {};`; } const dbConfigs = (nitro.options.dev && nitro.options.devDatabase) || nitro.options.database; const connectorsNames = [ ...new Set(Object.values(dbConfigs || {}).map((config) => config?.connector)), ].filter(Boolean); for (const name of connectorsNames) { if (!connectors[name]) { throw new Error(`Database connector "${name}" is invalid.`); } } return /* js */ ` ${connectorsNames .map((name) => /* js */ `import ${camelCase(name)}Connector from "${connectors[name]}";`) .join("\n")} export const connectionConfigs = { ${Object.entries(dbConfigs || {}) .filter(([, config]) => !!config?.connector) .map( ([name, { connector, options }]) => /* js */ `${name}: { connector: ${camelCase(connector)}Connector, options: ${JSON.stringify(options)} }` ) .join(",\n")} }; `; }, }; } ================================================ FILE: src/build/virtual/error-handler.ts ================================================ import type { Nitro } from "nitro/types"; import { runtimeDir } from "nitro/meta"; import { join } from "pathe"; export default function errorHandler(nitro: Nitro) { return { id: "#nitro/virtual/error-handler", template: () => { const errorHandlers = Array.isArray(nitro.options.errorHandler) ? nitro.options.errorHandler : [nitro.options.errorHandler]; const builtinHandler = join( runtimeDir, `internal/error/${nitro.options.dev ? "dev" : "prod"}` ); return /* js */ ` ${errorHandlers.map((h, i) => `import errorHandler$${i} from "${h}";`).join("\n")} const errorHandlers = [${errorHandlers.map((_, i) => `errorHandler$${i}`).join(", ")}]; import { defaultHandler } from "${builtinHandler}"; export default async function(error, event) { for (const handler of errorHandlers) { try { const response = await handler(error, event, { defaultHandler }); if (response) { return response; } } catch(error) { // Handler itself thrown, log and continue console.error(error); } } // H3 will handle fallback } `; }, }; } ================================================ FILE: src/build/virtual/feature-flags.ts ================================================ import type { Nitro } from "nitro/types"; export default function featureFlags(nitro: Nitro) { return { id: "#nitro/virtual/feature-flags", template: () => { const featureFlags: Record = { // Routing hasRoutes: nitro.routing.routes.hasRoutes(), hasRouteRules: nitro.routing.routeRules.hasRoutes(), hasRoutedMiddleware: nitro.routing.routedMiddleware.hasRoutes(), hasGlobalMiddleware: nitro.routing.globalMiddleware.length > 0, // Plugins hasPlugins: nitro.options.plugins.length > 0, hasHooks: nitro.options.features?.runtimeHooks ?? nitro.options.plugins.length > 0, }; return /* js */ Object.entries(featureFlags) .map(([key, value]) => /* js */ `export const ${key} = ${Boolean(value)};`) .join("\n"); }, }; } ================================================ FILE: src/build/virtual/plugins.ts ================================================ import type { Nitro } from "nitro/types"; import { hash } from "ohash"; export default function plugins(nitro: Nitro) { return { id: "#nitro/virtual/plugins", template: () => { const nitroPlugins = [...new Set(nitro.options.plugins)]; return /* js */ ` ${nitroPlugins .map((plugin) => /* js */ `import _${hash(plugin).replace(/-/g, "")} from "${plugin}";`) .join("\n")} export const plugins = [ ${nitroPlugins.map((plugin) => `_${hash(plugin).replace(/-/g, "")}`).join(",\n")} ] `; }, }; } ================================================ FILE: src/build/virtual/polyfills.ts ================================================ import type { Nitro } from "nitro/types"; export default function polyfills(_nitro: Nitro, polyfills: string[]) { return { id: "#nitro/virtual/polyfills", moduleSideEffects: true, template: () => { return ( polyfills.map((p) => /* js */ `import '${p}';`).join("\n") || /* js */ `/* No polyfills */` ); }, }; } ================================================ FILE: src/build/virtual/public-assets.ts ================================================ import { promises as fsp } from "node:fs"; import createEtag from "etag"; import { glob } from "tinyglobby"; import mime from "mime"; import type { Nitro } from "nitro/types"; import type { PublicAsset } from "nitro/types"; import { relative, resolve } from "pathe"; import { joinURL, withTrailingSlash } from "ufo"; import { runParallel } from "../../utils/parallel.ts"; const readAssetHandler: Record< Exclude, "node" | "deno" | "null" | "inline" > = { true: "node", node: "node", false: "null", deno: "deno", inline: "inline", }; export default function publicAssets(nitro: Nitro) { return [ // public-assets-data { id: "#nitro/virtual/public-assets-data", template: async () => { const assets: Record = {}; const files = await glob("**", { cwd: nitro.options.output.publicDir, absolute: false, dot: true, }); const { errors } = await runParallel( new Set(files), async (id) => { let mimeType = mime.getType(id.replace(/\.(gz|br|zst)$/, "")) || "text/plain"; if (mimeType.startsWith("text")) { mimeType += "; charset=utf-8"; } const fullPath = resolve(nitro.options.output.publicDir, id); const [assetData, stat] = await Promise.all([ fsp.readFile(fullPath), fsp.stat(fullPath), ]); const etag = createEtag(assetData); const assetId = joinURL(nitro.options.baseURL, decodeURIComponent(id)); let encoding; if (id.endsWith(".gz")) { encoding = "gzip"; } else if (id.endsWith(".br")) { encoding = "br"; } else if (id.endsWith(".zst")) { encoding = "zstd"; } assets[assetId] = { type: nitro._prerenderMeta?.[assetId]?.contentType || mimeType, encoding, etag, mtime: stat.mtime.toJSON(), size: stat.size, path: relative(nitro.options.output.serverDir, fullPath), data: nitro.options.serveStatic === "inline" ? assetData.toString("base64") : undefined, }; }, { concurrency: 25 } ); if (errors.length > 0) { throw new Error(`Failed to process public assets:\n${errors.join("\n")}`, { cause: errors, }); } return `export default ${JSON.stringify(assets, null, 2)};`; }, }, // public-assets { id: "#nitro/virtual/public-assets", template: () => { const publicAssetBases = Object.fromEntries( nitro.options.publicAssets .filter((dir) => !dir.fallthrough && dir.baseURL !== "/") .map((dir) => [ withTrailingSlash(joinURL(nitro.options.baseURL, dir.baseURL || "/")), { maxAge: dir.maxAge }, ]) ); // prettier-ignore type _serveStaticAsKey = Exclude | "true" | "false"; // prettier-ignore const handlerName = readAssetHandler[nitro.options.serveStatic as _serveStaticAsKey] || "null"; const readAssetImport = `#nitro/virtual/public-assets-${handlerName}`; return /* js */ ` import assets from '#nitro/virtual/public-assets-data' export { readAsset } from "${readAssetImport}" export const publicAssetBases = ${JSON.stringify(publicAssetBases)} export function isPublicAssetURL(id = '') { if (assets[id]) { return true } for (const base in publicAssetBases) { if (id.startsWith(base)) { return true } } return false } export function getPublicAssetMeta(id = '') { for (const base in publicAssetBases) { if (id.startsWith(base)) { return publicAssetBases[base] } } return {} } export function getAsset (id) { return assets[id] } `; }, }, // TODO: Handlers can be static templates! // public-assets-node { id: "#nitro/virtual/public-assets-node", template: () => { return /* js */ ` import { promises as fsp } from 'node:fs' import { fileURLToPath } from 'node:url' import { resolve, dirname } from 'node:path' import assets from '#nitro/virtual/public-assets-data' export function readAsset (id) { const serverDir = dirname(fileURLToPath(globalThis.__nitro_main__)) return fsp.readFile(resolve(serverDir, assets[id].path)) }`; }, }, // public-assets-deno { id: "#nitro/virtual/public-assets-deno", template: () => { return /* js */ ` import assets from '#nitro/virtual/public-assets-data' export function readAsset (id) { // https://deno.com/deploy/docs/serve-static-assets const path = '.' + decodeURIComponent(new URL(\`../public\${id}\`, 'file://').pathname) return Deno.readFile(path); }`; }, }, // public-assets-null { id: "#nitro/virtual/public-assets-null", template: () => { return /* js */ ` export function readAsset (id) { return Promise.resolve(null); }`; }, }, // public-assets-inline { id: "#nitro/virtual/public-assets-inline", template: () => { return /* js */ ` import assets from '#nitro/virtual/public-assets-data' export function readAsset (id) { if (!assets[id]) { return undefined } if (assets[id]._data) { return assets[id]._data } if (!assets[id].data) { return assets[id].data } assets[id]._data = Uint8Array.from(atob(assets[id].data), (c) => c.charCodeAt(0)) return assets[id]._data }`; }, }, ]; } ================================================ FILE: src/build/virtual/renderer-template.ts ================================================ import type { Nitro } from "nitro/types"; import { readFile } from "node:fs/promises"; import { hasTemplateSyntax, compileTemplateToString, RENDER_CONTEXT_KEYS } from "rendu"; export default function rendererTemplate(nitro: Nitro) { return { id: "#nitro/virtual/renderer-template", template: async () => { const template = nitro.options.renderer?.template; if (typeof template !== "string") { // No template return /* js */ ` export const rendererTemplate = () => ''; export const rendererTemplateFile = undefined; export const isStaticTemplate = true;`; } if (nitro.options.dev) { // Development return /* js */ ` import { readFile } from 'node:fs/promises'; export const rendererTemplate = () => readFile(${JSON.stringify(template)}, "utf8"); export const rendererTemplateFile = ${JSON.stringify(template)}; export const isStaticTemplate = ${JSON.stringify(nitro.options.renderer?.static)}; `; } else { // Production const html = await readFile(template, "utf8"); const isStatic = nitro.options.renderer?.static ?? !hasTemplateSyntax(html); if (isStatic) { return /* js */ ` import { HTTPResponse } from "h3"; export const rendererTemplate = () => new HTTPResponse(${JSON.stringify(html)}, { headers: { "content-type": "text/html; charset=utf-8" } }); `; } else { const template = compileTemplateToString(html, { contextKeys: [...RENDER_CONTEXT_KEYS], }); return /* js */ ` import { renderToResponse } from 'rendu' import { serverFetch } from 'nitro/app' const template = ${template}; export const rendererTemplate = (request) => renderToResponse(template, { request, context: { serverFetch } }) `; } } }, }; } ================================================ FILE: src/build/virtual/routing-meta.ts ================================================ import type { Nitro } from "nitro/types"; export default function routingMeta(nitro: Nitro) { return { id: "#nitro/virtual/routing-meta", template: () => { const handlers = Object.values(nitro.routing.routes.routes).flatMap((h) => h.data); const routeHandlers = uniqueBy(handlers, "_importHash"); return /* js */ ` ${routeHandlers .map((h) => /* js */ `import ${h._importHash}Meta from "${h.handler}?meta";`) .join("\n")} export const handlersMeta = [ ${handlers .map( (h) => /* js */ `{ route: ${JSON.stringify(h.route)}, method: ${JSON.stringify( h.method?.toLowerCase() )}, meta: ${h._importHash}Meta }` ) .join(",\n")} ]; `.trim(); }, }; } function uniqueBy(arr: T[], key: keyof T): T[] { return [...new Map(arr.map((item) => [item[key], item])).values()]; } ================================================ FILE: src/build/virtual/routing.ts ================================================ import type { Nitro, NitroEventHandler, NitroRouteRules } from "nitro/types"; export const RuntimeRouteRules = ["headers", "redirect", "proxy", "cache", "basicAuth"] as string[]; export default function routing(nitro: Nitro) { return { id: "#nitro/virtual/routing", template: () => { const allHandlers = uniqueBy( [ ...Object.values(nitro.routing.routes.routes).flatMap((h) => h.data), ...Object.values(nitro.routing.routedMiddleware.routes).map((h) => h.data), ...nitro.routing.globalMiddleware, ], "_importHash" ); return /* js */ ` import * as __routeRules__ from "#nitro/runtime/route-rules"; import * as srvxNode from "srvx/node" import * as h3 from "h3"; export const findRouteRules = ${nitro.routing.routeRules.compileToString({ serialize: serializeRouteRule, matchAll: true })} const multiHandler = (...handlers) => { const final = handlers.pop() const middleware = handlers.filter(Boolean).map(h => h3.toMiddleware(h)); return (ev) => h3.callMiddleware(ev, middleware, final); } ${allHandlers .filter((h) => !h.lazy) .map((h) => /* js */ `import ${h._importHash} from "${h.handler}";`) .join("\n")} ${allHandlers .filter((h) => h.lazy) .map( (h) => /* js */ `const ${h._importHash} = h3.defineLazyEventHandler(() => import("${h.handler}")${h.format === "node" ? ".then(m => srvxNode.toFetchHandler(m.default))" : ""});` ) .join("\n")} export const findRoute = ${nitro.routing.routes.compileToString({ serialize: serializeHandler })} export const findRoutedMiddleware = ${nitro.routing.routedMiddleware.compileToString({ serialize: serializeHandler, matchAll: true })}; export const globalMiddleware = [ ${nitro.routing.globalMiddleware.map((h) => (h.lazy ? h._importHash : `h3.toEventHandler(${h._importHash})`)).join(",")} ].filter(Boolean); `; }, }; } function uniqueBy(arr: T[], key: keyof T): T[] { return [...new Map(arr.map((item) => [item[key], item])).values()]; } // --- Serializing --- type MaybeArray = T | T[]; function serializeHandler(h: MaybeArray): string { const meta = Array.isArray(h) ? h[0] : h; return `{${[ `route:${JSON.stringify(meta.route)}`, meta.method && `method:${JSON.stringify(meta.method)}`, meta.meta && `meta:${JSON.stringify(meta.meta)}`, `handler:${ Array.isArray(h) ? `multiHandler(${h.map((handler) => serializeHandlerFn(handler)).join(",")})` : serializeHandlerFn(h) }`, ] .filter(Boolean) .join(",")}}`; } function serializeHandlerFn(h: NitroEventHandler & { _importHash: string }): string { let code = h._importHash; if (!h.lazy) { if (h.format === "node") { code = `srvxNode.toFetchHandler(${code})`; } code = `h3.toEventHandler(${code})`; } return code; } function serializeRouteRule(h: NitroRouteRules & { _route: string }): string { return `[${Object.entries(h) .filter(([name, options]) => options !== undefined && name[0] !== "_") .map(([name, options]) => { return `{${[ `name:${JSON.stringify(name)}`, `route:${JSON.stringify(h._route)}`, h._method && `method:${JSON.stringify(h._method)}`, RuntimeRouteRules.includes(name) && `handler:__routeRules__.${name}`, `options:${JSON.stringify(options)}`, ] .filter(Boolean) .join(",")}}`; }) .join(",")}]`; } ================================================ FILE: src/build/virtual/runtime-config.ts ================================================ import type { Nitro } from "nitro/types"; export default function runtimeConfig(nitro: Nitro) { return { id: "#nitro/virtual/runtime-config", template: () => { return /* js */ `export const runtimeConfig = ${JSON.stringify( nitro.options.runtimeConfig || {} )};`; }, }; } ================================================ FILE: src/build/virtual/server-assets.ts ================================================ import { promises as fsp } from "node:fs"; import createEtag from "etag"; import { glob } from "tinyglobby"; import mime from "mime"; import type { Nitro } from "nitro/types"; import { resolve } from "pathe"; import { normalizeKey } from "unstorage"; interface ResolvedAsset { fsPath: string; meta: { type?: string; etag?: string; mtime?: string; }; } export default function serverAssets(nitro: Nitro) { return { id: "#nitro/virtual/server-assets", template: async () => { if (nitro.options.dev || nitro.options.preset === "nitro-prerender") { return /* js */ ` import { createStorage } from 'unstorage' import fsDriver from 'unstorage/drivers/fs' const serverAssets = ${JSON.stringify(nitro.options.serverAssets)} export const assets = createStorage() for (const asset of serverAssets) { assets.mount(asset.baseName, fsDriver({ base: asset.dir, ignore: (asset?.ignore || []) })) }`; } // Scan all assets const assets: Record = {}; for (const asset of nitro.options.serverAssets) { const files = await glob(asset.pattern || "**/*", { cwd: asset.dir, absolute: false, ignore: asset.ignore, }); for (const _id of files) { const fsPath = resolve(asset.dir, _id); const id = asset.baseName + "/" + _id; assets[id] = { fsPath, meta: {} }; // @ts-ignore TODO: Use mime@2 types let type = mime.getType(id) || "text/plain"; if (type.startsWith("text")) { type += "; charset=utf-8"; } const etag = createEtag(await fsp.readFile(fsPath)); const mtime = await fsp.stat(fsPath).then((s) => s.mtime.toJSON()); assets[id].meta = { type, etag, mtime }; } } return /* js */ ` const _assets = {\n${Object.entries(assets) .map( ([id, asset]) => ` [${JSON.stringify(normalizeKey(id))}]: {\n import: () => import(${JSON.stringify( "raw:" + asset.fsPath )}).then(r => r.default || r),\n meta: ${JSON.stringify(asset.meta)}\n }` ) .join(",\n")}\n} const normalizeKey = ${normalizeKey.toString()} export const assets = { getKeys() { return Promise.resolve(Object.keys(_assets)) }, hasItem (id) { id = normalizeKey(id) return Promise.resolve(id in _assets) }, getItem (id) { id = normalizeKey(id) return Promise.resolve(_assets[id] ? _assets[id].import() : null) }, getMeta (id) { id = normalizeKey(id) return Promise.resolve(_assets[id] ? _assets[id].meta : {}) } } `; }, }; } ================================================ FILE: src/build/virtual/storage.ts ================================================ import { genImport, genSafeVariableName } from "knitwork"; import type { Nitro } from "nitro/types"; import { builtinDrivers } from "unstorage"; export default function storage(nitro: Nitro) { return { id: "#nitro/virtual/storage", template: () => { const mounts: { path: string; driver: string; opts: object }[] = []; const isDevOrPrerender = nitro.options.dev || nitro.options.preset === "nitro-prerender"; const storageMounts = isDevOrPrerender ? { ...nitro.options.storage, ...nitro.options.devStorage } : nitro.options.storage; for (const path in storageMounts) { const { driver: driverName, ...driverOpts } = storageMounts[path]; mounts.push({ path, driver: builtinDrivers[driverName as keyof typeof builtinDrivers] || driverName, opts: driverOpts, }); } const driverImports = [...new Set(mounts.map((m) => m.driver))]; return /* js */ ` import { createStorage } from 'unstorage' import { assets } from '#nitro/virtual/server-assets' ${driverImports.map((i) => genImport(i, genSafeVariableName(i))).join("\n")} export function initStorage() { const storage = createStorage({}) storage.mount('/assets', assets) ${mounts .map( (m) => `storage.mount('${m.path}', ${genSafeVariableName(m.driver)}(${JSON.stringify(m.opts)}))` ) .join("\n")} return storage } `; }, }; } ================================================ FILE: src/build/virtual/tasks.ts ================================================ import type { Nitro } from "nitro/types"; import { normalize } from "pathe"; export default function tasks(nitro: Nitro) { return { id: "#nitro/virtual/tasks", template: () => { const _scheduledTasks = Object.entries(nitro.options.scheduledTasks || {}) .map(([cron, _tasks]) => { const tasks = (Array.isArray(_tasks) ? _tasks : [_tasks]).filter((name) => { if (!nitro.options.tasks[name]) { nitro.logger.warn(`Scheduled task \`${name}\` is not defined!`); return false; } return true; }); return { cron, tasks }; }) .filter((e) => e.tasks.length > 0); const scheduledTasks: false | { cron: string; tasks: string[] }[] = _scheduledTasks.length > 0 ? _scheduledTasks : false; return /* js */ ` export const scheduledTasks = ${JSON.stringify(scheduledTasks)}; export const tasks = { ${Object.entries(nitro.options.tasks) .map( ([name, task]) => `"${name}": { meta: { description: ${JSON.stringify(task.description)}, }, resolve: ${ task.handler ? /* js */ `() => import("${normalize(task.handler)}").then(r => r.default || r)` : "undefined" }, }` ) .join(",\n")} };`; }, }; } ================================================ FILE: src/build/vite/build.ts ================================================ import type { Nitro } from "nitro/types"; import { isTest } from "std-env"; import { nitro as nitroPlugin } from "nitro/vite"; export async function viteBuild(nitro: Nitro) { if (nitro.options.dev) { throw new Error("Nitro dev CLI does not supports vite. Please use `vite dev` instead."); } const { createBuilder } = await import((nitro.options as any).__vitePkg__ || "vite"); const pluginInstance = nitroPlugin({ _nitro: nitro }); (globalThis as any).__nitro_build__ = true; const builder = await createBuilder({ base: nitro.options.rootDir, plugins: [pluginInstance], logLevel: isTest ? "warn" : undefined, }); delete (globalThis as any).__nitro_build__; await builder.buildApp(); } ================================================ FILE: src/build/vite/bundler.ts ================================================ import { defu } from "defu"; import { baseBuildConfig, type BaseBuildConfig } from "../config.ts"; import { getChunkName, libChunkName, NODE_MODULES_RE } from "../chunks.ts"; import { baseBuildPlugins } from "../plugins.ts"; import type { RolldownConfig, RollupConfig } from "nitro/types"; import type { Plugin as RollupPlugin } from "rollup"; import type { NitroPluginContext } from "./types.ts"; export const getBundlerConfig = async ( ctx: NitroPluginContext ): Promise<{ base: BaseBuildConfig; rollupConfig?: RollupConfig; rolldownConfig?: RolldownConfig; }> => { const nitro = ctx.nitro!; const base = baseBuildConfig(nitro); const commonConfig = { input: nitro.options.entry, external: [...base.env.external], plugins: [...(await baseBuildPlugins(nitro, base))].filter(Boolean) as RollupPlugin[], treeshake: { moduleSideEffects(id) { return nitro.options.moduleSideEffects.some((p) => id.startsWith(p)); }, }, onwarn(warning, warn) { if (!base.ignoreWarningCodes.has(warning.code || "")) { warn(warning); } }, output: { dir: nitro.options.output.serverDir, format: "esm", entryFileNames: "index.mjs", chunkFileNames: (chunk: { name: string; moduleIds: string[] }) => getChunkName(chunk, nitro), inlineDynamicImports: nitro.options.inlineDynamicImports, sourcemapIgnoreList: (id) => id.includes("node_modules"), }, } satisfies RollupConfig & RolldownConfig; if (ctx._isRolldown) { // Rolldown const rolldownConfig: RolldownConfig = defu( { transform: { inject: base.env.inject as Record, }, output: { codeSplitting: { groups: [ { test: NODE_MODULES_RE, name: (id: string) => libChunkName(id), }, ], }, }, } satisfies RolldownConfig, nitro.options.rolldownConfig, nitro.options.rollupConfig as RolldownConfig, // Added for backward compatibility commonConfig satisfies RolldownConfig ); const outputConfig = rolldownConfig.output!; if (outputConfig.inlineDynamicImports || outputConfig.format === ("iife" as string)) { delete outputConfig.inlineDynamicImports; outputConfig.codeSplitting = false; } return { base, rolldownConfig }; } else { // Rollup const inject = ( (await import("@rollup/plugin-inject")) as unknown as typeof import("@rollup/plugin-inject") ).default; const alias = ( (await import("@rollup/plugin-alias")) as unknown as typeof import("@rollup/plugin-alias") ).default; const rollupConfig: RollupConfig = defu( { plugins: [inject(base.env.inject), alias({ entries: base.aliases })], output: { sourcemapExcludeSources: true, generatedCode: { constBindings: true, }, manualChunks(id: string) { if (NODE_MODULES_RE.test(id)) { return libChunkName(id); } }, }, } satisfies RollupConfig, nitro.options.rolldownConfig as RollupConfig, // Added for backward compatibility nitro.options.rollupConfig, commonConfig ); const outputConfig = rollupConfig.output!; if (outputConfig.inlineDynamicImports || outputConfig.format === ("iife" as string)) { delete outputConfig.manualChunks; } return { base, rollupConfig }; } }; ================================================ FILE: src/build/vite/dev.ts ================================================ import type { NitroPluginContext } from "./types.ts"; import type { DevEnvironmentContext, ResolvedConfig, ViteDevServer } from "vite"; import type { FetchFunctionOptions, FetchResult } from "vite/module-runner"; import type { RunnerRPCHooks } from "env-runner"; import { IncomingMessage, ServerResponse } from "node:http"; import { NodeRequest, sendNodeResponse } from "srvx/node"; import { DevEnvironment } from "vite"; import { createViteHotChannel } from "env-runner/vite"; import { watch as chokidarWatch } from "chokidar"; import { watch as fsWatch } from "node:fs"; import { join } from "pathe"; import { debounce } from "perfect-debounce"; import { scanHandlers } from "../../scan.ts"; import { getEnvRunner } from "./env.ts"; // https://vite.dev/guide/api-environment-runtimes.html#modulerunner // ---- Types ---- export type FetchHandler = (req: Request) => Promise; export interface DevServer extends RunnerRPCHooks { fetch: FetchHandler; init?: () => void | Promise; } // ---- Fetchable Dev Environment ---- export function createFetchableDevEnvironment( name: string, config: ResolvedConfig, devServer: DevServer, entry: string, opts?: { preventExternalize?: boolean } ): FetchableDevEnvironment { const transport = createViteHotChannel(devServer, name); const context: DevEnvironmentContext = { hot: true, transport }; return new FetchableDevEnvironment(name, config, context, devServer, entry, opts); } export class FetchableDevEnvironment extends DevEnvironment { devServer: DevServer; #entry: string; #preventExternalize: boolean; constructor( name: string, config: ResolvedConfig, context: DevEnvironmentContext, devServer: DevServer, entry: string, opts?: { preventExternalize?: boolean } ) { super(name, config, context); this.devServer = devServer; this.#entry = entry; this.#preventExternalize = opts?.preventExternalize ?? false; } override async fetchModule( id: string, importer?: string, options?: FetchFunctionOptions ): Promise { // workerd cannot handle CJS/Node modules loaded via import(). // Bare imports (like "vue") are normally externalized by Vite's fetchModule, // resolved using mainFields: ["main"] which often picks CJS entries. // We intercept bare imports, resolve them through the environment's plugin // pipeline (which respects resolve.conditions and picks ESM), then route // the resolved path through transformRequest for proper SSR processing. if ( this.#preventExternalize && !id.startsWith("file://") && importer && id[0] !== "." && id[0] !== "/" ) { const resolved = await this.pluginContainer.resolveId(id, importer); if (resolved && !resolved.external) { return super.fetchModule(resolved.id, importer, options); } } return super.fetchModule(id, importer, options); } async dispatchFetch(request: Request): Promise { return this.devServer.fetch(request); } override async init(...args: any[]): Promise { await this.devServer.init?.(); await super.init(...args); this.devServer.sendMessage({ type: "custom", event: "nitro:vite-env", data: { name: this.name, entry: this.#entry }, }); } } // ---- Vite Dev Server Integration ---- export async function configureViteDevServer(ctx: NitroPluginContext, server: ViteDevServer) { const nitro = ctx.nitro!; const nitroEnv = server.environments.nitro as FetchableDevEnvironment; // Restart with nitro.config changes const nitroConfigFile = nitro.options._c12.configFile; if (nitroConfigFile) { server.config.configFileDependencies.push(nitroConfigFile); } // Websocket if (nitro.options.features.websocket ?? nitro.options.experimental.websocket) { server.httpServer!.on("upgrade", (req, socket, head) => { const protocol = req.headers["sec-websocket-protocol"]; if (protocol?.startsWith("vite-")) { // Vite HMR WebSocket connection return; } getEnvRunner(ctx).upgrade?.({ node: { req, socket, head } }); }); } // Rebuild on scan dir changes const reload = debounce(async () => { await scanHandlers(nitro); nitro.routing.sync(); nitroEnv.moduleGraph.invalidateAll(); nitroEnv.hot.send({ type: "full-reload" }); }); const scanDirs = nitro.options.scanDirs.flatMap((dir) => [ join(dir, nitro.options.apiDir || "api"), join(dir, nitro.options.routesDir || "routes"), join(dir, "middleware"), join(dir, "plugins"), join(dir, "modules"), ]); const watchReloadEvents = new Set(["add", "addDir", "unlink", "unlinkDir"]); const scanDirsWatcher = chokidarWatch(scanDirs, { ignoreInitial: true, }).on("all", (event, path, stat) => { if (watchReloadEvents.has(event)) { reload(); } }); const rootDirWatcher = fsWatch( nitro.options.rootDir, { persistent: false }, (_event, filename) => { if (filename && /^server\.[mc]?[jt]sx?$/.test(filename)) { reload(); } } ); nitro.hooks.hook("close", () => { scanDirsWatcher.close(); rootDirWatcher.close(); }); // Worker => Host RPC nitroEnv.devServer.onMessage(async (message: any) => { if (message?.__rpc === "transformHTML") { try { const html = (await server.transformIndexHtml("/", message.data)).replace( "", `{{{ globalThis.__nitro_vite_envs__?.["ssr"]?.fetch($REQUEST) || "" }}}` ); nitroEnv.devServer.sendMessage({ __rpc_id: message.__rpc_id, data: html }); } catch (error) { nitroEnv.devServer.sendMessage({ __rpc_id: message.__rpc_id, error: error instanceof Error ? error.message : String(error), }); } } }); const nitroDevMiddleware = async ( nodeReq: IncomingMessage & { _nitroHandled?: boolean }, nodeRes: ServerResponse, next: (error?: unknown) => void ) => { // Skip for vite internal requests or if already handled if ( !nodeReq.url || /^\/@(?:vite|fs|id)\//.test(nodeReq.url) || nodeReq._nitroHandled || server.middlewares.stack .map((mw) => mw.route) .some((base) => base && nodeReq.url!.startsWith(base)) ) { return next(); } nodeReq._nitroHandled = true; try { // Create web API compat request const req = new NodeRequest({ req: nodeReq, res: nodeRes }); // Try dev app const devAppRes = await ctx.devApp!.fetch(req); if (nodeRes.writableEnded || nodeRes.headersSent) { return; } if (devAppRes.status !== 404) { return await sendNodeResponse(nodeRes, devAppRes); } // Dispatch the request to the nitro environment const envRes = await nitroEnv.dispatchFetch(req); if (nodeRes.writableEnded || nodeRes.headersSent) { return; } return await sendNodeResponse(nodeRes, envRes); } catch (error) { return next(error); } }; // Handle server routes first to avoid conflicts with static assets served by Vite from the root // https://github.com/vitejs/vite/pull/20866 server.middlewares.use(function nitroDevMiddlewarePre(req, res, next) { const fetchDest = req.headers["sec-fetch-dest"]; res.setHeader("vary", "sec-fetch-dest"); if ( // Originating from browser tab or no fetch dest (curl, fetch, etc) and (not script, style, image, etc) (!fetchDest || /^(document|iframe|frame|empty)$/.test(fetchDest)) && // No file extension (not /src/index.ts) !req.url!.match(/\.([a-z0-9]+)(?:[?#]|$)/i)?.[1] && // Special prefixes (/__vue-router/auto-routes, /@vite-plugin-layouts/, etc) !/^\/(?:__|@)/.test(req.url!) ) { nitroDevMiddleware(req, res, next); } else { next(); } }); return () => { server.middlewares.use(nitroDevMiddleware); }; } ================================================ FILE: src/build/vite/env.ts ================================================ import type { EnvironmentOptions, RollupCommonJSOptions } from "vite"; import type { NitroPluginContext, ServiceConfig } from "./types.ts"; import type { RunnerName } from "env-runner"; import { RunnerManager, loadRunner } from "env-runner"; import { join, resolve } from "node:path"; import { runtimeDependencies, runtimeDir } from "nitro/meta"; import { resolveModulePath } from "exsolve"; import { createFetchableDevEnvironment } from "./dev.ts"; import { isAbsolute } from "pathe"; export function createNitroEnvironment(ctx: NitroPluginContext): EnvironmentOptions { const isWorkerdRunner = _isWorkerdRunner(ctx); return { consumer: "server", build: { rollupOptions: ctx.bundlerConfig!.rollupConfig as any, rolldownOptions: ctx.bundlerConfig!.rolldownConfig, minify: ctx.nitro!.options.minify, emptyOutDir: false, sourcemap: ctx.nitro!.options.sourcemap, commonjsOptions: ctx.nitro!.options.commonJS as RollupCommonJSOptions, copyPublicDir: false, }, resolve: { noExternal: ctx.nitro!.options.dev ? isWorkerdRunner ? true : [ /^nitro$/, // i have absolutely no idea why and how it fixes issues! new RegExp(`^(${runtimeDependencies.join("|")})$`), // virtual resolutions in vite skip plugin hooks ...ctx.bundlerConfig!.base.noExternal, ] : true, // production build is standalone // workerd cannot handle CJS modules, so we must avoid the "node" export // condition which often resolves to CJS entries. conditions: isWorkerdRunner ? ["workerd", "worker", ...ctx.nitro!.options.exportConditions!.filter((c) => c !== "node")] : ctx.nitro!.options.exportConditions, externalConditions: ctx.nitro!.options.exportConditions?.filter( (c) => !/browser|wasm|module/.test(c) ), }, define: { // Workaround for tanstack-start (devtools) "process.env.NODE_ENV": JSON.stringify(ctx.nitro!.options.dev ? "development" : "production"), }, dev: { createEnvironment: (envName, envConfig) => { const entry = resolve(runtimeDir, "internal/vite/dev-entry.mjs"); const env = createFetchableDevEnvironment(envName, envConfig, getEnvRunner(ctx), entry, { preventExternalize: isWorkerdRunner, }); ctx._transformRequest = (id) => env.transformRequest(id); (ctx._viteEnvs ??= new Map()).set(envName, entry); return env; }, }, }; } export function createServiceEnvironment( ctx: NitroPluginContext, name: string, serviceConfig: ServiceConfig ): EnvironmentOptions { const isWorkerdRunner = _isWorkerdRunner(ctx); return { consumer: "server", build: { rollupOptions: { input: { index: serviceConfig.entry } }, minify: ctx.nitro!.options.minify, sourcemap: ctx.nitro!.options.sourcemap, outDir: join(ctx.nitro!.options.buildDir, "vite/services", name), emptyOutDir: true, copyPublicDir: false, }, resolve: { ...(isWorkerdRunner ? { noExternal: true } : {}), conditions: isWorkerdRunner ? ["workerd", "worker", ...ctx.nitro!.options.exportConditions!.filter((c) => c !== "node")] : ctx.nitro!.options.exportConditions, externalConditions: ctx.nitro!.options.exportConditions?.filter( (c) => !/browser|wasm|module/.test(c) ), }, dev: { createEnvironment: (envName, envConfig) => { const entry = tryResolve(serviceConfig.entry); (ctx._viteEnvs ??= new Map()).set(envName, entry); return createFetchableDevEnvironment(envName, envConfig, getEnvRunner(ctx), entry, { preventExternalize: isWorkerdRunner, }); }, }, }; } export function createServiceEnvironments( ctx: NitroPluginContext ): Record { return Object.fromEntries( Object.entries(ctx.services).map(([name, config]) => [ name, createServiceEnvironment(ctx, name, config), ]) ); } export async function initEnvRunner(ctx: NitroPluginContext) { if (ctx._envRunner) { return ctx._envRunner; } if (!ctx._initPromise) { ctx._initPromise = (async () => { const manager = new RunnerManager(); let _retries = 0; manager.onClose((_runner, cause) => { if (_retries++ < 3) { ctx.nitro!.logger.info("Restarting env runner...", cause ? `Cause: ${cause}` : ""); _loadRunner(ctx, manager); } else { ctx.nitro!.logger.error( "Env runner failed after 3 retries.", cause ? `Last cause: ${cause}` : "" ); } }); manager.onReady(() => { _retries = 0; if (ctx._viteEnvs) { for (const [name, entry] of ctx._viteEnvs) { manager.sendMessage({ type: "custom", event: "nitro:vite-env", data: { name, entry }, }); } } }); await _loadRunner(ctx, manager); ctx._envRunner = manager; return manager; })(); } return await ctx._initPromise; } export function getEnvRunner(ctx: NitroPluginContext) { if (!ctx._envRunner) { throw new Error("Env runner not initialized. Call initEnvRunner() first."); } return ctx._envRunner; } export async function reloadEnvRunner(ctx: NitroPluginContext) { const manager = ctx._envRunner; if (!manager) { return initEnvRunner(ctx); } await _loadRunner(ctx, manager); return manager; } async function _loadRunner(ctx: NitroPluginContext, manager: RunnerManager) { const runnerName = (ctx.nitro!.options.devServer.runner || process.env.NITRO_DEV_RUNNER || "node-worker") as RunnerName; const entry = resolve(runtimeDir, "internal/vite/dev-worker.mjs"); let runner; if (runnerName === "miniflare") { const { MiniflareEnvRunner } = await import("env-runner/runners/miniflare"); runner = new MiniflareEnvRunner({ name: "nitro-vite", data: { entry }, }); } else { runner = await loadRunner(runnerName, { name: "nitro-vite", data: { entry }, }); } await manager.reload(runner); } // workerd-based runners (miniflare) cannot handle CJS externals via import(), // so all dependencies must be processed through Vite's transform pipeline. function _isWorkerdRunner(ctx: NitroPluginContext): boolean { const runnerName = ctx.nitro!.options.devServer.runner || process.env.NITRO_DEV_RUNNER || "node-worker"; return runnerName === "miniflare"; } function tryResolve(id: string) { if (/^[~#/\0]/.test(id) || isAbsolute(id)) { return id; } const resolved = resolveModulePath(id, { suffixes: ["", "/index"], extensions: ["", ".ts", ".mjs", ".cjs", ".js", ".mts", ".cts"], try: true, }); return resolved || id; } ================================================ FILE: src/build/vite/plugin.ts ================================================ import type { ConfigEnv, EnvironmentModuleNode, EnvironmentOptions, PluginOption, UserConfig, Plugin as VitePlugin, } from "vite"; import type { InputOption } from "rollup"; import type { NitroPluginConfig, NitroPluginContext } from "./types.ts"; import { resolve, join } from "pathe"; import { createNitro, prepare } from "../../builder.ts"; import { getBundlerConfig } from "./bundler.ts"; import { buildEnvironments, prodSetup } from "./prod.ts"; import { initEnvRunner, getEnvRunner, createNitroEnvironment, createServiceEnvironments, createServiceEnvironment, } from "./env.ts"; import { configureViteDevServer } from "./dev.ts"; import { runtimeDir } from "nitro/meta"; import { resolveModulePath } from "exsolve"; import { defu } from "defu"; import { prettyPath } from "../../utils/fs.ts"; import { NitroDevApp } from "../../dev/app.ts"; import { nitroPreviewPlugin } from "./preview.ts"; import assetsPlugin from "@hiogawa/vite-plugin-fullstack/assets"; import type { NitroConfig } from "nitro/types"; // https://vite.dev/guide/api-environment-plugins // https://vite.dev/guide/api-environment-frameworks.html const DEFAULT_EXTENSIONS = [".ts", ".js", ".mts", ".mjs", ".tsx", ".jsx"]; const debug = process.env.NITRO_DEBUG ? (...args: any[]) => console.log("[nitro]", ...args) : () => {}; export function nitro(pluginConfig: NitroPluginConfig = {}): VitePlugin[] { if ((globalThis as any).__nitro_build__) { // We are in `nitro build` context. Nitro injects vite plugin itself return []; } const ctx: NitroPluginContext = createContext(pluginConfig); return [ nitroInit(ctx), nitroEnv(ctx), nitroMain(ctx), nitroPrepare(ctx), nitroService(ctx), nitroPreviewPlugin(ctx), pluginConfig.experimental?.vite?.assetsImport !== false && assetsPlugin({ experimental: { // See https://github.com/hi-ogawa/vite-plugins/pull/1289 clientBuildFallback: false, }, }), ].filter(Boolean) as VitePlugin[]; } function nitroInit(ctx: NitroPluginContext): VitePlugin { return { name: "nitro:init", sharedDuringBuild: true, apply: (_config, configEnv) => !configEnv.isPreview, async config(config, configEnv) { ctx._isRolldown = !!(this.meta as Record).rolldownVersion; if (!ctx._initialized) { debug("[init] Initializing nitro"); ctx._initialized = true; await setupNitroContext(ctx, configEnv, config); } }, applyToEnvironment(env) { if (env.name === "nitro" && ctx.nitro?.options.dev) { debug("[init] Adding rollup plugins for dev"); const plugins = (ctx.bundlerConfig?.rolldownConfig?.plugins as VitePlugin[]) || (ctx.bundlerConfig?.rollupConfig?.plugins as VitePlugin[]) || []; return [...(plugins || [])]; } }, }; } function nitroEnv(ctx: NitroPluginContext): VitePlugin { return { name: "nitro:env", sharedDuringBuild: true, apply: (_config, configEnv) => !configEnv.isPreview, async config(userConfig, _configEnv) { debug("[env] Extending config (environments)"); const environments: Record = { ...createServiceEnvironments(ctx), nitro: createNitroEnvironment(ctx), }; environments.client = { consumer: userConfig.environments?.client?.consumer ?? "client", build: { rollupOptions: { input: userConfig.environments?.client?.build?.rollupOptions?.input ?? useNitro(ctx).options.renderer?.template, }, }, }; debug("[env] Environments:", Object.keys(environments).join(", ")); return { environments, }; }, configEnvironment(name, config) { if (config.consumer === "client") { debug("[env] Configuring client environment", name === "client" ? "" : ` (${name})`); config.build!.emptyOutDir = false; config.build!.outDir = useNitro(ctx).options.output.publicDir; config.build!.copyPublicDir ??= false; return; } // Skip if already registered as a service if (name === "nitro" || ctx.services[name]) { return; } // Auto-register server consumer environments as services const entry = getEntry( config.build?.rolldownOptions?.input || config.build?.rollupOptions?.input ); if (typeof entry !== "string") { return; } // Resolve and register as a service const resolvedEntry = resolveModulePath(entry, { from: [ctx.nitro!.options.rootDir, ...ctx.nitro!.options.scanDirs], extensions: DEFAULT_EXTENSIONS, suffixes: ["", "/index"], try: true, }) || entry; ctx.services[name] = { entry: resolvedEntry }; debug(`[env] Auto-detected service "${name}" with entry: ${resolvedEntry}`); // Return service environment configuration to merge return createServiceEnvironment(ctx, name, { entry: resolvedEntry }); }, configResolved() { // Setup default SSR renderer after all environments are configured if ( !ctx.nitro!.options.renderer?.handler && !ctx.nitro!.options.renderer?.template && ctx.services.ssr?.entry ) { ctx.nitro!.options.renderer ??= {}; ctx.nitro!.options.renderer.handler = resolve(runtimeDir, "internal/vite/ssr-renderer"); ctx.nitro!.routing.sync(); } }, }; } function nitroMain(ctx: NitroPluginContext): VitePlugin { return { name: "nitro:main", sharedDuringBuild: true, apply: (_config, configEnv) => !configEnv.isPreview, async config(userConfig, _configEnv) { debug("[main] Extending config (appType, resolve, server)"); if (!ctx.bundlerConfig) { throw new Error("Bundler config is not initialized yet!"); } return { appType: userConfig.appType || "custom", resolve: { // TODO: environment specific aliases not working // https://github.com/vitejs/vite/pull/17583 (seems not effective) alias: ctx.bundlerConfig.base.aliases, }, builder: { sharedConfigBuild: true, }, server: { port: Number.parseInt(process.env.PORT || "") || userConfig.server?.port || useNitro(ctx).options.devServer?.port || 3000, // #3673, disable Vite's `cors` by default as Nitro handles all requests cors: false, }, }; }, buildApp: { order: "post", handler(builder) { debug("[main] Building environments"); return buildEnvironments(ctx, builder); }, }, generateBundle: { handler(_options, bundle) { const environment = this.environment; debug("[main] Generating manifest and entry points for environment:", environment.name); const serviceNames = Object.keys(ctx.services); const isRegisteredService = serviceNames.includes(environment.name); // Find entry point of this service let entryFile: string | undefined; for (const [_name, file] of Object.entries(bundle)) { if (file.type === "chunk" && isRegisteredService && file.isEntry) { if (entryFile === undefined) { entryFile = file.fileName; } else { this.warn(`Multiple entry points found for service "${environment.name}"`); } } } if (isRegisteredService) { if (entryFile === undefined) { this.error(`No entry point found for service "${this.environment.name}".`); } ctx._entryPoints![this.environment.name] = entryFile!; } }, }, configureServer: (server) => { debug("[main] Configuring dev server"); return configureViteDevServer(ctx, server); }, // Invalidate server-only modules and optionally reload the browser // see: https://github.com/vitejs/vite/issues/19114 async hotUpdate({ server, modules, timestamp }) { if (ctx.pluginConfig.experimental?.vite?.serverReload === false) { return; } const env = this.environment; if (env.config.consumer === "client") { return; } const clientEnvs = Object.values(server.environments).filter( (env) => env.config.consumer === "client" ); const serverOnlyModules: EnvironmentModuleNode[] = []; const sharedModules: EnvironmentModuleNode[] = []; const invalidated = new Set(); for (const mod of modules) { if (mod.id && !clientEnvs.some((env) => env.moduleGraph.getModuleById(mod.id!))) { serverOnlyModules.push(mod); env.moduleGraph.invalidateModule(mod, invalidated, timestamp, false); } else { sharedModules.push(mod); } } if (serverOnlyModules.length > 0) { env.hot.send({ type: "full-reload" }); if (sharedModules.length === 0 && serverOnlyModules.some((m) => m.environment !== "ssr")) { server.ws.send({ type: "full-reload" }); } return sharedModules; } }, }; } function nitroPrepare(ctx: NitroPluginContext): VitePlugin { return { name: "nitro:prepare", sharedDuringBuild: true, applyToEnvironment: (env) => env.name === "nitro", buildApp: { // Clean the output directory before any environment is built order: "pre", async handler() { debug("[prepare] Preparing output directory"); const nitro = ctx.nitro!; await prepare(nitro); }, }, }; } function nitroService(ctx: NitroPluginContext): VitePlugin { return { name: "nitro:service", enforce: "pre", sharedDuringBuild: true, applyToEnvironment: (env) => env.name === "nitro", resolveId: { filter: { id: /^#nitro-vite-setup$/ }, async handler(id) { // Virtual modules if (id === "#nitro-vite-setup") { return { id, moduleSideEffects: true }; } }, }, load: { filter: { id: /^#nitro-vite-setup$/ }, async handler(id) { // Virtual modules if (id === "#nitro-vite-setup") { return prodSetup(ctx); } }, }, }; } // --- internal helpers --- function createContext(pluginConfig: NitroPluginConfig): NitroPluginContext { return { pluginConfig, services: { ...pluginConfig.experimental?.vite?.services }, _entryPoints: {}, }; } function useNitro(ctx: NitroPluginContext) { if (!ctx.nitro) { throw new Error("Nitro instance is not initialized yet."); } return ctx.nitro; } async function setupNitroContext( ctx: NitroPluginContext, configEnv: ConfigEnv, userConfig: UserConfig ) { // Nitro config overrides const nitroConfig: NitroConfig = { dev: configEnv.command === "serve", builder: "vite", rootDir: userConfig.root, ...defu( ctx.pluginConfig, (ctx.pluginConfig as any).config, // TODO: Remove shortly userConfig.nitro ), }; // Register Nitro modules from Vite plugins nitroConfig.modules ??= []; for (const plugin of flattenPlugins(userConfig.plugins || [])) { if (plugin.nitro) { nitroConfig.modules.push(plugin.nitro); } } // @see https://vite.dev/guide/env-and-mode#env-files const dotenvFileNames = [".env", ".env.local"]; if (configEnv.mode) { dotenvFileNames.push(`.env.${configEnv.mode}`, `.env.${configEnv.mode}.local`); } // Initialize a new Nitro instance ctx.nitro = ctx.pluginConfig._nitro || (await createNitro(nitroConfig, { dotenv: { fileName: dotenvFileNames } })); // Config ssr env as a fetchable ssr service if (!ctx.services?.ssr) { if (userConfig.environments?.ssr === undefined) { const ssrEntry = resolveModulePath("./entry-server", { from: ["app", "src", ""].flatMap((d) => [ctx.nitro!.options.rootDir, ...ctx.nitro!.options.scanDirs].map((s) => join(s, d) + "/") ), extensions: DEFAULT_EXTENSIONS, try: true, }); if (ssrEntry) { ctx.services.ssr = { entry: ssrEntry }; ctx.nitro!.logger.info(`Using \`${prettyPath(ssrEntry)}\` as vite ssr entry.`); } } else { let ssrEntry = getEntry(userConfig.environments.ssr.build?.rollupOptions?.input); if (typeof ssrEntry === "string") { ssrEntry = resolveModulePath(ssrEntry, { from: [ctx.nitro.options.rootDir, ...ctx.nitro.options.scanDirs], extensions: DEFAULT_EXTENSIONS, suffixes: ["", "/index"], try: true, }) || ssrEntry; ctx.services.ssr = { entry: ssrEntry }; } } } if ( ctx.nitro.options.serverEntry && ctx.nitro.options.serverEntry.handler === ctx.services.ssr?.entry ) { ctx.nitro.logger.warn( `Nitro server entry and Vite SSR both set to ${prettyPath(ctx.services.ssr.entry)}. Use a separate SSR entry (e.g. \`src/server.ts\`).` ); ctx.nitro.options.serverEntry = false; } // Determine default Vite dist directory const publicDistDir = (ctx._publicDistDir = userConfig.build?.outDir || resolve(ctx.nitro.options.buildDir, "vite/public")); ctx.nitro.options.publicAssets.push({ dir: publicDistDir, maxAge: 0, baseURL: "/", fallthrough: true, }); // Nitro Vite Production Runtime if (!ctx.nitro.options.dev) { ctx.nitro.options.unenv.push({ meta: { name: "nitro-vite" }, polyfill: ["#nitro-vite-setup"], }); } // Call build:before hook **before resolving rollup config** for compatibility await ctx.nitro.hooks.callHook("build:before", ctx.nitro); // Resolve common rollup options ctx.bundlerConfig = await getBundlerConfig(ctx); // Call rollup:before hook to allow modifying rollup config await ctx.nitro.hooks.callHook( "rollup:before", ctx.nitro, ctx.bundlerConfig.rollupConfig || (ctx.bundlerConfig.rolldownConfig as any) ); // Warm up env runner for dev if (ctx.nitro.options.dev) { await initEnvRunner(ctx); } // Attach nitro.fetch to env runner ctx.nitro.fetch = (req) => getEnvRunner(ctx).fetch(req); // Create dev app if (ctx.nitro.options.dev && !ctx.devApp) { ctx.devApp = new NitroDevApp(ctx.nitro); } // Cleanup resources after close { ctx.nitro.hooks.hook("close", async () => { if (ctx._envRunner) { await ctx._envRunner.close(); } }); } function getEntry(input: InputOption | undefined): string | undefined { if (typeof input === "string") { return input; } else if (Array.isArray(input) && input.length > 0) { return input[0]; } else if (input && "index" in input) { return input.index as string; } } function flattenPlugins(plugins: PluginOption[]): VitePlugin[] { return plugins .flatMap((plugin) => (Array.isArray(plugin) ? flattenPlugins(plugin) : [plugin])) .filter((p) => p && !(p instanceof Promise)) as VitePlugin[]; } ================================================ FILE: src/build/vite/preview.ts ================================================ import type { Plugin as VitePlugin } from "vite"; import type { NitroPluginContext } from "./types.ts"; import { startPreview } from "../../preview.ts"; export function nitroPreviewPlugin(ctx: NitroPluginContext): VitePlugin { return { name: "nitro:preview", apply: (_config, configEnv) => !!configEnv.isPreview, config(config) { return { preview: { port: config.preview?.port || 3000, }, }; }, async configurePreviewServer(server) { // Init Nitro preview handler const preview = await startPreview({ rootDir: server.config.root, loader: { nodeServer: server.httpServer }, }); // Close preview server when Vite's preview server is closed server.httpServer.once("close", async () => { await preview.close(); }); // Handle all requests with Nitro preview handler (also handles production static assets) const { NodeRequest, sendNodeResponse } = await import("srvx/node"); server.middlewares.use(async (req, res, next) => { const nodeReq = new NodeRequest({ req, res }); const previewRes: Response = await preview.fetch(nodeReq); await sendNodeResponse(res, previewRes).catch(next); }); // Handle WebSocket upgrade requests with Nitro preview handler if supported if (preview.upgrade) { server.httpServer.on("upgrade", (req, socket, head) => { preview.upgrade!(req, socket, head); }); } }, } satisfies VitePlugin; } ================================================ FILE: src/build/vite/prod.ts ================================================ import type { ViteBuilder } from "vite"; import type { NitroPluginContext } from "./types.ts"; import { basename, dirname, resolve } from "pathe"; import { formatCompatibilityDate } from "compatx"; import { colors as C } from "consola/utils"; import { copyPublicAssets, prerender } from "../../builder.ts"; import { existsSync } from "node:fs"; import { writeBuildInfo } from "../info.ts"; import { mkdir, readFile, rm, writeFile } from "node:fs/promises"; import { isTest, isCI } from "std-env"; import type { RolldownOutput } from "rolldown"; const BuilderNames = { nitro: C.magenta("Nitro"), client: C.green("Client"), ssr: C.blue("SSR"), } as Record; export async function buildEnvironments(ctx: NitroPluginContext, builder: ViteBuilder) { const nitro = ctx.nitro!; // ---------------------------------------------- // Stage 1: Build all environments before Nitro // ---------------------------------------------- for (const [envName, env] of Object.entries(builder.environments)) { // prettier-ignore const fmtName = BuilderNames[envName] || (envName.length <= 3 ? envName.toUpperCase() : envName[0].toUpperCase() + envName.slice(1)); if (envName === "nitro" || !env.config.build.rollupOptions.input || env.isBuilt) { if (!["nitro", "ssr", "client"].includes(envName)) { nitro.logger.info( env.isBuilt ? `Skipping ${fmtName} (already built)` : `Skipping ${fmtName} (no input defined)` ); } continue; } if (!isTest && !isCI) console.log(); nitro.logger.start(`Building [${fmtName}]`); await builder.build(env); } // Use transformed client input for renderer template generation const nitroOptions = ctx.nitro!.options; const clientInput = builder.environments.client?.config?.build?.rollupOptions?.input; if (nitroOptions.renderer?.template && nitroOptions.renderer?.template === clientInput) { const outputPath = resolve(nitroOptions.output.publicDir, basename(clientInput as string)); if (existsSync(outputPath)) { const html = await readFile(outputPath, "utf8").then((r) => r.replace( "", `{{{ globalThis.__nitro_vite_envs__?.["ssr"]?.fetch($REQUEST) || "" }}}` ) ); await rm(outputPath); const tmp = resolve(nitroOptions.buildDir, "vite/index.html"); await mkdir(dirname(tmp), { recursive: true }); await writeFile(tmp, html, "utf8"); nitroOptions.renderer.template = tmp; } } // Extended builder API by assets plugin // https://github.com/hi-ogawa/vite-plugins/pull/1288 await builder.writeAssetsManifest?.(); // ---------------------------------------------- // Stage 2: Build Nitro // ---------------------------------------------- if (!isTest && !isCI) console.log(); const buildInfo = [ ["preset", nitro.options.preset], ["compatibility", formatCompatibilityDate(nitro.options.compatibilityDate)], ].filter((e) => e[1]); nitro.logger.start( `Building [${BuilderNames.nitro}] ${C.dim(`(${buildInfo.map(([k, v]) => `${k}: \`${v}\``).join(", ")})`)}` ); // Copy public assets to the final output directory await copyPublicAssets(nitro); // Add route rule for asset dirs const assetDirs = new Set( Object.values(builder.environments) .filter((env) => env.config.consumer === "client") .map((env) => env.config.build.assetsDir) .filter(Boolean) as string[] ); for (const assetsDir of assetDirs) { if (!existsSync(resolve(nitro.options.output.publicDir, assetsDir))) { continue; } const rule = (ctx.nitro!.options.routeRules[`/${assetsDir}/**`] ??= {}); if (!rule.headers?.["cache-control"]) { rule.headers = { ...rule.headers, "cache-control": `public, max-age=31536000, immutable`, }; } } ctx.nitro!.routing.sync(); // Prerender routes if configured await prerender(nitro); // Build the Nitro server bundle const output = (await builder.build(builder.environments.nitro)) as RolldownOutput; // Close the Nitro instance await nitro.close(); // Call compiled hook await nitro.hooks.callHook("compiled", nitro); // Write build info await writeBuildInfo(nitro, output); // Show deploy and preview commands if (!isTest && !isCI) console.log(); nitro.logger.success("You can preview this build using `npx vite preview`"); if (nitro.options.commands.deploy) { nitro.logger.success("You can deploy this build using `npx nitro deploy --prebuilt`"); } } export function prodSetup(ctx: NitroPluginContext): string { const serviceNames = Object.keys(ctx.services); const serviceEntries = serviceNames.map((name) => { const entry = resolve( ctx.nitro!.options.buildDir, "vite/services", name, ctx._entryPoints[name] ); return [name, entry]; }); return /* js */ ` function lazyService(loader) { let promise, mod return { fetch(req) { if (mod) { return mod.fetch(req) } if (!promise) { promise = loader().then(_mod => (mod = _mod.default || _mod)) } return promise.then(mod => mod.fetch(req)) } } } const services = { ${serviceEntries .map( ([name, entry]) => /* js */ `[${JSON.stringify(name)}]: lazyService(() => import(${JSON.stringify(entry)}))` ) .join(",\n")} }; globalThis.__nitro_vite_envs__ = services; `; } ================================================ FILE: src/build/vite/types.ts ================================================ import type { TransformResult } from "vite"; import type { getBundlerConfig } from "./bundler.ts"; import type { Nitro, NitroConfig, NitroModule } from "nitro/types"; import type { RunnerManager } from "env-runner"; import type { NitroDevApp } from "../../dev/app.ts"; declare module "vite" { interface UserConfig { /** * Nitro Vite Plugin options. */ nitro?: NitroConfig; } interface Plugin { nitro?: NitroModule; } } export interface NitroPluginConfig extends NitroConfig { /** * @internal Use preinitialized Nitro instance for the plugin. */ _nitro?: Nitro; experimental?: NitroConfig["experimental"] & { vite: { /** * @experimental Enable `?assets` import proposed by https://github.com/vitejs/vite/discussions/20913 * @default true */ assetsImport?: boolean; /** * * Invalidate server-only modules and optionally reload the browser when a server-only module is updated. * * @default true */ serverReload?: boolean; /** * Additional Vite environment services to register. */ services?: Record; }; }; } export interface ServiceConfig { entry: string; } export interface NitroPluginContext { nitro?: Nitro; pluginConfig: NitroPluginConfig; bundlerConfig?: Awaited>; devApp?: NitroDevApp; services: Record; _isRolldown?: boolean; _initialized?: boolean; _envRunner?: RunnerManager; _initPromise?: Promise; _viteEnvs?: Map; _transformRequest?: (id: string) => Promise; _publicDistDir?: string; _entryPoints: Record; } ================================================ FILE: src/builder.ts ================================================ // Core export { createNitro } from "./nitro.ts"; // Config loader export { loadOptions } from "./config/loader.ts"; // Build export { build } from "./build/build.ts"; export { copyPublicAssets } from "./build/assets.ts"; export { prepare } from "./build/prepare.ts"; export { writeTypes } from "./build/types.ts"; export { getBuildInfo } from "./build/info.ts"; // Dev server export { createDevServer } from "./dev/server.ts"; // Prerender export { prerender } from "./prerender/prerender.ts"; // Tasks API export { runTask, listTasks } from "./task.ts"; ================================================ FILE: src/cli/commands/build.ts ================================================ import { defineCommand } from "citty"; import type { DateString } from "compatx"; import { build, copyPublicAssets, createNitro, prepare, prerender } from "nitro/builder"; import { resolve } from "pathe"; import { commonArgs } from "../common.ts"; export const buildArgs = { ...commonArgs, minify: { type: "boolean", description: "Minify the output (overrides preset defaults you can also use `--no-minify` to disable).", }, preset: { type: "string", description: "The build preset to use (you can also use `NITRO_PRESET` environment variable).", }, builder: { type: "string", description: "The builder to use (you can also use `NITRO_BUILDER` environment variable).", }, compatibilityDate: { type: "string", description: "The date to use for preset compatibility (you can also use `NITRO_COMPATIBILITY_DATE` environment variable).", }, } as const; export default defineCommand({ meta: { name: "build", description: "Build nitro project for production", }, args: buildArgs, async run({ args }) { const rootDir = resolve((args.dir || args._dir || ".") as string); const nitro = await createNitro( { rootDir, dev: false, minify: args.minify, preset: args.preset, builder: args.builder as "rollup" | "rolldown" | "vite", }, { compatibilityDate: args.compatibilityDate as DateString, } ); await prepare(nitro); await copyPublicAssets(nitro); await prerender(nitro); await build(nitro); await nitro.close(); }, }); ================================================ FILE: src/cli/commands/deploy.ts ================================================ import { defineCommand } from "citty"; import { relative, resolve } from "pathe"; import consola from "consola"; import { execSync } from "node:child_process"; import { getBuildInfo } from "../../build/info.ts"; import buildCmd, { buildArgs } from "./build.ts"; export default defineCommand({ meta: { name: "deploy", description: "Build and deploy nitro project for production", }, args: { ...buildArgs, prebuilt: { type: "boolean", description: "Skip the build step and deploy the existing build", }, }, async run(ctx) { (globalThis as any).__nitroDeploying__ = true; if (!ctx.args.prebuilt) { await buildCmd.run!(ctx as any); } if ((globalThis as any).__nitroDeployed__) { return; } const rootDir = resolve((ctx.args.dir || ctx.args._dir || ".") as string); const { buildInfo, outputDir } = await getBuildInfo(rootDir); if (!buildInfo) { // throw new Error("No build info found, cannot deploy."); consola.error("No build info found, cannot deploy."); process.exit(1); } if (!buildInfo.commands?.deploy) { consola.error( `The \`${buildInfo.preset}\` preset does not have a default deploy command.\n\nTry using a different preset with the \`--preset\` option, or configure a deploy command in the Nitro config, or deploy manually.` ); process.exit(1); } const extraArgs = ctx.rawArgs.indexOf("--") !== -1 ? ctx.rawArgs.slice(ctx.rawArgs.indexOf("--") + 1).join(" ") : ""; const deployCommand = buildInfo.commands.deploy.replace( /([\s:])\.\/(\S*)/g, `$1${relative(process.cwd(), outputDir)}/$2` ) + (extraArgs ? ` ${extraArgs}` : ""); consola.info(`$ ${deployCommand}`); execSync(deployCommand, { stdio: "inherit" }); }, }); ================================================ FILE: src/cli/commands/dev.ts ================================================ import type { Nitro } from "nitro/types"; import { defineCommand } from "citty"; import { consola } from "consola"; import { build, createNitro, prepare } from "nitro/builder"; import { resolve } from "pathe"; import { commonArgs } from "../common.ts"; import { NitroDevServer } from "../../dev/server.ts"; const hmrKeyRe = /^runtimeConfig\.|routeRules\./; export default defineCommand({ meta: { name: "dev", description: "Start the development server", }, args: { ...commonArgs, port: { type: "string", description: "specify port" }, host: { type: "string", description: "specify hostname " }, }, async run({ args }) { const rootDir = resolve((args.dir || args._dir || ".") as string); let nitro: Nitro; const reload = async () => { if (nitro) { consola.info("Restarting dev server..."); if ("unwatch" in nitro.options._c12) { await nitro.options._c12.unwatch(); } await nitro.close(); } nitro = await createNitro( { rootDir, dev: true, _cli: { command: "dev" }, }, { watch: true, c12: { async onUpdate({ getDiff, newConfig }) { const diff = getDiff(); if (diff.length === 0) { return; // No changes } consola.info( "Nitro config updated:\n" + diff.map((entry) => ` ${entry.toString()}`).join("\n") ); await (diff.every((e) => hmrKeyRe.test(e.key)) ? nitro.updateConfig(newConfig.config || {}) // Hot reload : reload()); // Full reload }, }, } ); nitro.hooks.hookOnce("restart", reload); const server = new NitroDevServer(nitro); await server.listen({ port: args.port || nitro.options.devServer.port, hostname: args.host || nitro.options.devServer.hostname, }); await prepare(nitro); await build(nitro); }; await reload(); }, }); ================================================ FILE: src/cli/commands/docs.ts ================================================ import { execSync } from "node:child_process"; import { defineCommand } from "citty"; export default defineCommand({ meta: { name: "docs", description: "Explore Nitro documentation", }, args: { page: { type: "string", description: "Page path to open" }, }, run({ rawArgs }) { const runner = ( [ ["bun", "x"], ["pnpm", "dlx"], ["npm", "x"], ] as const ).find(([pkg]) => { try { execSync(`${pkg} -v`, { stdio: "ignore" }); return true; } catch {} }) || ["npm", "x"]; const runnerCmd = runner.join(" "); const docsDir = new URL("../../../skills/nitro/docs", import.meta.url).pathname; const args = rawArgs?.join(" ") || ""; execSync(`${runnerCmd} mdzilla ${docsDir}${args ? ` ${args}` : ""}`, { stdio: "inherit", }); }, }); ================================================ FILE: src/cli/commands/prepare.ts ================================================ import { defineCommand } from "citty"; import { createNitro, writeTypes } from "nitro/builder"; import { resolve } from "pathe"; import { commonArgs } from "../common.ts"; export default defineCommand({ meta: { name: "prepare", description: "Generate types for the project", }, args: { ...commonArgs, }, async run({ args }) { const rootDir = resolve((args.dir || args._dir || ".") as string); const nitro = await createNitro({ rootDir }); await writeTypes(nitro); }, }); ================================================ FILE: src/cli/commands/preview.ts ================================================ import { defineCommand } from "citty"; import { resolve } from "pathe"; import { commonArgs } from "../common.ts"; import { startPreview } from "../../preview.ts"; import { serve } from "srvx"; import { log } from "srvx/log"; export default defineCommand({ meta: { name: "preview", description: "Start a local server to preview the built server", }, args: { ...commonArgs, port: { type: "string", description: "specify port" }, host: { type: "string", description: "specify hostname" }, }, async run({ args }) { const rootDir = resolve((args.dir || args._dir || ".") as string); const server = serve({ fetch(req) { return preview.fetch(req); }, middleware: [log()], gracefulShutdown: false, port: args.port, hostname: args.host, }); const preview = await startPreview({ rootDir, loader: { srvxServer: server }, }); if (preview.upgrade) { server.node?.server?.on("upgrade", (req, socket, head) => { preview.upgrade!(req, socket, head); }); } process.on("SIGINT", async () => { await server.close(); await preview.close(); process.exit(0); }); }, }); ================================================ FILE: src/cli/commands/task/index.ts ================================================ import { defineCommand } from "citty"; export default defineCommand({ meta: { name: "task", description: "Operate in nitro tasks (experimental)", }, subCommands: { list: () => import("./list.ts").then((r) => r.default), run: () => import("./run.ts").then((r) => r.default), }, }); ================================================ FILE: src/cli/commands/task/list.ts ================================================ import { defineCommand } from "citty"; import { consola } from "consola"; import { listTasks, loadOptions } from "nitro/builder"; import { resolve } from "pathe"; export default defineCommand({ meta: { name: "run", description: "List available tasks (experimental)", }, args: { dir: { type: "string", description: "project root directory", }, }, async run({ args }) { const cwd = resolve((args.dir || args.cwd || ".") as string); const options = await loadOptions({ rootDir: cwd }).catch(() => undefined); const tasks = await listTasks({ cwd, buildDir: options?.buildDir || ".nitro", }); for (const [name, task] of Object.entries(tasks)) { consola.log(` - \`${name}\`${task.meta?.description ? ` - ${task.meta.description}` : ""}`); } }, }); ================================================ FILE: src/cli/commands/task/run.ts ================================================ import { defineCommand } from "citty"; import { consola } from "consola"; import destr from "destr"; import { loadOptions, runTask } from "nitro/builder"; import { resolve } from "pathe"; export default defineCommand({ meta: { name: "run", description: "Run a runtime task in the currently running dev server (experimental)", }, args: { name: { type: "positional", description: "task name", required: true, }, dir: { type: "string", description: "project root directory", }, payload: { type: "string", description: "payload json to pass to the task", }, }, async run({ args }) { const cwd = resolve((args.dir || args.cwd || ".") as string); const options = await loadOptions({ rootDir: cwd }).catch(() => undefined); consola.info(`Running task \`${args.name}\`...`); let payload: any = destr(args.payload || "{}"); if (typeof payload !== "object") { consola.error(`Invalid payload: \`${args.payload}\` (it should be a valid JSON object)`); payload = undefined; } try { const { result } = await runTask( { name: args.name, context: {}, payload, }, { cwd, buildDir: options?.buildDir || ".nitro", } ); consola.success("Result:", result); } catch (error) { consola.error(`Failed to run task \`${args.name}\`: ${error}`); process.exit(1); // eslint-disable-line unicorn/no-process-exit } }, }); ================================================ FILE: src/cli/common.ts ================================================ import type { ArgsDef } from "citty"; export const commonArgs = { dir: { type: "string", description: "project root directory", }, _dir: { type: "positional", default: ".", description: "project root directory (prefer using `--dir`)", }, } satisfies ArgsDef; ================================================ FILE: src/cli/index.ts ================================================ #!/usr/bin/env node import { defineCommand, runMain } from "citty"; import { version as nitroVersion } from "nitro/meta"; const main = defineCommand({ meta: { name: "nitro", description: "Nitro CLI", version: nitroVersion, }, subCommands: { dev: () => import("./commands/dev.ts").then((r) => r.default), build: () => import("./commands/build.ts").then((r) => r.default), deploy: () => import("./commands/deploy.ts").then((r) => r.default), prepare: () => import("./commands/prepare.ts").then((r) => r.default), task: () => import("./commands/task/index.ts").then((r) => r.default), preview: () => import("./commands/preview.ts").then((r) => r.default), docs: () => import("./commands/docs.ts").then((r) => r.default), }, }); runMain(main); ================================================ FILE: src/config/defaults.ts ================================================ import type { NitroConfig } from "nitro/types"; import { isDebug, isTest } from "std-env"; import { version as nitroVersion } from "nitro/meta"; export const NitroDefaults: NitroConfig = { // General compatibilityDate: "latest", debug: isDebug, logLevel: isTest ? 1 : 3, runtimeConfig: { app: {}, nitro: {} }, // Dirs serverDir: false, scanDirs: [], buildDir: `node_modules/.nitro`, output: { dir: "{{ rootDir }}/.output", serverDir: "{{ output.dir }}/server", publicDir: "{{ output.dir }}/public", }, // Features features: {}, experimental: {}, future: {}, storage: {}, devStorage: {}, publicAssets: [], serverAssets: [], plugins: [], tasks: {}, scheduledTasks: {}, imports: false, virtual: {}, compressPublicAssets: false, ignore: [], wasm: {}, // Dev dev: false, devServer: { watch: [] }, watchOptions: { ignoreInitial: true }, devProxy: {}, // Logging logging: { compressedSizes: true, buildSuccess: true, }, // Routing baseURL: process.env.NITRO_APP_BASE_URL || "/", handlers: [], devHandlers: [], errorHandler: undefined, routes: {}, routeRules: {}, prerender: { autoSubfolderIndex: true, concurrency: 1, interval: 0, retry: 3, retryDelay: 500, failOnError: false, crawlLinks: false, ignore: [], routes: [], }, // Builder builder: undefined, moduleSideEffects: ["unenv/polyfill/"], replace: {}, node: true, sourcemap: false, traceDeps: [], // Advanced typescript: { strict: true, generateRuntimeConfigTypes: false, generateTsConfig: false, tsconfigPath: "tsconfig.json", tsConfig: undefined, }, hooks: {}, commands: {}, // Framework framework: { name: "nitro", version: nitroVersion, }, }; ================================================ FILE: src/config/loader.ts ================================================ import { loadConfig, watchConfig } from "c12"; import consola from "consola"; import { resolveCompatibilityDates } from "compatx"; import type { CompatibilityDateSpec } from "compatx"; import { klona } from "klona/full"; import type { PresetName } from "../presets/index.ts"; import type { LoadConfigOptions, NitroConfig, NitroOptions, NitroPresetMeta } from "nitro/types"; import { NitroDefaults } from "./defaults.ts"; // Resolvers import { resolveAssetsOptions } from "./resolvers/assets.ts"; import { resolveCompatibilityOptions } from "./resolvers/compatibility.ts"; import { resolveDatabaseOptions } from "./resolvers/database.ts"; import { resolveExportConditionsOptions } from "./resolvers/export-conditions.ts"; import { resolveImportsOptions } from "./resolvers/imports.ts"; import { resolveOpenAPIOptions } from "./resolvers/open-api.ts"; import { resolveTsconfig } from "./resolvers/tsconfig.ts"; import { resolvePathOptions } from "./resolvers/paths.ts"; import { resolveRouteRulesOptions } from "./resolvers/route-rules.ts"; import { resolveRuntimeConfigOptions } from "./resolvers/runtime-config.ts"; import { resolveStorageOptions } from "./resolvers/storage.ts"; import { resolveURLOptions } from "./resolvers/url.ts"; import { resolveErrorOptions } from "./resolvers/error.ts"; import { resolveUnenv } from "./resolvers/unenv.ts"; import { resolveBuilder } from "./resolvers/builder.ts"; const configResolvers = [ resolveCompatibilityOptions, resolveTsconfig, resolvePathOptions, resolveImportsOptions, resolveRouteRulesOptions, resolveDatabaseOptions, resolveExportConditionsOptions, resolveRuntimeConfigOptions, resolveOpenAPIOptions, resolveURLOptions, resolveAssetsOptions, resolveStorageOptions, resolveErrorOptions, resolveUnenv, resolveBuilder, ] as const; export async function loadOptions( configOverrides: NitroConfig = {}, opts: LoadConfigOptions = {} ): Promise { const options = await _loadUserConfig(configOverrides, opts); for (const resolver of configResolvers) { await resolver(options); } return options; } async function _loadUserConfig( configOverrides: NitroConfig = {}, opts: LoadConfigOptions = {} ): Promise { // Load configuration and preset configOverrides = klona(configOverrides); // @ts-ignore globalThis.defineNitroConfig = globalThis.defineNitroConfig || ((c) => c); // Compatibility date let compatibilityDate: CompatibilityDateSpec | undefined = configOverrides.compatibilityDate || opts.compatibilityDate || ((process.env.NITRO_COMPATIBILITY_DATE || process.env.SERVER_COMPATIBILITY_DATE || process.env.COMPATIBILITY_DATE) as CompatibilityDateSpec); // Preset resolver const { resolvePreset } = await import("../presets/index.ts"); // prettier-ignore let preset: string | undefined = (configOverrides.preset as string) || process.env.NITRO_PRESET || process.env.SERVER_PRESET const _dotenv = opts.dotenv ?? (configOverrides.dev && { fileName: [".env", ".env.local"] }); const envName = opts.c12?.envName ?? (configOverrides.dev ? "development" : "production"); const loadedConfig = await ( opts.watch ? watchConfig : loadConfig )({ name: "nitro", cwd: configOverrides.rootDir, dotenv: _dotenv, envName, extend: { extendKey: ["extends", "preset"] }, defaults: NitroDefaults, async overrides({ rawConfigs }) { // prettier-ignore const getConf = (key: K) => (configOverrides[key] ?? (rawConfigs.main as NitroConfig)?.[key] ?? (rawConfigs.rc as NitroConfig)?.[key] ?? (rawConfigs.packageJson as NitroConfig)?.[key]) as NitroConfig[K]; if (!compatibilityDate) { compatibilityDate = getConf("compatibilityDate"); } // prettier-ignore const framework = getConf("framework") const isCustomFramework = framework?.name && framework.name !== "nitro"; if (!preset) { preset = getConf("preset"); } if (configOverrides.dev) { // Check if preset has compatible dev support // Otherwise use default nitro-dev preset preset = preset && preset !== "nitro-dev" ? await resolvePreset(preset, { static: getConf("static"), dev: true, compatibilityDate: compatibilityDate || "latest", }) .then((p) => p?._meta?.name || "nitro-dev") .catch(() => "nitro-dev") : "nitro-dev"; } else if (!preset) { // Auto detect production preset preset = await resolvePreset("" /* auto detect */, { static: getConf("static"), dev: false, compatibilityDate: compatibilityDate || "latest", }).then((p) => p?._meta?.name); } return { ...configOverrides, preset, typescript: { generateRuntimeConfigTypes: !isCustomFramework, ...getConf("typescript"), ...configOverrides.typescript, }, }; }, async resolve(id: string) { const preset = await resolvePreset(id, { static: configOverrides.static, compatibilityDate: compatibilityDate || "latest", dev: configOverrides.dev, }); if (preset) { return { config: klona(preset), }; } }, ...opts.c12, }); const options = klona(loadedConfig.config) as NitroOptions; options._config = configOverrides; options._c12 = loadedConfig; const _presetName = (loadedConfig.layers || []).find((l) => l.config?._meta?.name)?.config?._meta?.name || preset; options.preset = _presetName as PresetName; options.compatibilityDate = resolveCompatibilityDates( compatibilityDate, options.compatibilityDate ); if (options.dev && options.preset !== "nitro-dev") { consola.info(`Using \`${options.preset}\` emulation in development mode.`); } return options; } ================================================ FILE: src/config/resolvers/assets.ts ================================================ import { existsSync } from "node:fs"; import { defu } from "defu"; import type { NitroOptions } from "nitro/types"; import { resolve } from "pathe"; import { withLeadingSlash, withoutTrailingSlash } from "ufo"; export async function resolveAssetsOptions(options: NitroOptions) { // Public Assets // 1. Normalize user paths for (const publicAsset of options.publicAssets) { publicAsset.dir = resolve(options.rootDir, publicAsset.dir); publicAsset.baseURL = withLeadingSlash(withoutTrailingSlash(publicAsset.baseURL || "/")); } // 2. Add public/ directories from each layer for (const dir of [options.rootDir, ...options.scanDirs]) { const publicDir = resolve(dir, "public"); if (!existsSync(publicDir)) { continue; } if (options.publicAssets.some((asset) => asset.dir === publicDir)) { continue; } options.publicAssets.push({ dir: publicDir } as any); } // Server Assets // 1. Normalize user paths for (const serverAsset of options.serverAssets) { serverAsset.dir = resolve(options.rootDir, serverAsset.dir); } // 2. Add server/ directory options.serverAssets.push({ baseName: "server", dir: resolve(options.rootDir, "assets"), }); // Infer `fallthrough` and `maxAge` from publicAssets for (const asset of options.publicAssets) { asset.baseURL = asset.baseURL || "/"; const isTopLevel = asset.baseURL === "/"; asset.fallthrough = asset.fallthrough ?? isTopLevel; const routeRule = options.routeRules[asset.baseURL + "/**"]; asset.maxAge = (routeRule?.cache as { maxAge: number })?.maxAge ?? asset.maxAge ?? 0; if (asset.maxAge && !asset.fallthrough) { options.routeRules[asset.baseURL + "/**"] = defu(routeRule, { headers: { "cache-control": `public, max-age=${asset.maxAge}, immutable`, }, }); } } } ================================================ FILE: src/config/resolvers/builder.ts ================================================ import { existsSync, readFileSync } from "node:fs"; import { createRequire } from "node:module"; import consola from "consola"; import type { NitroOptions } from "nitro/types"; import { resolve } from "pathe"; const VALID_BUILDERS = ["rolldown", "rollup", "vite"] as const; export async function resolveBuilder(options: NitroOptions) { // NITRO_BUILDER environment variable options.builder ??= process.env.NITRO_BUILDER as any; // Builder is explicitly set if (options.builder) { // Validate builder name if (!VALID_BUILDERS.includes(options.builder)) { throw new Error( `Invalid nitro builder "${options.builder}". Valid builders are: ${VALID_BUILDERS.join(", ")}.` ); } // Check if the builder package is installed (rolldown is a direct dep) const pkg = options.builder; if (pkg !== "rolldown" && !isPkgInstalled(pkg, options.rootDir)) { const shouldInstall = await consola.prompt( `Nitro builder package \`${pkg}\` is not installed. Would you like to install it?`, { type: "confirm", default: true, cancel: "null" } ); if (!shouldInstall) { throw new Error( `Nitro builder package "${options.builder}" is not installed. Please install it in your project dependencies.` ); } await installPkg(pkg, options.rootDir); } return; } // Auto-detect: check for vite.config with nitro() plugin if (isPkgInstalled("vite", options.rootDir) && hasNitroViteConfig(options)) { options.builder = "vite"; return; } // Default to rolldown (direct dependency of nitro) options.builder = "rolldown"; } const _require = createRequire(import.meta.url); function isPkgInstalled(pkg: string, root: string) { try { _require.resolve(pkg, { paths: [root] }); return true; } catch { return false; } } async function installPkg(pkg: string, root: string) { const { addDevDependency } = await import("nypm"); return addDevDependency(pkg, { cwd: root }); } function hasNitroViteConfig(options: NitroOptions): boolean { const configExts = [".ts", ".mts", ".js", ".mjs"]; for (const ext of configExts) { const configPath = resolve(options.rootDir, `vite.config${ext}`); if (existsSync(configPath)) { try { const content = readFileSync(configPath, "utf8"); if (content.includes("nitro(")) { return true; } } catch {} } } return false; } ================================================ FILE: src/config/resolvers/compatibility.ts ================================================ import type { NitroOptions } from "nitro/types"; import { resolveCompatibilityDatesFromEnv } from "compatx"; export async function resolveCompatibilityOptions(options: NitroOptions) { options.compatibilityDate = resolveCompatibilityDatesFromEnv(options.compatibilityDate); } ================================================ FILE: src/config/resolvers/database.ts ================================================ import type { NitroOptions } from "nitro/types"; export async function resolveDatabaseOptions(options: NitroOptions) { if (options.experimental.database && options.imports) { options.imports.presets ??= []; options.imports.presets.push({ from: "nitro/database", imports: ["useDatabase"], }); if (options.dev && !options.database && !options.devDatabase) { options.devDatabase = { default: { connector: "sqlite", options: { cwd: options.rootDir, }, }, }; } else if (options.node && !options.database) { options.database = { default: { connector: "sqlite", options: {}, }, }; } } } ================================================ FILE: src/config/resolvers/error.ts ================================================ import { runtimeDir } from "nitro/meta"; import type { NitroOptions } from "nitro/types"; import { join } from "pathe"; import { resolveNitroPath } from "../../utils/fs.ts"; export async function resolveErrorOptions(options: NitroOptions) { if (!options.errorHandler) { options.errorHandler = []; } else if (!Array.isArray(options.errorHandler)) { options.errorHandler = [options.errorHandler]; } options.errorHandler = options.errorHandler.map((h) => resolveNitroPath(h, options)); // Always add the default error handler as the last one options.errorHandler.push(join(runtimeDir, `internal/error/${options.dev ? "dev" : "prod"}`)); } ================================================ FILE: src/config/resolvers/export-conditions.ts ================================================ import type { NitroOptions } from "nitro/types"; export async function resolveExportConditionsOptions(options: NitroOptions) { options.exportConditions = _resolveExportConditions(options.exportConditions || [], { dev: options.dev, node: options.node, wasm: options.wasm !== false, }); } function _resolveExportConditions( userConditions: string[], opts: { dev: boolean; node: boolean; wasm?: boolean } ) { const conditions: string[] = [...userConditions.filter((c) => !c.startsWith("!"))]; conditions.push(opts.dev ? "development" : "production"); if (opts.wasm) { conditions.push("wasm", "unwasm"); } if (opts.node) { conditions.push("node"); } const negated = new Set(userConditions.filter((c) => c.startsWith("!")).map((c) => c.slice(1))); return [...new Set(conditions)].filter((c) => !negated.has(c)); } ================================================ FILE: src/config/resolvers/imports.ts ================================================ import escapeRE from "escape-string-regexp"; import type { NitroOptions } from "nitro/types"; import { join } from "pathe"; export async function resolveImportsOptions(options: NitroOptions) { // Skip loader entirely if imports disabled if (options.imports === false) { return; } options.imports.presets ??= []; // Auto imports from utils dirs options.imports.dirs ??= []; options.imports.dirs.push(...options.scanDirs.map((dir) => join(dir, "utils/**/*"))); // Normalize exclude if (Array.isArray(options.imports.exclude) && options.imports.exclude.length === 0) { // Exclude .git and buildDir by default options.imports.exclude.push(/[/\\]\.git[/\\]/); options.imports.exclude.push(options.buildDir); // Exclude all node modules that are not a scanDir const scanDirsInNodeModules = options.scanDirs .map((dir) => dir.match(/(?<=\/)node_modules\/(.+)$/)?.[1]) .filter(Boolean) as string[]; options.imports.exclude.push( scanDirsInNodeModules.length > 0 ? new RegExp( `node_modules\\/(?!${scanDirsInNodeModules.map((dir) => escapeRE(dir)).join("|")})` ) : /[/\\]node_modules[/\\]/ ); } } ================================================ FILE: src/config/resolvers/open-api.ts ================================================ import { runtimeDir } from "nitro/meta"; import type { NitroOptions } from "nitro/types"; import { join } from "pathe"; export async function resolveOpenAPIOptions(options: NitroOptions) { // Check if the experimental.openAPI option is enabled if (!options.experimental.openAPI) { return; } // Only enable for dev and (opt-in) production if (!options.dev && !options.openAPI?.production) { return; } const shouldPrerender = !options.dev && options.openAPI?.production === "prerender"; const handlersEnv = shouldPrerender ? "prerender" : ""; const prerenderRoutes: string[] = []; // Add openapi json route const jsonRoute = options.openAPI?.route || "/_openapi.json"; prerenderRoutes.push(jsonRoute); options.handlers.push({ route: jsonRoute, env: handlersEnv, handler: join(runtimeDir, "internal/routes/openapi"), }); // Scalar UI if (options.openAPI?.ui?.scalar !== false) { const scalarRoute = options.openAPI?.ui?.scalar?.route || "/_scalar"; prerenderRoutes.push(scalarRoute); options.handlers.push({ route: options.openAPI?.ui?.scalar?.route || "/_scalar", env: handlersEnv, handler: join(runtimeDir, "internal/routes/scalar"), }); } // Swagger UI if (options.openAPI?.ui?.swagger !== false) { const swaggerRoute = options.openAPI?.ui?.swagger?.route || "/_swagger"; prerenderRoutes.push(swaggerRoute); options.handlers.push({ route: swaggerRoute, env: handlersEnv, handler: join(runtimeDir, "internal/routes/swagger"), }); } // Prerender if (shouldPrerender) { options.prerender ??= {} as any; options.prerender.routes ??= []; options.prerender.routes.push(...prerenderRoutes); } } ================================================ FILE: src/config/resolvers/paths.ts ================================================ import { prettyPath, resolveNitroPath } from "../../utils/fs.ts"; import { runtimeDir } from "nitro/meta"; import type { NitroOptions, NitroConfig } from "nitro/types"; import { join, resolve } from "pathe"; import { findWorkspaceDir } from "pkg-types"; import { NitroDefaults } from "../defaults.ts"; import { resolveModulePath } from "exsolve"; import consola from "consola"; const RESOLVE_EXTENSIONS = [".ts", ".js", ".mts", ".mjs", ".tsx", ".jsx"]; export async function resolvePathOptions(options: NitroOptions) { options.rootDir = resolve(options.rootDir || ".") + "/"; options.buildDir = resolve(options.rootDir, options.buildDir || ".") + "/"; options.workspaceDir ||= (await findWorkspaceDir(options.rootDir).catch(() => options.rootDir)) + "/"; if (options.srcDir) { if (options.serverDir === undefined) { options.serverDir = options.srcDir; } consola.warn(`"srcDir" option is deprecated. Please use "serverDir" instead.`); } if (options.serverDir !== false) { if ((options as any).serverDir === true) { options.serverDir = "server"; } options.serverDir = resolve(options.rootDir, options.serverDir || ".") + "/"; } options.alias ??= {}; // Resolve possibly template paths if (!options.static && !options.entry) { throw new Error(`Nitro entry is missing! Is "${options.preset}" preset correct?`); } if (options.entry) { options.entry = resolveNitroPath(options.entry, options); } options.output.dir = resolveNitroPath(options.output.dir || NitroDefaults.output!.dir!, options, options.rootDir) + "/"; options.output.publicDir = resolveNitroPath( options.output.publicDir || NitroDefaults.output!.publicDir!, options, options.rootDir ) + "/"; options.output.serverDir = resolveNitroPath( options.output.serverDir || NitroDefaults.output!.serverDir!, options, options.rootDir ) + "/"; // Resolve plugin paths options.plugins = options.plugins.map((p) => resolveNitroPath(p, options)); // Resolve scanDirs if (options.serverDir) { options.scanDirs.unshift(options.serverDir); } options.scanDirs = options.scanDirs.map((dir) => resolve(options.rootDir, dir)); options.scanDirs = [...new Set(options.scanDirs.map((dir) => dir + "/"))]; // Resolve handler and route paths options.handlers = options.handlers.map((h) => { return { ...h, handler: resolveNitroPath(h.handler, options), }; }); options.routes = Object.fromEntries( Object.entries(options.routes).map(([route, h]) => { if (typeof h === "string") { h = { handler: h }; } h.handler = resolveNitroPath(h.handler, options); return [route, h]; }) ); // Server entry if (options.serverEntry !== false) { if (typeof options?.serverEntry === "string") { options.serverEntry = { handler: options.serverEntry }; } if (options.serverEntry?.handler) { options.serverEntry.handler = resolveNitroPath(options.serverEntry.handler, options); } else { const detected = resolveModulePath("./server", { try: true, from: options.rootDir, extensions: RESOLVE_EXTENSIONS.flatMap((ext) => [ext, `.node${ext}`]), }); if (detected) { options.serverEntry ??= { handler: "" }; options.serverEntry.handler = detected; consola.info(`Detected \`${prettyPath(detected)}\` as server entry.`); } } if (options.serverEntry?.handler && !options.serverEntry?.format) { const isNode = /\.(node)\.\w+$/.test(options.serverEntry.handler); options.serverEntry.format = isNode ? "node" : "web"; } } if ((options as NitroConfig).renderer === false) { // Skip (auto) resolve renderer, // and reset it to meet "NitroOptions" requirements options.renderer = undefined; } else { // Resolve renderer handler if (options.renderer?.handler) { options.renderer.handler = resolveModulePath( resolveNitroPath(options.renderer?.handler, options), { from: [options.rootDir, ...options.scanDirs], extensions: RESOLVE_EXTENSIONS, } ); } // Resolve renderer template if (options.renderer?.template) { options.renderer.template = resolveModulePath( resolveNitroPath(options.renderer?.template, options), { from: [options.rootDir, ...options.scanDirs], extensions: [".html"], } )!; } else if (!options.renderer?.handler) { const defaultIndex = resolveModulePath("./index.html", { from: [options.rootDir, ...options.scanDirs], extensions: [".html"], try: true, }); if (defaultIndex) { options.renderer ??= {}; options.renderer.template = defaultIndex; consola.info(`Using \`${prettyPath(defaultIndex)}\` as renderer template.`); } } // Default renderer handler if template is set if (options.renderer?.template && !options.renderer?.handler) { options.renderer ??= {}; options.renderer.handler = join( runtimeDir, "internal/routes/renderer-template" + (options.dev ? ".dev" : "") ); } } } ================================================ FILE: src/config/resolvers/route-rules.ts ================================================ import type { NitroConfig, NitroOptions, NitroRouteConfig, NitroRouteRules } from "nitro/types"; import { withLeadingSlash } from "ufo"; export async function resolveRouteRulesOptions(options: NitroOptions) { options.routeRules = normalizeRouteRules(options); } export function normalizeRouteRules(config: NitroConfig): Record { const normalizedRules: Record = {}; for (let path in config.routeRules) { const routeConfig = config.routeRules[path] as NitroRouteConfig; path = withLeadingSlash(path); const routeRules: NitroRouteRules = { ...routeConfig, redirect: undefined, proxy: undefined, }; // Redirect if (routeConfig.redirect) { routeRules.redirect = { // @ts-ignore to: "/", status: 307, ...(typeof routeConfig.redirect === "string" ? { to: routeConfig.redirect } : routeConfig.redirect), }; if (path.endsWith("/**")) { // Internal flag (routeRules.redirect as any)._redirectStripBase = path.slice(0, -3); } } // Proxy if (routeConfig.proxy) { routeRules.proxy = typeof routeConfig.proxy === "string" ? { to: routeConfig.proxy } : routeConfig.proxy; if (path.endsWith("/**")) { // Internal flag (routeRules.proxy as any)._proxyStripBase = path.slice(0, -3); } } // CORS if (routeConfig.cors) { routeRules.headers = { "access-control-allow-origin": "*", "access-control-allow-methods": "*", "access-control-allow-headers": "*", "access-control-max-age": "0", ...routeRules.headers, }; } // Cache: swr if (routeConfig.swr) { routeRules.cache = routeRules.cache || {}; routeRules.cache.swr = true; if (typeof routeConfig.swr === "number") { routeRules.cache.maxAge = routeConfig.swr; } } // Cache: false if (routeConfig.cache === false) { routeRules.cache = false; } normalizedRules[path] = routeRules; } return normalizedRules; } ================================================ FILE: src/config/resolvers/runtime-config.ts ================================================ import { defu } from "defu"; import type { NitroConfig, NitroOptions, NitroRuntimeConfig } from "nitro/types"; export async function resolveRuntimeConfigOptions(options: NitroOptions) { options.runtimeConfig = normalizeRuntimeConfig(options); } export function normalizeRuntimeConfig(config: NitroConfig) { provideFallbackValues(config.runtimeConfig || {}); const runtimeConfig: NitroRuntimeConfig = defu( config.runtimeConfig as NitroRuntimeConfig, { app: { baseURL: config.baseURL, }, nitro: { envExpansion: config.experimental?.envExpansion, openAPI: config.openAPI, }, } as NitroRuntimeConfig ); runtimeConfig.nitro ??= {}; runtimeConfig.nitro.routeRules = config.routeRules; checkSerializableRuntimeConfig(runtimeConfig); return runtimeConfig as NitroRuntimeConfig; } function provideFallbackValues(obj: Record) { for (const key in obj) { if (obj[key] === undefined || obj[key] === null) { obj[key] = ""; } else if (typeof obj[key] === "object") { provideFallbackValues(obj[key]); } } } function checkSerializableRuntimeConfig(obj: any, path: string[] = []) { if (isPrimitiveValue(obj)) { return; } for (const key in obj) { const value = obj[key]; if (value === null || value === undefined || isPrimitiveValue(value)) { continue; } if (Array.isArray(value)) { for (const [index, item] of value.entries()) checkSerializableRuntimeConfig(item, [...path, `${key}[${index}]`]); } else if ( typeof value === "object" && value.constructor === Object && (!value.constructor?.name || value.constructor.name === "Object") ) { checkSerializableRuntimeConfig(value, [...path, key]); } else { console.warn( `Runtime config option \`${[...path, key].join(".")}\` may not be able to be serialized.` ); } } } function isPrimitiveValue(value: any) { return typeof value === "string" || typeof value === "number" || typeof value === "boolean"; } ================================================ FILE: src/config/resolvers/storage.ts ================================================ import type { NitroOptions } from "nitro/types"; export async function resolveStorageOptions(options: NitroOptions) { // } ================================================ FILE: src/config/resolvers/tsconfig.ts ================================================ import type { NitroOptions } from "nitro/types"; import type { TSConfig } from "pkg-types"; import { join, resolve } from "pathe"; import * as tsco from "tsconfck"; export async function resolveTsconfig(options: NitroOptions) { const root = resolve(options.rootDir || ".") + "/"; if (!options.typescript.tsConfig) { options.typescript.tsConfig = await loadTsconfig(root); } } async function loadTsconfig(root: string): Promise { const opts: tsco.TSConfckParseOptions = { root, cache: ((loadTsconfig as any)["__cache"] ??= new tsco.TSConfckCache()), ignoreNodeModules: true, }; const tsConfigPath = join(root, "tsconfig.json"); const parsed = await tsco.parse(tsConfigPath, opts).catch(() => undefined); if (!parsed) return {} as TSConfig; const { tsconfig, tsconfigFile } = parsed; tsconfig.compilerOptions ??= {}; if (!tsconfig.compilerOptions.baseUrl) { tsconfig.compilerOptions.baseUrl = resolve(tsconfigFile, ".."); } return tsconfig; } ================================================ FILE: src/config/resolvers/unenv.ts ================================================ import type { NitroOptions } from "nitro/types"; import type { Preset } from "unenv"; export const common: Preset = { meta: { name: "nitro-common", url: import.meta.url, }, alias: { "buffer/": "node:buffer", "buffer/index": "node:buffer", "buffer/index.js": "node:buffer", "string_decoder/": "node:string_decoder", "process/": "node:process", }, }; export const nodeless: Preset = { meta: { name: "nitro-nodeless", url: import.meta.url, }, inject: { global: "unenv/polyfill/globalthis", process: "node:process", Buffer: ["node:buffer", "Buffer"], clearImmediate: ["node:timers", "clearImmediate"], setImmediate: ["node:timers", "setImmediate"], performance: "unenv/polyfill/performance", PerformanceObserver: ["node:perf_hooks", "PerformanceObserver"], BroadcastChannel: ["node:worker_threads", "BroadcastChannel"], }, polyfill: [ "unenv/polyfill/globalthis-global", "unenv/polyfill/process", "unenv/polyfill/buffer", "unenv/polyfill/timers", ], }; export async function resolveUnenv(options: NitroOptions) { options.unenv ??= []; if (!Array.isArray(options.unenv)) { options.unenv = [options.unenv]; } options.unenv = options.unenv.filter(Boolean); if (!options.node) { options.unenv.unshift(nodeless); } options.unenv.unshift(common); } ================================================ FILE: src/config/resolvers/url.ts ================================================ import type { NitroOptions } from "nitro/types"; import { withLeadingSlash, withTrailingSlash } from "ufo"; export async function resolveURLOptions(options: NitroOptions) { options.baseURL = withLeadingSlash(withTrailingSlash(options.baseURL)); } ================================================ FILE: src/config/update.ts ================================================ import consola from "consola"; import type { Nitro, NitroDynamicConfig } from "nitro/types"; import { normalizeRouteRules } from "./resolvers/route-rules.ts"; import { normalizeRuntimeConfig } from "./resolvers/runtime-config.ts"; export async function updateNitroConfig(nitro: Nitro, config: NitroDynamicConfig) { nitro.options.routeRules = normalizeRouteRules(config.routeRules ? config : nitro.options); nitro.options.runtimeConfig = normalizeRuntimeConfig( config.runtimeConfig ? config : nitro.options ); await nitro.hooks.callHook("rollup:reload"); consola.success("Nitro config hot reloaded!"); } ================================================ FILE: src/dev/app.ts ================================================ import type { Nitro } from "nitro/types"; import type { H3Event, HTTPHandler } from "h3"; import { createProxyServer, type ProxyServerOptions } from "httpxy"; import type { IncomingMessage, ServerResponse } from "node:http"; import { H3, toEventHandler, serveStatic, fromNodeHandler, HTTPError } from "h3"; import { joinURL } from "ufo"; import mime from "mime"; import { join, resolve, extname } from "pathe"; import { stat } from "node:fs/promises"; import { createReadStream } from "node:fs"; import { createGzip, createBrotliCompress } from "node:zlib"; import { createVFSHandler } from "./vfs.ts"; import devErrorHandler, { defaultHandler as devErrorHandlerInternal, loadStackTrace, } from "../runtime/internal/error/dev.ts"; export class NitroDevApp { nitro: Nitro; fetch: (req: Request) => Response | Promise; constructor(nitro: Nitro, catchAllHandler?: HTTPHandler) { this.nitro = nitro; const app = this.#createApp(catchAllHandler); this.fetch = app.fetch.bind(app); } #createApp(catchAllHandler?: HTTPHandler) { // Init h3 app const app = new H3({ debug: true, onError: async (error, event) => { const errorHandler = this.nitro.options.devErrorHandler || devErrorHandler; await loadStackTrace(error).catch(() => {}); return errorHandler(error, event, { defaultHandler: devErrorHandlerInternal, }); }, }); // Dev-only handlers for (const h of this.nitro.options.devHandlers) { const handler = toEventHandler(h.handler); if (!handler) { this.nitro.logger.warn("Invalid dev handler:", h); continue; } if (h.middleware || !h.route) { // Middleware if (h.route) { app.use(h.route, handler, { method: h.method }); } else { app.use(handler, { method: h.method }); } } else { // Route app.on(h.method || "", h.route, handler, { meta: h.meta as any }); } } // Debugging endpoint to view vfs app.get("/_vfs/**", createVFSHandler(this.nitro)); // Serve asset dirs for (const asset of this.nitro.options.publicAssets) { const assetBase = joinURL(this.nitro.options.baseURL, asset.baseURL || "/"); app.use(joinURL(assetBase, "**"), (event) => serveStaticDir(event, { dir: asset.dir, base: assetBase, fallthrough: asset.fallthrough, }) ); } // User defined dev proxy const routes = Object.keys(this.nitro.options.devProxy).sort().reverse(); for (const route of routes) { let opts = this.nitro.options.devProxy[route]; if (typeof opts === "string") { opts = { target: opts }; } const proxy = createHTTPProxy(opts); app.all(route, proxy.handleEvent); } // Main handler if (catchAllHandler) { app.all("/**", catchAllHandler); } return app; } } // TODO: upstream to h3/node function serveStaticDir( event: H3Event, opts: { dir: string; base: string; fallthrough?: boolean } ) { const dir = resolve(opts.dir) + "/"; const r = (id: string) => { if (!id.startsWith(opts.base) || !extname(id)) return; const resolved = join(dir, id.slice(opts.base.length)); if (resolved.startsWith(dir)) { return resolved; } }; return serveStatic(event, { fallthrough: opts.fallthrough, getMeta: async (id) => { const path = r(id); if (!path) return; const s = await stat(path).catch(() => null); if (!s?.isFile()) return; const ext = extname(path); return { size: s.size, mtime: s.mtime, type: mime.getType(ext) || "application/octet-stream", }; }, getContents(id) { const path = r(id); if (!path) return; const stream = createReadStream(path); const acceptEncoding = event.req.headers.get("accept-encoding") || ""; if (acceptEncoding.includes("br")) { event.res.headers.set("Content-Encoding", "br"); event.res.headers.delete("Content-Length"); event.res.headers.set("Vary", "Accept-Encoding"); return stream.pipe(createBrotliCompress()); } else if (acceptEncoding.includes("gzip")) { event.res.headers.set("Content-Encoding", "gzip"); event.res.headers.delete("Content-Length"); event.res.headers.set("Vary", "Accept-Encoding"); return stream.pipe(createGzip()); } return stream as any; }, }); } function createHTTPProxy(defaults: ProxyServerOptions = {}) { const proxy = createProxyServer({ xfwd: true, ...defaults }); return { proxy, async handleEvent(event: H3Event, opts?: ProxyServerOptions) { try { return await fromNodeHandler((req, res) => { return proxy.web(req as IncomingMessage, res as ServerResponse, opts); })(event); } catch (error: any) { event.res.headers.set("refresh", "3"); throw new HTTPError({ status: 503, message: "Dev server is unavailable.", cause: error, }); } }, }; } ================================================ FILE: src/dev/server.ts ================================================ import type { IncomingMessage } from "node:http"; import type { Socket } from "node:net"; import type { FSWatcher } from "chokidar"; import type { ServerOptions, Server } from "srvx"; import type { EnvRunnerData, RunnerMessageListener, RunnerRPCHooks } from "env-runner"; import type { RunnerName } from "env-runner"; import { RunnerManager, loadRunner } from "env-runner"; import type { Nitro } from "nitro/types"; import { HTTPError } from "h3"; import consola from "consola"; import { resolve } from "pathe"; import { watch } from "chokidar"; import { serve } from "srvx/node"; import { debounce } from "perfect-debounce"; import { isTest, isCI } from "std-env"; import { NitroDevApp } from "./app.ts"; import { writeDevBuildInfo } from "../build/info.ts"; export function createDevServer(nitro: Nitro): NitroDevServer { return new NitroDevServer(nitro); } export class NitroDevServer extends NitroDevApp implements RunnerRPCHooks { #entry: string; #workerData: EnvRunnerData = {}; #listeners: Server[] = []; #watcher?: FSWatcher; #manager: RunnerManager; #workerIdCtr: number = 0; #workerError?: unknown; #workerRetries: number = 0; #building?: boolean = true; // Assume initial build will start soon #buildError?: unknown; #reloadPromise?: Promise; constructor(nitro: Nitro) { super(nitro, async (event) => { if (this.#building) { await this.#waitForBuild(); } if (this.#reloadPromise) { await this.#reloadPromise; } if (this.#buildError) { return this.#generateError(); } const response = await this.#manager.fetch(event.req as Request); if (response.status === 503 && !this.#manager.ready) { return this.#generateError(); } return response; }); // Bind all methods to `this` for (const key of Object.getOwnPropertyNames(NitroDevServer.prototype)) { const value = (this as any)[key]; if (typeof value === "function" && key !== "constructor") { (this as any)[key] = value.bind(this); } } // Attach to Nitro.fetch nitro.fetch = this.fetch.bind(this); this.#entry = resolve(nitro.options.output.dir, nitro.options.output.serverDir, "index.mjs"); this.#manager = new RunnerManager(); this.#manager.onReady(async (_runner, addr) => { this.#workerRetries = 0; writeDevBuildInfo(this.nitro, addr).catch((error) => { this.nitro.logger.warn( `Failed to write dev build info: ${error instanceof Error ? error.message : String(error)}` ); }); }); this.#manager.onClose((_runner, cause) => { this.#workerError = cause; if (this.#workerRetries++ < 3) { this.nitro.logger.info("Restarting dev worker...", cause ? `Cause: ${cause}` : ""); this.reload(); } else { this.nitro.logger.error( "Dev worker failed after 3 retries.", cause ? `Last cause: ${cause}` : "" ); } }); nitro.hooks.hook("close", () => this.close()); nitro.hooks.hook("dev:start", () => { this.#building = true; this.#buildError = undefined; }); nitro.hooks.hook("dev:reload", (payload) => { this.#buildError = undefined; this.#building = false; if (payload?.entry) { this.#entry = payload.entry; } if (payload?.workerData) { this.#workerData = payload.workerData; } this.reload(); }); nitro.hooks.hook("dev:error", (cause: unknown) => { this.#buildError = cause; this.#building = false; }); const devWatch = nitro.options.devServer.watch; if (devWatch && devWatch.length > 0) { const debouncedReload = debounce(() => this.reload()); this.#watcher = watch(devWatch, nitro.options.watchOptions); this.#watcher.on("add", debouncedReload).on("change", debouncedReload); } } // #region Public Methods async upgrade(req: IncomingMessage, socket: Socket, head: any) { if (!this.#manager.upgrade) { throw new HTTPError({ status: 501, statusText: "Worker does not support upgrades.", }); } return this.#manager.upgrade({ node: { req, socket, head } }); } listen(opts?: Partial>): Server { const server = serve({ ...opts, fetch: this.fetch, gracefulShutdown: false, }); this.#listeners.push(server); if (server.node?.server) { server.node.server.on("upgrade", (req, sock, head) => this.upgrade(req, sock, head)); } return server; } async close() { await Promise.all( [ Promise.all(this.#listeners.map((l) => l.close())).then(() => { this.#listeners = []; }), this.#manager.close(), Promise.resolve(this.#watcher?.close()).then(() => { this.#watcher = undefined; }), ].map((p) => p.catch((error) => { consola.error(error); }) ) ); } reload() { const nextReload = (this.#reloadPromise ?? Promise.resolve()) .catch(() => {}) .then(() => this.#reload()); this.#reloadPromise = nextReload.finally(() => { if (this.#reloadPromise === nextReload) { this.#reloadPromise = undefined; } }); } async #reload() { const runnerName = this.nitro.options.devServer.runner || process.env.NITRO_DEV_RUNNER || "node-worker"; const runner = await loadRunner(runnerName as RunnerName, { name: `Nitro_${this.#workerIdCtr++}`, data: { entry: this.#entry, ...this.#workerData }, }); await this.#manager.reload(runner); } sendMessage(message: unknown) { this.#manager.sendMessage(message); } onMessage(listener: RunnerMessageListener) { this.#manager.onMessage(listener); } offMessage(listener: RunnerMessageListener) { this.#manager.offMessage(listener); } // #endregion // #region Private Methods async #waitForBuild() { const timeout = isTest || isCI ? 60_000 : 6000; await this.#manager.waitForReady(timeout); } #generateError() { const error: any = this.#buildError || this.#workerError; if (error) { try { error.unhandled = false; let id = error.id || error.path; if (id) { const cause = (error as { errors?: any[] }).errors?.[0]; const loc = error.location || error.loc || cause?.location || cause?.loc; if (loc) { id += `:${loc.line}:${loc.column}`; } error.stack = (error.stack || "").replace(/(^\s*at\s+.+)/m, ` at ${id}\n$1`); } } catch { // ignore } return new HTTPError(error); } return new Response( JSON.stringify( { error: "Dev server is unavailable.", hint: "Please reload the page and check the console for errors if the issue persists.", }, null, 2 ), { status: 503, statusText: "Dev server is unavailable", headers: { "Content-Type": "application/json", "Cache-Control": "no-store", Refresh: "3", }, } ); } // #endregion } ================================================ FILE: src/dev/vfs.ts ================================================ import { HTTPError, defineHandler, getRequestIP } from "h3"; import type { Nitro } from "nitro/types"; export function createVFSHandler(nitro: Nitro) { return defineHandler(async (event) => { const { socket } = event.runtime?.node?.req || {}; // prettier-ignore const isUnixSocket = // No network addresses (!socket?.remoteAddress && !socket?.localAddress) && // Empty address object Object.keys(socket?.address?.() || {}).length === 0 && // Socket is readable/writable but has no port info socket?.readable && socket?.writable && !socket?.remotePort; const ip = getRequestIP(event, { xForwardedFor: isUnixSocket }); const isLocalRequest = ip && /^::1$|^127\.\d+\.\d+\.\d+$/.test(ip); if (!isLocalRequest) { throw new HTTPError({ statusText: `Forbidden IP: "${ip || "?"}"`, status: 403, }); } const url = event.context.params?._ || ""; const isJson = url.endsWith(".json") || event.req.headers.get("accept")?.includes("application/json"); const id = decodeURIComponent(url.replace(/^(\.json)?\/?/, "") || ""); if (id && !nitro.vfs.has(id)) { throw new HTTPError({ message: "File not found", status: 404 }); } const content = id ? await nitro.vfs.get(id)?.render() : undefined; if (isJson) { return { rootDir: nitro.options.rootDir, entries: [...nitro.vfs.keys()].map((id) => ({ id, path: "/_vfs.json/" + encodeURIComponent(id), })), current: id ? { id, content, } : null, }; } const directories: Record = { [nitro.options.rootDir]: {} }; const fpaths = [...nitro.vfs.keys()]; for (const item of fpaths) { const segments = item.replace(nitro.options.rootDir, "").split("/").filter(Boolean); let currentDir = item.startsWith(nitro.options.rootDir) ? directories[nitro.options.rootDir] : directories; for (const segment of segments) { if (!currentDir[segment]) { currentDir[segment] = {}; } currentDir = currentDir[segment]; } } const generateHTML = (directory: Record, path: string[] = []): string => Object.entries(directory) .map(([fname, value = {}]) => { const subpath = [...path, fname]; const key = subpath.join("/"); const encodedUrl = encodeURIComponent(key); const linkClass = url === `/${encodedUrl}` ? "bg-gray-700 text-white" : "hover:bg-gray-800 text-gray-200"; return Object.keys(value).length === 0 ? `
  • ${fname}
  • ` : `
  • ${fname}
      ${generateHTML(value, subpath)}
  • `; }) .join(""); const rootDirectory = directories[nitro.options.rootDir]; delete directories[nitro.options.rootDir]; const items = generateHTML(rootDirectory, [nitro.options.rootDir]) + generateHTML(directories); const files = `

    Virtual Files

      ${items}
    `; const file = id ? editorTemplate({ readOnly: true, language: id.endsWith("html") ? "html" : "javascript", theme: "vs-dark", value: content, wordWrap: "wordWrapColumn", wordWrapColumn: 80, }) : `

    Select a virtual file to inspect

    `; event.res.headers.set("Content-Type", "text/html; charset=utf-8"); return /* html */ `
    ${files} ${file}
    `; }); } const monacoVersion = "0.30.0"; const monacoUrl = `https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/${monacoVersion}/min`; const vsUrl = `${monacoUrl}/vs`; const editorTemplate = (options: Record) => `
    `; ================================================ FILE: src/global.ts ================================================ import type { Nitro } from "nitro/types"; const nitroInstances: Nitro[] = ((globalThis as any).__nitro_instances__ ||= []); const globalKey = "__nitro_builder__"; declare global { var __nitro_builder__: { fetch: (req: Request) => Promise; }; } export function registerNitroInstance(nitro: Nitro) { if (nitroInstances.includes(nitro)) { return; } globalInit(); nitroInstances.unshift(nitro); nitro.hooks.hookOnce("close", () => { nitroInstances.splice(nitroInstances.indexOf(nitro), 1); if (nitroInstances.length === 0) { delete (globalThis as any)[globalKey]; } }); } function globalInit() { if (globalThis[globalKey]) { return; } globalThis[globalKey] = { async fetch(req) { for (let r = 0; r < 10 && nitroInstances.length === 0; r++) { await new Promise((resolve) => setTimeout(resolve, 300)); } const nitro = nitroInstances[0]; if (!nitro) { throw new Error("No Nitro instance is running."); } return nitro.fetch(req); }, }; } ================================================ FILE: src/module.ts ================================================ import type { Nitro, NitroModule, NitroModuleInput } from "nitro/types"; import { resolveModuleURL } from "exsolve"; export async function installModules(nitro: Nitro) { const _modules = [...(nitro.options.modules || [])]; const modules = await Promise.all(_modules.map((mod) => _resolveNitroModule(mod, nitro.options))); const _installedURLs = new Set(); for (const mod of modules) { if (mod._url) { if (_installedURLs.has(mod._url)) { continue; } _installedURLs.add(mod._url); } await mod.setup(nitro); } } async function _resolveNitroModule( mod: NitroModuleInput, nitroOptions: Nitro["options"] ): Promise { let _url: string | undefined; if (typeof mod === "string") { _url = resolveModuleURL(mod, { from: [nitroOptions.rootDir], extensions: [".mjs", ".cjs", ".js", ".mts", ".cts", ".ts"], }); mod = (await import(_url).then((m: any) => m.default || m)) as NitroModule; } if (typeof mod === "function") { mod = { setup: mod }; } if ("nitro" in mod) { mod = mod.nitro; } if (!mod.setup) { throw new Error("Invalid Nitro module: missing setup() function."); } return { _url, ...mod, }; } ================================================ FILE: src/nitro.ts ================================================ import { consola } from "consola"; import { Hookable, createDebugger } from "hookable"; import type { LoadConfigOptions, Nitro, NitroConfig, NitroDynamicConfig } from "nitro/types"; import { loadOptions } from "./config/loader.ts"; import { updateNitroConfig } from "./config/update.ts"; import { installModules } from "./module.ts"; import { scanAndSyncOptions, scanHandlers } from "./scan.ts"; import { initNitroRouting } from "./routing.ts"; import { registerNitroInstance } from "./global.ts"; export async function createNitro( config: NitroConfig = {}, opts: LoadConfigOptions = {} ): Promise { // Resolve options const options = await loadOptions(config, opts); // Create nitro context const nitro: Nitro = { options, hooks: new Hookable(), vfs: new Map(), routing: {} as any, logger: consola.withTag("nitro"), scannedHandlers: [], fetch: () => { throw new Error("no dev server attached!"); }, close: () => Promise.resolve(nitro.hooks.callHook("close")), async updateConfig(config: NitroDynamicConfig) { updateNitroConfig(nitro, config); }, }; // Global setup registerNitroInstance(nitro); // Init routers initNitroRouting(nitro); // Scan dirs (plugins, tasks, modules) and sync options // TODO: Make it side-effect free to allow proper watching await scanAndSyncOptions(nitro); // Debug if (nitro.options.debug) { createDebugger(nitro.hooks, { tag: "nitro" }); } // Logger if (nitro.options.logLevel !== undefined) { nitro.logger.level = nitro.options.logLevel; } // Hooks nitro.hooks.addHooks(nitro.options.hooks); // Scan and install modules await installModules(nitro); // Auto imports if (nitro.options.imports) { // Create unimport instance const { createUnimport } = await import("unimport"); nitro.unimport = createUnimport(nitro.options.imports); await nitro.unimport.init(); // Support for importing from '#imports' nitro.options.virtual["#imports"] = () => nitro.unimport?.toExports() || ""; // Backward compatibility nitro.options.virtual["#nitro"] = 'export * from "#imports"'; } // Ensure initial handlers are populated await scanHandlers(nitro); // Sync routers nitro.routing.sync(); return nitro; } ================================================ FILE: src/prerender/prerender.ts ================================================ import { pathToFileURL } from "node:url"; import { defu } from "defu"; import mime from "mime"; import { writeFile } from "../utils/fs.ts"; import type { Nitro, NitroRouteRules, PrerenderRoute, PublicAssetDir } from "nitro/types"; import { join, relative, resolve } from "pathe"; import { createRouter, addRoute, findAllRoutes } from "rou3"; import { joinURL, withBase, withoutBase, withTrailingSlash } from "ufo"; import { build } from "../build/build.ts"; import { createNitro } from "../nitro.ts"; import { compressPublicAssets } from "../utils/compress.ts"; import { runParallel } from "../utils/parallel.ts"; import { extractLinks, formatPrerenderRoute, matchesIgnorePattern } from "./utils.ts"; import { scanUnprefixedPublicAssets } from "../build/assets.ts"; import { toRequest } from "h3"; const JsonSigRx = /^\s*["[{]|^\s*-?\d{1,16}(\.\d{1,17})?([Ee][+-]?\d+)?\s*$/; // From unjs/destr // const linkParents = new Map>(); export async function prerender(nitro: Nitro) { if (nitro.options.noPublicDir) { nitro.logger.warn("Skipping prerender since `noPublicDir` option is enabled."); return; } // Initial list of routes to prerender const routes = new Set(nitro.options.prerender.routes); // Extend with static prerender route rules const prerenderRulePaths = Object.entries(nitro.options.routeRules) .filter(([path, options]) => options.prerender && !path.includes("*")) .map((e) => e[0]); for (const route of prerenderRulePaths) { routes.add(route); } // Allow extending prerender routes await nitro.hooks.callHook("prerender:routes", routes); // Skip if no prerender routes specified if (routes.size === 0) { // Crawl / at least if no routes are specified if (nitro.options.prerender.crawlLinks) { routes.add("/"); } else { return; } } // Build with prerender preset nitro.logger.info("Initializing prerenderer"); nitro._prerenderedRoutes = []; nitro._prerenderMeta = nitro._prerenderMeta || {}; const prerendererConfig = { ...nitro.options._config, static: false, rootDir: nitro.options.rootDir, logLevel: 0, preset: "nitro-prerender", builder: nitro.options.builder === "vite" ? "rolldown" : nitro.options.builder, }; await nitro.hooks.callHook("prerender:config", prerendererConfig); const nitroRenderer = await createNitro(prerendererConfig); const prerenderStartTime = Date.now(); await nitro.hooks.callHook("prerender:init", nitroRenderer); // Set path to preview prerendered routes relative to the "host" nitro preset let path = relative(nitro.options.output.dir, nitro.options.output.publicDir); if (!path.startsWith(".")) { path = `./${path}`; } nitroRenderer.options.commands.preview = `npx serve ${path}`; nitroRenderer.options.output.dir = nitro.options.output.dir; await build(nitroRenderer); // Import renderer entry const serverFilename = typeof nitroRenderer.options.rollupConfig?.output?.entryFileNames === "string" ? nitroRenderer.options.rollupConfig.output.entryFileNames : "index.mjs"; const serverEntrypoint = resolve(nitroRenderer.options.output.serverDir, serverFilename); const entryURL = pathToFileURL(serverEntrypoint).href; const prerenderer = (await import(entryURL).then((m: any) => m.default)) as { close: () => Promise; fetch: (req: Request) => Promise; }; // Create route rule matcher const routeRules = createRouter(); for (const [route, rules] of Object.entries(nitro.options.routeRules)) { addRoute(routeRules, undefined, route, rules); } const _getRouteRules = (path: string) => defu( {}, ...findAllRoutes(routeRules, undefined, path) .map((r) => r.data) .reverse() ) as NitroRouteRules; // Start prerendering const generatedRoutes = new Set(); const failedRoutes = new Set(); const skippedRoutes = new Set(); const displayedLengthWarns = new Set(); const publicAssetBases: string[] = nitro.options.publicAssets .filter( (a): a is PublicAssetDir & { baseURL: string } => !!a.baseURL && a.baseURL !== "/" && !a.fallthrough ) .map((a) => withTrailingSlash(a.baseURL)); const scannedPublicAssets = nitro.options.prerender.ignoreUnprefixedPublicAssets ? new Set(await scanUnprefixedPublicAssets(nitro)) : new Set(); const canPrerender = (route = "/") => { // Skip if route is already generated or skipped if (generatedRoutes.has(route) || skippedRoutes.has(route)) { return false; } // Check for explicitly ignored routes if (nitro.options.prerender.ignore) { for (const pattern of nitro.options.prerender.ignore) { if (matchesIgnorePattern(route, pattern)) { return false; } } } if (publicAssetBases.some((base) => route.startsWith(base))) { return false; } if (scannedPublicAssets.has(route)) { return false; } // Check for route rules explicitly disabling prerender if (_getRouteRules(route).prerender === false) { return false; } return true; }; const canWriteToDisk = (route: PrerenderRoute) => { // Cannot write routes with query or containing .. if (route.route.includes("?") || route.route.includes("..")) { return false; } // Ensure length is not too long for filesystem // https://en.wikipedia.org/wiki/Comparison_of_file_systems#Limits const FS_MAX_SEGMENT = 255; // 1024 is the max path length on APFS (undocumented) const FS_MAX_PATH = 1024; const FS_MAX_PATH_PUBLIC_HTML = FS_MAX_PATH - (nitro.options.output.publicDir.length + 10); if ( (route.route.length >= FS_MAX_PATH_PUBLIC_HTML || route.route.split("/").some((s) => s.length > FS_MAX_SEGMENT)) && !displayedLengthWarns.has(route) ) { displayedLengthWarns.add(route); const _route = route.route.slice(0, 60) + "..."; if (route.route.length >= FS_MAX_PATH_PUBLIC_HTML) { nitro.logger.warn( `Prerendering long route "${_route}" (${route.route.length}) can cause filesystem issues since it exceeds ${FS_MAX_PATH_PUBLIC_HTML}-character limit when writing to \`${nitro.options.output.publicDir}\`.` ); } else { nitro.logger.warn( `Skipping prerender of the route "${_route}" since it exceeds the ${FS_MAX_SEGMENT}-character limit in one of the path segments and can cause filesystem issues.` ); return false; } } return true; }; const generateRoute = async (route: string) => { const start = Date.now(); // Ensure route is decoded to start with route = decodeURI(route); // Check if we should render route if (!canPrerender(route)) { skippedRoutes.add(route); return; } generatedRoutes.add(route); // Create result object const _route: PrerenderRoute = { route }; // Fetch the route const encodedRoute = encodeURI(route); const req = toRequest(withBase(encodedRoute, nitro.options.baseURL), { headers: [["x-nitro-prerender", encodedRoute]], // TODO // retry: nitro.options.prerender.retry, // retryDelay: nitro.options.prerender.retryDelay, }); const res = await prerenderer.fetch(req); // Data will be removed as soon as written to the disk let dataBuff: Buffer | undefined = Buffer.from(await res.arrayBuffer()); Object.defineProperty(_route, "contents", { get: () => { return dataBuff ? dataBuff.toString("utf8") : undefined; }, set(value: string) { // Only set if we didn't consume the buffer yet if (dataBuff) { dataBuff = Buffer.from(value); } }, }); Object.defineProperty(_route, "data", { get: () => { return dataBuff ? dataBuff.buffer : undefined; }, set(value: string) { // Only set if we didn't consume the buffer yet if (dataBuff) { dataBuff = Buffer.from(value); } }, }); // https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections const redirectCodes = [301, 302, 303, 304, 307, 308]; if (![200, ...redirectCodes].includes(res.status)) { _route.error = new Error(`[${res.status}] ${res.statusText}`) as any; // @ts-expect-error (typed as readonly) _route.error!.status = res.status; // @ts-expect-error (typed as readonly) _route.error!.statusText = res.statusText; } // Measure actual time taken for generating route _route.generateTimeMS = Date.now() - start; // Guess route type and populate fileName const contentType = res.headers.get("content-type") || ""; const isImplicitHTML = !route.endsWith(".html") && contentType.includes("html") && !JsonSigRx.test(dataBuff!.subarray(0, 32).toString("utf8")); const routeWithIndex = route.endsWith("/") ? route + "index" : route; const htmlPath = route.endsWith("/") || nitro.options.prerender.autoSubfolderIndex ? joinURL(route, "index.html") : route + ".html"; _route.fileName = withoutBase( isImplicitHTML ? htmlPath : routeWithIndex, nitro.options.baseURL ); // Allow overriding content-type in `prerender:generate` hook const inferredContentType = mime.getType(_route.fileName) || "text/plain"; _route.contentType = contentType || inferredContentType; // Allow hooking before generate await nitro.hooks.callHook("prerender:generate", _route, nitro); if (_route.contentType !== inferredContentType) { nitro._prerenderMeta![_route.fileName] ||= {}; nitro._prerenderMeta![_route.fileName].contentType = _route.contentType; } // After hook to allow ignoring in `prerender:generate` hook if (_route.error) { failedRoutes.add(_route); } // Check if route is skipped or has errors if (_route.skip || _route.error) { await nitro.hooks.callHook("prerender:route", _route); nitro.logger.log(formatPrerenderRoute(_route)); dataBuff = undefined; // Free memory return _route; } // Write to the disk const filePath = join(nitro.options.output.publicDir, _route.fileName); if (canWriteToDisk(_route) && filePath.startsWith(nitro.options.output.publicDir)) { await writeFile(filePath, dataBuff!); nitro._prerenderedRoutes!.push(_route); } else { _route.skip = true; } // Crawl route links if (!_route.error && (isImplicitHTML || route.endsWith(".html"))) { const extractedLinks = await extractLinks( dataBuff!.toString("utf8"), route, res, nitro.options.prerender.crawlLinks ?? false ); for (const _link of extractedLinks) { if (canPrerender(_link)) { routes.add(_link); } } } await nitro.hooks.callHook("prerender:route", _route); nitro.logger.log(formatPrerenderRoute(_route)); dataBuff = undefined; // Free memory return _route; }; nitro.logger.info( nitro.options.prerender.crawlLinks ? `Prerendering ${routes.size} initial routes with crawler` : `Prerendering ${routes.size} routes` ); await runParallel(routes, generateRoute, { concurrency: nitro.options.prerender.concurrency || 1, interval: nitro.options.prerender.interval, }); await prerenderer.close(); await nitro.hooks.callHook("prerender:done", { prerenderedRoutes: nitro._prerenderedRoutes, failedRoutes: [...failedRoutes], }); if (nitro.options.prerender.failOnError && failedRoutes.size > 0) { nitro.logger.log("\nErrors prerendering:"); for (const route of failedRoutes) { // const parents = linkParents.get(route.route); // const parentsText = parents?.size // ? `\n${[...parents.values()] // .map((link) => colors.gray(` │ └── Linked from ${link}`)) // .join("\n")}` // : ""; nitro.logger.log(formatPrerenderRoute(route)); } nitro.logger.log(""); throw new Error("Exiting due to prerender errors."); } const prerenderTimeInMs = Date.now() - prerenderStartTime; nitro.logger.info( `Prerendered ${nitro._prerenderedRoutes.length} routes in ${prerenderTimeInMs / 1000} seconds` ); if (nitro.options.compressPublicAssets) { await compressPublicAssets(nitro); } } ================================================ FILE: src/prerender/utils.ts ================================================ import { colors } from "consola/utils"; import type { PrerenderRoute } from "nitro/types"; import { parseURL } from "ufo"; import { parse as parseHTML, walk } from "ultrahtml"; const allowedExtensions = new Set(["", ".json"]); const linkParents = new Map>(); const HTML_ENTITIES = { "<": "<", ">": ">", "&": "&", "'": "'", """: '"', } as Record; function escapeHtml(text: string) { return text.replace(/&(lt|gt|amp|apos|quot);/g, (ch) => HTML_ENTITIES[ch] || ch); } export async function extractLinks(html: string, from: string, res: Response, crawlLinks: boolean) { const links: string[] = []; const _links: string[] = []; // Extract from any to crawl if (crawlLinks) { await walk(parseHTML(html), (node) => { if (!node.attributes?.href) { return; } const link = escapeHtml(node.attributes.href); if (!decodeURIComponent(link).startsWith("#") && allowedExtensions.has(getExtension(link))) { _links.push(link); } }); } // Extract from x-nitro-prerender headers const header = res.headers.get("x-nitro-prerender") || ""; _links.push(...header.split(",").map((i) => decodeURIComponent(i.trim()))); for (const link of _links.filter(Boolean)) { const _link = parseURL(link); if (_link.protocol || _link.host) { continue; } if (!_link.pathname.startsWith("/")) { const fromURL = new URL(from, "http://localhost"); _link.pathname = new URL(_link.pathname, fromURL).pathname; } links.push(_link.pathname + _link.search); } for (const link of links) { const _parents = linkParents.get(link); if (_parents) { _parents.add(from); } else { linkParents.set(link, new Set([from])); } } return links; } const EXT_REGEX = /\.[\da-z]+$/; function getExtension(link: string): string { const pathname = parseURL(link).pathname; return (pathname.match(EXT_REGEX) || [])[0] || ""; } export function formatPrerenderRoute(route: PrerenderRoute) { let str = ` ├─ ${route.route} (${route.generateTimeMS}ms)`; if (route.error) { const parents = linkParents.get(route.route); const errorColor = colors[route.error.status === 404 ? "yellow" : "red"]; const errorLead = parents?.size ? "├──" : "└──"; str += `\n │ ${errorLead} ${errorColor(route.error.message || "unknown error")}`; if (parents?.size) { str += `\n${[...parents.values()].map((link) => ` │ └── Linked from ${link}`).join("\n")}`; } } if (route.skip) { str += colors.gray(" (skipped)"); } return colors.gray(str); } // prettier-ignore type IgnorePattern = | string | RegExp | ((path: string) => undefined | null | boolean); export function matchesIgnorePattern(path: string, pattern: IgnorePattern) { if (typeof pattern === "string") { // TODO: support rou3 patterns return path.startsWith(pattern as string); } if (typeof pattern === "function") { return pattern(path) === true; } if (pattern instanceof RegExp) { return pattern.test(path); } return false; } ================================================ FILE: src/presets/_all.gen.ts ================================================ // Auto-generated using gen-presets script import _nitro from "./_nitro/preset.ts"; import _static from "./_static/preset.ts"; import _alwaysdata from "./alwaysdata/preset.ts"; import _awsAmplify from "./aws-amplify/preset.ts"; import _awsLambda from "./aws-lambda/preset.ts"; import _azure from "./azure/preset.ts"; import _bun from "./bun/preset.ts"; import _cleavr from "./cleavr/preset.ts"; import _cloudflare from "./cloudflare/preset.ts"; import _deno from "./deno/preset.ts"; import _digitalocean from "./digitalocean/preset.ts"; import _firebase from "./firebase/preset.ts"; import _flightcontrol from "./flightcontrol/preset.ts"; import _genezio from "./genezio/preset.ts"; import _heroku from "./heroku/preset.ts"; import _iis from "./iis/preset.ts"; import _koyeb from "./koyeb/preset.ts"; import _netlify from "./netlify/preset.ts"; import _node from "./node/preset.ts"; import _platformSh from "./platform.sh/preset.ts"; import _renderCom from "./render.com/preset.ts"; import _standard from "./standard/preset.ts"; import _stormkit from "./stormkit/preset.ts"; import _vercel from "./vercel/preset.ts"; import _winterjs from "./winterjs/preset.ts"; import _zeabur from "./zeabur/preset.ts"; import _zephyr from "./zephyr/preset.ts"; import _zerops from "./zerops/preset.ts"; export default [ ..._nitro, ..._static, ..._alwaysdata, ..._awsAmplify, ..._awsLambda, ..._azure, ..._bun, ..._cleavr, ..._cloudflare, ..._deno, ..._digitalocean, ..._firebase, ..._flightcontrol, ..._genezio, ..._heroku, ..._iis, ..._koyeb, ..._netlify, ..._node, ..._platformSh, ..._renderCom, ..._standard, ..._stormkit, ..._vercel, ..._winterjs, ..._zeabur, ..._zephyr, ..._zerops, ] as const; ================================================ FILE: src/presets/_nitro/base-worker.ts ================================================ import { defineNitroPreset } from "../_utils/preset.ts"; const baseWorker = defineNitroPreset( { entry: null as any, // Abstract node: false, minify: true, noExternals: true, rollupConfig: { output: { format: "iife", generatedCode: { symbols: true, }, }, }, inlineDynamicImports: true, // iffe does not support code-splitting }, { name: "base-worker" as const, } ); export default [baseWorker] as const; ================================================ FILE: src/presets/_nitro/nitro-dev.ts ================================================ import { runtimeDir } from "nitro/meta"; import { defineNitroPreset } from "../_utils/preset.ts"; import { join } from "pathe"; const nitroDev = defineNitroPreset( { entry: "./_nitro/runtime/nitro-dev", output: { dir: "{{ buildDir }}/dev", serverDir: "{{ buildDir }}/dev", publicDir: "{{ buildDir }}/dev", }, handlers: [ { route: "/_nitro/tasks/**", lazy: true, handler: join(runtimeDir, "internal/routes/dev-tasks"), }, ], externals: { noTrace: true }, serveStatic: true, inlineDynamicImports: true, // externals plugin limitation sourcemap: true, }, { name: "nitro-dev" as const, dev: true, } ); export default [nitroDev] as const; ================================================ FILE: src/presets/_nitro/nitro-prerender.ts ================================================ import { defineNitroPreset } from "../_utils/preset.ts"; const nitroPrerender = defineNitroPreset( { entry: "./_nitro/runtime/nitro-prerenderer", serveStatic: true, output: { serverDir: "{{ buildDir }}/prerender", }, externals: { noTrace: true }, }, { name: "nitro-prerender" as const, } ); export default [nitroPrerender] as const; ================================================ FILE: src/presets/_nitro/preset.ts ================================================ import worker from "./base-worker.ts"; import dev from "./nitro-dev.ts"; import prerender from "./nitro-prerender.ts"; export default [...worker, ...dev, ...prerender] as const; ================================================ FILE: src/presets/_nitro/runtime/nitro-dev.ts ================================================ import "#nitro/virtual/polyfills"; import { useNitroApp, useNitroHooks } from "nitro/app"; import { startScheduleRunner } from "#nitro/runtime/task"; import { trapUnhandledErrors } from "#nitro/runtime/error/hooks"; import { resolveWebsocketHooks } from "#nitro/runtime/app"; import type { AppEntry } from "env-runner"; const nitroApp = useNitroApp(); const nitroHooks = useNitroHooks(); trapUnhandledErrors(); // Scheduled tasks if (import.meta._tasks) { startScheduleRunner({}); } const ws = import.meta._websocket ? await import("crossws/adapters/node").then((m) => (m.default || m)({ resolve: resolveWebsocketHooks }) ) : undefined; export default { fetch: nitroApp.fetch, upgrade: ws ? (context: { node: { req: any; socket: any; head: any } }) => { ws.handleUpgrade(context.node.req, context.node.socket, context.node.head); } : undefined, ipc: { onClose: () => nitroHooks.callHook("close"), }, } satisfies AppEntry; ================================================ FILE: src/presets/_nitro/runtime/nitro-prerenderer.ts ================================================ import "#nitro/virtual/polyfills"; import consola from "consola"; import { HTTPError } from "h3"; import { useNitroApp, useNitroHooks } from "nitro/app"; const nitroApp = useNitroApp(); const nitroHooks = useNitroHooks(); export default { fetch: nitroApp.fetch, close: () => nitroHooks.callHook("close"), }; nitroHooks.hook("error", (error, context) => { if ( !(error as HTTPError).unhandled && (error as HTTPError).status >= 500 && context.event?.req?.headers instanceof Headers && context.event.req.headers.get("x-nitro-prerender") ) { consola.error( `[prerender error]`, `[${context.event.req.method}]`, `[${context.event.req.url}]`, error ); } }); ================================================ FILE: src/presets/_nitro/runtime/service-worker.ts ================================================ import "#nitro/virtual/polyfills"; import { useNitroApp } from "nitro/app"; import { isPublicAssetURL } from "#nitro/virtual/public-assets"; import type { ServerRequest } from "srvx"; const nitroApp = useNitroApp(); // @ts-expect-error addEventListener("fetch", (event: FetchEvent) => { const url = new URL(event.request.url); if (isPublicAssetURL(url.pathname) || url.pathname.includes("/_server/")) { return; } // srvx compatibility const req = event.request as unknown as ServerRequest; req.runtime ??= { name: "service-worker" }; req.runtime.serviceWorker ??= { event } as any; req.waitUntil = event.waitUntil.bind(event); event.respondWith(nitroApp.fetch(req)); }); declare const self: ServiceWorkerGlobalScope; self.addEventListener("install", () => { self.skipWaiting(); }); self.addEventListener("activate", (event) => { event.waitUntil(self.clients.claim()); }); ================================================ FILE: src/presets/_resolve.ts ================================================ import { resolveCompatibilityDatesFromEnv, formatCompatibilityDate } from "compatx"; import type { CompatibilityDateSpec, PlatformName } from "compatx"; import type { NitroPreset, NitroPresetMeta } from "nitro/types"; import { kebabCase } from "scule"; import { provider, runtime } from "std-env"; import type { ProviderName } from "std-env"; import allPresets from "./_all.gen.ts"; // std-env has more specific keys for providers than compatx const _stdProviderMap: Partial> = { aws_amplify: "aws", azure_static: "azure", cloudflare_pages: "cloudflare", }; export async function resolvePreset( name: string, opts: { static?: boolean; compatibilityDate?: false | CompatibilityDateSpec; dev?: boolean; } = {} ): Promise<(NitroPreset & { _meta?: NitroPresetMeta }) | undefined> { if (name === ".") { return undefined; // invalid input } const _name = kebabCase(name) || provider; const _compatDates = opts.compatibilityDate ? resolveCompatibilityDatesFromEnv(opts.compatibilityDate) : false; const matches = allPresets .filter((preset) => { // prettier-ignore const names = [preset._meta.name, preset._meta.stdName, ...(preset._meta.aliases || [])].filter(Boolean); if (!names.includes(_name)) { return false; } // Match dev|prod if ((opts.dev && !preset._meta.dev) || (!opts.dev && preset._meta.dev)) { return false; } if (_compatDates) { const _date = _compatDates[_stdProviderMap[preset._meta.stdName!] as PlatformName] || _compatDates[preset._meta.stdName as PlatformName] || _compatDates[preset._meta.name as PlatformName] || _compatDates.default; if ( _date && preset._meta.compatibilityDate && new Date(preset._meta.compatibilityDate) > new Date(_date) ) { return false; } } return true; }) .sort((a, b) => { const aDate = new Date(a._meta.compatibilityDate || 0); const bDate = new Date(b._meta.compatibilityDate || 0); return bDate > aDate ? 1 : -1; }); const preset = matches.find((p) => (p._meta.static || false) === (opts?.static || false)) || matches[0]; if (typeof preset === "function") { // @ts-expect-error unreachable return preset(); } // Auto-detect preset if (!name && !preset) { if (opts?.static) { return resolvePreset("static", opts); } const runtimeMap = { deno: "deno", bun: "bun" } as Record; return resolvePreset(runtimeMap[runtime] || "node", opts); } if (name && !preset) { // prettier-ignore const options = allPresets .filter((p) =>p._meta.name === name ||p._meta.stdName === name ||p._meta.aliases?.includes(name) ) .sort((a, b) => (a._meta.compatibilityDate || 0) > (b._meta.compatibilityDate || 0) ? 1 : -1); if (options.length > 0) { let msg = `Preset "${name}" cannot be resolved with current compatibilityDate: ${formatCompatibilityDate(_compatDates || "")}.\n\n`; for (const option of options) { msg += `\n- ${option._meta.name} (requires compatibilityDate >= ${option._meta.compatibilityDate})`; } const err = new Error(msg); Error.captureStackTrace?.(err, resolvePreset); throw err; } } return preset; } ================================================ FILE: src/presets/_static/preset.ts ================================================ import fsp from "node:fs/promises"; import { defineNitroPreset } from "../_utils/preset.ts"; import { join } from "pathe"; const _static = defineNitroPreset( { static: true, output: { dir: "{{ rootDir }}/.output", publicDir: "{{ output.dir }}/public", }, prerender: { crawlLinks: true, }, commands: { preview: "npx serve ./public", }, }, { name: "static" as const, static: true, } ); const githubPages = defineNitroPreset( { extends: "static", commands: { deploy: "npx gh-pages --dotfiles -d ./public", }, prerender: { routes: [ "/", // https://docs.github.com/en/pages/getting-started-with-github-pages/creating-a-custom-404-page-for-your-github-pages-site "/404.html", ], }, hooks: { async compiled(nitro) { await fsp.writeFile(join(nitro.options.output.publicDir, ".nojekyll"), ""); }, }, }, { name: "github-pages" as const, static: true, } ); const gitlabPages = defineNitroPreset( { extends: "static", prerender: { routes: [ "/", // https://docs.gitlab.com/ee/user/project/pages/introduction.html#custom-error-codes-pages "/404.html", ], }, }, { name: "gitlab-pages" as const, static: true, } ); export default [_static, githubPages, gitlabPages] as const; ================================================ FILE: src/presets/_types.gen.ts ================================================ // Auto-generated using gen-presets script import type { PresetOptions as AwsAmplifyOptions } from "./aws-amplify/preset.ts"; import type { PresetOptions as AwsLambdaOptions } from "./aws-lambda/preset.ts"; import type { PresetOptions as AzureOptions } from "./azure/preset.ts"; import type { PresetOptions as CloudflareOptions } from "./cloudflare/preset.ts"; import type { PresetOptions as FirebaseOptions } from "./firebase/preset.ts"; import type { PresetOptions as NetlifyOptions } from "./netlify/preset.ts"; import type { PresetOptions as VercelOptions } from "./vercel/preset.ts"; import type { PresetOptions as ZephyrOptions } from "./zephyr/preset.ts"; export interface PresetOptions { awsAmplify?: AwsAmplifyOptions; awsLambda?: AwsLambdaOptions; azure?: AzureOptions; cloudflare?: CloudflareOptions; firebase?: FirebaseOptions; netlify?: NetlifyOptions; vercel?: VercelOptions; zephyr?: ZephyrOptions; } export const presetsWithConfig = ["awsAmplify","awsLambda","azure","cloudflare","firebase","netlify","vercel","zephyr"] as const; export type PresetName = "alwaysdata" | "aws-amplify" | "aws-lambda" | "azure-swa" | "base-worker" | "bun" | "cleavr" | "cloudflare-dev" | "cloudflare-durable" | "cloudflare-module" | "cloudflare-pages" | "cloudflare-pages-static" | "deno" | "deno-deploy" | "deno-server" | "digital-ocean" | "firebase-app-hosting" | "flight-control" | "genezio" | "github-pages" | "gitlab-pages" | "heroku" | "iis-handler" | "iis-node" | "koyeb" | "netlify" | "netlify-edge" | "netlify-static" | "nitro-dev" | "nitro-prerender" | "node" | "node-cluster" | "node-middleware" | "node-server" | "platform-sh" | "render-com" | "standard" | "static" | "stormkit" | "vercel" | "vercel-static" | "winterjs" | "zeabur" | "zeabur-static" | "zephyr" | "zerops" | "zerops-static"; export type PresetNameInput = "alwaysdata" | "aws-amplify" | "awsAmplify" | "aws_amplify" | "aws-lambda" | "awsLambda" | "aws_lambda" | "azure-swa" | "azureSwa" | "azure_swa" | "base-worker" | "baseWorker" | "base_worker" | "bun" | "cleavr" | "cloudflare-dev" | "cloudflareDev" | "cloudflare_dev" | "cloudflare-durable" | "cloudflareDurable" | "cloudflare_durable" | "cloudflare-module" | "cloudflareModule" | "cloudflare_module" | "cloudflare-pages" | "cloudflarePages" | "cloudflare_pages" | "cloudflare-pages-static" | "cloudflarePagesStatic" | "cloudflare_pages_static" | "deno" | "deno-deploy" | "denoDeploy" | "deno_deploy" | "deno-server" | "denoServer" | "deno_server" | "digital-ocean" | "digitalOcean" | "digital_ocean" | "firebase-app-hosting" | "firebaseAppHosting" | "firebase_app_hosting" | "flight-control" | "flightControl" | "flight_control" | "genezio" | "github-pages" | "githubPages" | "github_pages" | "gitlab-pages" | "gitlabPages" | "gitlab_pages" | "heroku" | "iis-handler" | "iisHandler" | "iis_handler" | "iis-node" | "iisNode" | "iis_node" | "koyeb" | "netlify" | "netlify-edge" | "netlifyEdge" | "netlify_edge" | "netlify-static" | "netlifyStatic" | "netlify_static" | "nitro-dev" | "nitroDev" | "nitro_dev" | "nitro-prerender" | "nitroPrerender" | "nitro_prerender" | "node" | "node-cluster" | "nodeCluster" | "node_cluster" | "node-middleware" | "nodeMiddleware" | "node_middleware" | "node-server" | "nodeServer" | "node_server" | "platform-sh" | "platformSh" | "platform_sh" | "render-com" | "renderCom" | "render_com" | "standard" | "static" | "stormkit" | "vercel" | "vercel-static" | "vercelStatic" | "vercel_static" | "winterjs" | "zeabur" | "zeabur-static" | "zeaburStatic" | "zeabur_static" | "zephyr" | "zerops" | "zerops-static" | "zeropsStatic" | "zerops_static" | (string & {}); ================================================ FILE: src/presets/_utils/fs.ts ================================================ import fsp from "node:fs/promises"; import { relative, dirname } from "pathe"; import consola from "consola"; import { colors } from "consola/utils"; export function prettyPath(p: string, highlight = true) { p = relative(process.cwd(), p); return highlight ? colors.cyan(p) : p; } export async function writeFile(file: string, contents: Buffer | string, log = false) { await fsp.mkdir(dirname(file), { recursive: true }); await fsp.writeFile(file, contents, typeof contents === "string" ? "utf8" : undefined); if (log) { consola.info("Generated", prettyPath(file)); } } export async function isDirectory(path: string) { try { return (await fsp.stat(path)).isDirectory(); } catch { return false; } } ================================================ FILE: src/presets/_utils/preset.ts ================================================ import type { NitroPreset, NitroPresetMeta } from "nitro/types"; import { presetsDir } from "nitro/meta"; import { resolve } from "node:path"; export function defineNitroPreset

    ( preset: P, meta?: M ): P & { _meta: NitroPresetMeta } { if (typeof preset !== "function" && preset.entry && preset.entry.startsWith(".")) { preset.entry = resolve(presetsDir, preset.entry); } return { ...preset, _meta: meta } as P & { _meta: M }; } ================================================ FILE: src/presets/alwaysdata/preset.ts ================================================ import { defineNitroPreset } from "../_utils/preset.ts"; const alwaysdata = defineNitroPreset( { extends: "node-server", serveStatic: true, commands: { deploy: "rsync -rRt --info=progress2 ./ [account]@ssh-[account].alwaysdata.net:www/my-app", }, }, { name: "alwaysdata" as const, } ); export default [alwaysdata] as const; ================================================ FILE: src/presets/aws-amplify/preset.ts ================================================ import { defineNitroPreset } from "../_utils/preset.ts"; import { writeAmplifyFiles } from "./utils.ts"; export type { AWSAmplifyOptions as PresetOptions } from "./types.ts"; const awsAmplify = defineNitroPreset( { entry: "./aws-amplify/runtime/aws-amplify", manifest: { // https://docs.aws.amazon.com/amplify/latest/userguide/environment-variables.html#amplify-console-environment-variables deploymentId: process.env.AWS_JOB_ID, }, serveStatic: true, output: { dir: "{{ rootDir }}/.amplify-hosting", serverDir: "{{ output.dir }}/compute/default", publicDir: "{{ output.dir }}/static{{ baseURL }}", }, commands: { preview: "node ./compute/default/server.js", }, hooks: { async compiled(nitro) { await writeAmplifyFiles(nitro); }, }, }, { name: "aws-amplify" as const, stdName: "aws_amplify", } ); export default [awsAmplify] as const; ================================================ FILE: src/presets/aws-amplify/runtime/aws-amplify.ts ================================================ import "#nitro/virtual/polyfills"; import { useNitroApp } from "nitro/app"; import { Server } from "node:http"; import type { NodeHttp1Handler } from "srvx"; import { toNodeHandler } from "srvx/node"; const nitroApp = useNitroApp(); const server = new Server(toNodeHandler(nitroApp.fetch) as NodeHttp1Handler); // @ts-ignore server.listen(3000, (err) => { if (err) { console.error(err); } else { console.log(`Listening on http://localhost:3000 (AWS Amplify Hosting)`); } }); ================================================ FILE: src/presets/aws-amplify/types.ts ================================================ export interface AmplifyComputeConfig { /** * The name property dictates the name of the provisioned compute resource. It is also the name * of the sub-directory in the `compute` folder in the deployment bundle. */ name: string; /** * The runtime property dictates the runtime of the provisioned compute resource. * Values are subset of https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html */ runtime: "nodejs20.x" | "nodejs22.x"; /** * Specifies the starting file from which code will run for the given compute resource. * The specified file should exist inside the given sub-directory that represents a compute resource. * * @example `"entrypoint.js"` */ entrypoint: string; } export type AmplifyRouteTarget = | { kind: "Static"; cacheControl?: string } | { kind: "ImageOptimization"; cacheControl?: string } | { kind: "Compute"; /** * A string that indicates the name of the sub-directory in the given deployment bundle that * contains the primitive's executable code. Valid and required only for the Compute primitive. * The value here should point to one of the compute resources present in the given * deployment bundle. The only supported value for this field is default. */ src: string; }; export type AmplifyRoute = { /** * The path defines a glob pattern that matches incoming request paths (excluding querystring). * The first match in a given list of rules determines which routing rule is applied to the incoming request. * Only the following wildcard characters are supported as far as pattern matching is concerned: `*` (matches 0 or more characters) * * _Note_: The "/*" pattern is called a catch-all pattern and will match all incoming requests. * It is special because fallback routing is only supported for catch-all routes. * */ path: string; /** * An object that dictates the target to route the matched request to. */ target: AmplifyRouteTarget; /** An object that dictates the target to fallback to if the original target returns a 404. */ fallback?: AmplifyRouteTarget; }; export type AmplifyImageSettings = { /** Array of supported image widths */ sizes: number[]; /** * Array of allowed external domains that can use Image Optimization. * Leave empty for only allowing the deployment domain to use Image Optimization. */ domains: string[]; /** * Array of allowed external patterns that can use Image Optimization. * Similar to `domains` but provides more control with RegExp. */ remotePatterns: { /** The protocol of the allowed remote pattern. Can be `http` or `https`. */ protocol?: "http" | "https"; /** * The hostname of the allowed remote pattern. * Can be literal or wildcard. Single `*` matches a single subdomain. * Double `**` matches any number of subdomains. * We will disallow blanket wildcards of `**` with nothing else. */ hostname: string; /** The port of the allowed remote pattern. */ port?: string; /** The pathname of the allowed remote pattern. */ pathname?: string; }[]; /** Array of allowed output image formats. */ formats: ("image/avif" | "image/webp" | "image/gif" | "image/png" | "image/jpeg")[]; /** Cache duration (in seconds) for the optimized images. */ minimumCacheTTL: number; /** Allow SVG input image URLs. This is disabled by default for security purposes. */ dangerouslyAllowSVG: boolean; }; export interface AmplifyDeployManifest { /** The `version` property dictates the version of the Deployment Specification that has been implemented */ version: 1; /** * The routes property allows framework authors to leverage the routing rules primitive. * Routing rules provide a mechanism by which incoming request paths can be routed to a specific target * in the deployment bundle. Routing rules only dictate the destination of an incoming request and they are * applied after rewrite/redirect rules have already transformed the request. * * Limits for routing rules: * - A limit of 25 rules will be enforced on the routes array */ routes?: AmplifyRoute[]; /** * The imageSettings property allows framework authors to customize the behavior of the image optimization primitive, * which provides on-demand optimization of images at runtime. */ imageSettings?: AmplifyImageSettings; /** * Metadata about the provisioned compute resource(s). Each item in the array is an object that contains metadata * about that compute resource. * * For example, given the following directory structure: * ``` * .amplify * └── compute * └── default * └── index.js * ``` * The `computeResources` property would look like: * ``` * [ * { * name: 'default', * runtime: 'nodejs16.x', * entrypoint: 'index.js', * } * ] * ``` */ computeResources?: AmplifyComputeConfig[]; // Framework Metadata framework: { name: string; version: string; }; } export interface AWSAmplifyOptions { catchAllStaticFallback?: boolean; imageOptimization?: { path?: string; cacheControl?: string; }; imageSettings?: AmplifyImageSettings; runtime?: "nodejs20.x" | "nodejs22.x"; } ================================================ FILE: src/presets/aws-amplify/utils.ts ================================================ import { writeFile } from "node:fs/promises"; import { resolve } from "node:path"; import type { Nitro } from "nitro/types"; import { joinURL } from "ufo"; import type { AmplifyDeployManifest, AmplifyRoute, AmplifyRouteTarget } from "./types.ts"; export async function writeAmplifyFiles(nitro: Nitro) { const outDir = nitro.options.output.dir; // Generate routes const routes: AmplifyRoute[] = []; let hasWildcardPublicAsset = false; if (nitro.options.awsAmplify?.imageOptimization && !nitro.options.static) { const { path, cacheControl } = nitro.options.awsAmplify?.imageOptimization || {}; if (path) { routes.push({ path, target: { kind: "ImageOptimization", cacheControl, }, }); } } const computeTarget: AmplifyRouteTarget = nitro.options.static ? { kind: "Static" } : { kind: "Compute", src: "default" }; for (const publicAsset of nitro.options.publicAssets) { if (!publicAsset.baseURL || publicAsset.baseURL === "/") { hasWildcardPublicAsset = true; continue; } routes.push({ path: `${publicAsset.baseURL!.replace(/\/$/, "")}/*`, target: { kind: "Static", cacheControl: publicAsset.maxAge > 0 ? `public, max-age=${publicAsset.maxAge}, immutable` : undefined, }, fallback: publicAsset.fallthrough ? computeTarget : undefined, }); } if (hasWildcardPublicAsset && !nitro.options.static) { routes.push({ path: "/*.*", target: { kind: "Static", }, fallback: computeTarget, }); } routes.push({ path: "/*", target: computeTarget, fallback: hasWildcardPublicAsset && nitro.options.awsAmplify?.catchAllStaticFallback ? { kind: "Static", } : undefined, }); // Prefix with baseURL for (const route of routes) { if (route.path !== "/*") { route.path = joinURL(nitro.options.baseURL, route.path); } } // Generate deploy-manifest.json const deployManifest: AmplifyDeployManifest = { version: 1, routes, imageSettings: nitro.options.awsAmplify?.imageSettings || undefined, computeResources: nitro.options.static ? undefined : [ { name: "default", entrypoint: "server.js", runtime: nitro.options.awsAmplify?.runtime || "nodejs20.x", }, ], framework: { name: nitro.options.framework.name || "nitro", version: nitro.options.framework.version || "0.0.0", }, }; await writeFile(resolve(outDir, "deploy-manifest.json"), JSON.stringify(deployManifest, null, 2)); // Write server.js (CJS) if (!nitro.options.static) { await writeFile(resolve(outDir, "compute/default/server.js"), `import("./index.mjs")`); } } ================================================ FILE: src/presets/aws-lambda/preset.ts ================================================ import { defineNitroPreset } from "../_utils/preset.ts"; export type { AwsLambdaOptions as PresetOptions } from "./types.ts"; const awsLambda = defineNitroPreset( { entry: "./aws-lambda/runtime/aws-lambda", awsLambda: { streaming: false, }, hooks: { "rollup:before": (nitro, rollupConfig) => { if (nitro.options.awsLambda?.streaming) { (rollupConfig.input as string) += "-streaming"; } }, }, }, { name: "aws-lambda" as const, } ); export default [awsLambda] as const; ================================================ FILE: src/presets/aws-lambda/runtime/_utils.ts ================================================ import type { APIGatewayProxyEvent, APIGatewayProxyEventV2 } from "aws-lambda"; import type { ServerRequest } from "srvx"; import { stringifyQuery } from "ufo"; // Incoming (AWS => Web) export function awsRequest( event: APIGatewayProxyEvent | APIGatewayProxyEventV2, context: unknown ): ServerRequest { const method = awsEventMethod(event); const url = awsEventURL(event); const headers = awsEventHeaders(event); const body = awsEventBody(event); const req = new Request(url, { method, headers, body }) as ServerRequest; // srvx compatibility req.runtime ??= { name: "aws-lambda" }; // @ts-expect-error (add to srvx types) req.runtime.aws ??= { event, context } as any; return new Request(url, { method, headers, body }); } function awsEventMethod(event: APIGatewayProxyEvent | APIGatewayProxyEventV2): string { return ( (event as APIGatewayProxyEvent).httpMethod || (event as APIGatewayProxyEventV2).requestContext?.http?.method || "GET" ); } function awsEventURL(event: APIGatewayProxyEvent | APIGatewayProxyEventV2): URL { const hostname = event.headers.host || event.headers.Host || event.requestContext?.domainName || "."; const path = (event as APIGatewayProxyEvent).path || (event as APIGatewayProxyEventV2).rawPath; const query = awsEventQuery(event); const protocol = (event.headers["X-Forwarded-Proto"] || event.headers["x-forwarded-proto"]) === "http" ? "http" : "https"; return new URL(`${path}${query ? `?${query}` : ""}`, `${protocol}://${hostname}`); } function awsEventQuery(event: APIGatewayProxyEvent | APIGatewayProxyEventV2) { if (typeof (event as APIGatewayProxyEventV2).rawQueryString === "string") { return (event as APIGatewayProxyEventV2).rawQueryString; } const queryObj = { ...event.queryStringParameters, ...(event as APIGatewayProxyEvent).multiValueQueryStringParameters, }; return stringifyQuery(queryObj); } function awsEventHeaders(event: APIGatewayProxyEvent | APIGatewayProxyEventV2): Headers { const headers = new Headers(); for (const [key, value] of Object.entries(event.headers)) { if (value) { headers.set(key, value); } } if ("cookies" in event && event.cookies) { for (const cookie of event.cookies) { headers.append("cookie", cookie); } } return headers; } function awsEventBody(event: APIGatewayProxyEvent | APIGatewayProxyEventV2): BodyInit | undefined { if (!event.body) { return undefined; } if (event.isBase64Encoded) { return Buffer.from(event.body || "", "base64"); } return event.body; } // Outgoing (Web => AWS) export function awsResponseHeaders(response: Response) { const headers: Record = Object.create(null); for (const [key, value] of response.headers) { if (value) { headers[key] = Array.isArray(value) ? value.join(",") : String(value); } } const cookies = response.headers.getSetCookie(); return cookies.length > 0 ? { headers, cookies, // ApiGateway v2 multiValueHeaders: { "set-cookie": cookies }, // ApiGateway v1 } : { headers }; } // AWS Lambda proxy integrations requires base64 encoded buffers // binaryMediaTypes should be */* // see https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-payload-encodings.html export async function awsResponseBody( response: Response ): Promise<{ body: string; isBase64Encoded?: boolean }> { if (!response.body) { return { body: "" }; } const buffer = await toBuffer(response.body as any); const contentType = response.headers.get("content-type") || ""; return isTextType(contentType) ? { body: buffer.toString("utf8") } : { body: buffer.toString("base64"), isBase64Encoded: true }; } function isTextType(contentType = "") { return /^text\/|\/(javascript|json|xml)|utf-?8/i.test(contentType); } function toBuffer(data: ReadableStream): Promise { return new Promise((resolve, reject) => { const chunks: Buffer[] = []; data .pipeTo( new WritableStream({ write(chunk) { chunks.push(chunk); }, close() { resolve(Buffer.concat(chunks)); }, abort(reason) { reject(reason); }, }) ) .catch(reject); }); } ================================================ FILE: src/presets/aws-lambda/runtime/aws-lambda-streaming.ts ================================================ import "#nitro/virtual/polyfills"; import { useNitroApp } from "nitro/app"; import { awsRequest, awsResponseHeaders } from "./_utils.ts"; import type { StreamingResponse } from "@netlify/functions"; import type { Readable } from "node:stream"; import type { APIGatewayProxyEventV2 } from "aws-lambda"; const nitroApp = useNitroApp(); export const handler = awslambda.streamifyResponse( async (event: APIGatewayProxyEventV2, responseStream, context) => { const request = awsRequest(event, context); const response = await nitroApp.fetch(request); const httpResponseMetadata: Omit = { statusCode: response.status, ...awsResponseHeaders(response), }; if (!httpResponseMetadata.headers!["transfer-encoding"]) { httpResponseMetadata.headers!["transfer-encoding"] = "chunked"; } const body = response.body ?? new ReadableStream({ start(controller) { controller.enqueue(""); controller.close(); }, }); const writer = awslambda.HttpResponseStream.from(responseStream, httpResponseMetadata); const reader = body.getReader(); await streamToNodeStream(reader, responseStream); writer.end(); } ); async function streamToNodeStream( reader: Readable | ReadableStreamDefaultReader, writer: NodeJS.WritableStream ) { let readResult = await reader.read(); while (!readResult.done) { writer.write(readResult.value); readResult = await reader.read(); } writer.end(); } ================================================ FILE: src/presets/aws-lambda/runtime/aws-lambda.ts ================================================ import "#nitro/virtual/polyfills"; import { useNitroApp } from "nitro/app"; import { awsRequest, awsResponseHeaders, awsResponseBody } from "./_utils.ts"; import type { APIGatewayProxyEvent, APIGatewayProxyEventV2, APIGatewayProxyResult, APIGatewayProxyResultV2, Context, } from "aws-lambda"; const nitroApp = useNitroApp(); export async function handler( event: APIGatewayProxyEvent | APIGatewayProxyEventV2, context: Context ): Promise { const request = awsRequest(event, context); const response = await nitroApp.fetch(request); return { statusCode: response.status, ...awsResponseHeaders(response), ...(await awsResponseBody(response)), }; } ================================================ FILE: src/presets/aws-lambda/types.ts ================================================ export interface AwsLambdaOptions { streaming?: boolean; } ================================================ FILE: src/presets/azure/preset.ts ================================================ import { defineNitroPreset } from "../_utils/preset.ts"; import type { Nitro } from "nitro/types"; import { writeSWARoutes } from "./utils.ts"; export type { AzureOptions as PresetOptions } from "./types.ts"; const azureSWA = defineNitroPreset( { entry: "./azure/runtime/azure-swa", output: { serverDir: "{{ output.dir }}/server/functions", publicDir: "{{ output.dir }}/public/{{ baseURL }}", }, commands: { preview: "npx @azure/static-web-apps-cli start ./public --api-location ./server", }, hooks: { async compiled(ctx: Nitro) { await writeSWARoutes(ctx); }, }, }, { name: "azure-swa" as const, stdName: "azure_static", } ); export default [azureSWA] as const; ================================================ FILE: src/presets/azure/runtime/_utils.ts ================================================ import type { Cookie } from "@azure/functions"; import { parse } from "cookie-es"; export function getAzureParsedCookiesFromHeaders(headers: Headers): Cookie[] { const setCookieHeader = headers.getSetCookie(); if (setCookieHeader.length === 0) { return []; } const azureCookies: Cookie[] = []; for (const setCookieStr of setCookieHeader) { const setCookie = Object.entries(parse(setCookieStr)); if (setCookie.length === 0) { continue; } const [[key, value], ..._setCookieOptions] = setCookie; const setCookieOptions = Object.fromEntries( _setCookieOptions.map(([k, v]) => [k.toLowerCase(), v]) ); const cookieObject: Cookie = { name: key, value, domain: setCookieOptions.domain, path: setCookieOptions.path, expires: parseNumberOrDate(setCookieOptions.expires), sameSite: setCookieOptions.samesite as "Lax" | "Strict" | "None", maxAge: parseNumber(setCookieOptions["max-age"]), secure: setCookieStr.includes("Secure") ? true : undefined, httpOnly: setCookieStr.includes("HttpOnly") ? true : undefined, }; azureCookies.push(cookieObject); } return azureCookies; } function parseNumberOrDate(expires: string) { const expiresAsNumber = parseNumber(expires); if (expiresAsNumber !== undefined) { return expiresAsNumber; } // Convert to Date if possible const expiresAsDate = new Date(expires); if (!Number.isNaN(expiresAsDate.getTime())) { return expiresAsDate; } } function parseNumber(maxAge: string) { if (!maxAge) { return undefined; } // Convert to number if possible const maxAgeAsNumber = Number(maxAge); if (!Number.isNaN(maxAgeAsNumber)) { return maxAgeAsNumber; } } ================================================ FILE: src/presets/azure/runtime/azure-swa.ts ================================================ import "#nitro/virtual/polyfills"; import { parseURL } from "ufo"; import { useNitroApp } from "nitro/app"; import { getAzureParsedCookiesFromHeaders } from "./_utils.ts"; import type { HttpRequest, HttpResponse, HttpResponseSimple } from "@azure/functions"; const nitroApp = useNitroApp(); export async function handle(context: { res: HttpResponse }, req: HttpRequest) { let url: string; if (req.headers["x-ms-original-url"]) { // This URL has been proxied as there was no static file matching it. const parsedURL = parseURL(req.headers["x-ms-original-url"]); url = parsedURL.pathname + parsedURL.search; } else { // Because Azure SWA handles /api/* calls differently they // never hit the proxy and we have to reconstitute the URL. url = "/api/" + (req.params.url || ""); } const request = new Request(url, { method: req.method || undefined, // https://github.com/Azure/azure-functions-nodejs-worker/issues/294 // https://github.com/Azure/azure-functions-host/issues/293 body: req.bufferBody ?? req.rawBody, }); const response = await nitroApp.fetch(request); // (v3 - current) https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-node?tabs=typescript%2Cwindows%2Cazure-cli&pivots=nodejs-model-v3#http-response // (v4) https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-node?tabs=typescript%2Cwindows%2Cazure-cli&pivots=nodejs-model-v4#http-response context.res = { status: response.status, body: response.body, cookies: getAzureParsedCookiesFromHeaders(response.headers), headers: Object.fromEntries( [...response.headers.entries()].filter(([key]) => key !== "set-cookie") ), } satisfies HttpResponseSimple; } ================================================ FILE: src/presets/azure/types.ts ================================================ export interface AzureOptions { config?: { platform?: { apiRuntime?: string; [key: string]: unknown; }; navigationFallback?: { rewrite?: string; [key: string]: unknown; }; [key: string]: unknown; }; } ================================================ FILE: src/presets/azure/utils.ts ================================================ import fsp from "node:fs/promises"; import { writeFile } from "../_utils/fs.ts"; import type { Nitro } from "nitro/types"; import { join, resolve } from "pathe"; export async function writeSWARoutes(nitro: Nitro) { const host = { version: "2.0", }; // https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-node?tabs=typescript%2Cwindows%2Cazure-cli&pivots=nodejs-model-v4#supported-versions const supportedNodeVersions = new Set(["20", "22"]); let nodeVersion = "18"; try { const currentNodeVersion = JSON.parse( await fsp.readFile(join(nitro.options.rootDir, "package.json"), "utf8") ).engines.node; if (supportedNodeVersions.has(currentNodeVersion)) { nodeVersion = currentNodeVersion; } } catch { const currentNodeVersion = process.versions.node.slice(0, 2); if (supportedNodeVersions.has(currentNodeVersion)) { nodeVersion = currentNodeVersion; } } // Merge custom config into the generated config const config = { ...nitro.options.azure?.config, // Overwrite routes for now, we will add existing routes after generating routes routes: [] as Array<{ route: string; redirect?: string; rewrite?: string }>, platform: { apiRuntime: `node:${nodeVersion}`, ...nitro.options.azure?.config?.platform, }, navigationFallback: { rewrite: "/api/server", ...nitro.options.azure?.config?.navigationFallback, }, }; const routeFiles = nitro._prerenderedRoutes || []; const indexFileExists = routeFiles.some((route) => route.fileName === "/index.html"); if (!indexFileExists) { config.routes.unshift( { route: "/index.html", redirect: "/", }, { route: "/", rewrite: "/api/server", } ); } const suffix = "/index.html".length; for (const { fileName } of routeFiles) { if (!fileName || !fileName.endsWith("/index.html")) { continue; } config.routes.unshift({ route: fileName.slice(0, -suffix) || "/", rewrite: fileName, }); } for (const { fileName } of routeFiles) { if (!fileName || !fileName.endsWith(".html") || fileName.endsWith("index.html")) { continue; } const route = fileName.slice(0, -".html".length); const existingRouteIndex = config.routes.findIndex((_route) => _route.route === route); if (existingRouteIndex !== -1) { config.routes.splice(existingRouteIndex, 1); } config.routes.unshift({ route, rewrite: fileName, }); } // Prepend custom routes to the beginning of the routes array and override if they exist if ( nitro.options.azure?.config && "routes" in nitro.options.azure.config && Array.isArray(nitro.options.azure.config.routes) ) { // We iterate through the reverse so the order in the custom config is persisted for (const customRoute of nitro.options.azure.config.routes.reverse()) { const existingRouteMatchIndex = config.routes.findIndex( (value) => value.route === customRoute.route ); if (existingRouteMatchIndex === -1) { // If we don't find a match, put the customRoute at the beginning of the array config.routes.unshift(customRoute); } else { // Otherwise override the existing route with our customRoute config.routes[existingRouteMatchIndex] = customRoute; } } } const functionDefinition = { entryPoint: "handle", bindings: [ { authLevel: "anonymous", type: "httpTrigger", direction: "in", name: "req", route: "{*url}", methods: ["delete", "get", "head", "options", "patch", "post", "put"], }, { type: "http", direction: "out", name: "res", }, ], }; await writeFile( resolve(nitro.options.output.serverDir, "function.json"), JSON.stringify(functionDefinition, null, 2) ); await writeFile( resolve(nitro.options.output.serverDir, "../host.json"), JSON.stringify(host, null, 2) ); const stubPackageJson = resolve(nitro.options.output.serverDir, "../package.json"); await writeFile(stubPackageJson, JSON.stringify({ private: true })); await writeFile( resolve(nitro.options.rootDir, "staticwebapp.config.json"), JSON.stringify(config, null, 2) ); if (!indexFileExists) { const baseURLSegments = nitro.options.baseURL.split("/").filter(Boolean); const relativePrefix = baseURLSegments.map(() => "..").join("/"); await writeFile( resolve( nitro.options.output.publicDir, relativePrefix ? `${relativePrefix}/index.html` : "index.html" ), "" ); } } ================================================ FILE: src/presets/bun/preset.ts ================================================ import { defineNitroPreset } from "../_utils/preset.ts"; const bun = defineNitroPreset( { entry: "./bun/runtime/bun", serveStatic: true, // https://bun.sh/docs/runtime/modules#resolution exportConditions: ["bun"], commands: { preview: "bun run ./server/index.mjs", }, }, { name: "bun" as const, } ); export default [bun] as const; ================================================ FILE: src/presets/bun/runtime/bun.ts ================================================ import "#nitro/virtual/polyfills"; import type { ServerRequest } from "srvx"; import { serve } from "srvx/bun"; import wsAdapter from "crossws/adapters/bun"; import { useNitroApp } from "nitro/app"; import { startScheduleRunner } from "#nitro/runtime/task"; import { trapUnhandledErrors } from "#nitro/runtime/error/hooks"; import { resolveWebsocketHooks } from "#nitro/runtime/app"; const _parsedPort = Number.parseInt(process.env.NITRO_PORT ?? process.env.PORT ?? ""); const port = Number.isNaN(_parsedPort) ? 3000 : _parsedPort; const host = process.env.NITRO_HOST || process.env.HOST; const cert = process.env.NITRO_SSL_CERT; const key = process.env.NITRO_SSL_KEY; // const socketPath = process.env.NITRO_UNIX_SOCKET; // TODO const nitroApp = useNitroApp(); let _fetch = nitroApp.fetch; const ws = import.meta._websocket ? wsAdapter({ resolve: resolveWebsocketHooks }) : undefined; if (import.meta._websocket) { _fetch = (req: ServerRequest) => { if (req.headers.get("upgrade") === "websocket") { return ws!.handleUpgrade(req, req.runtime!.bun!.server) as Promise; } return nitroApp.fetch(req); }; } const server = serve({ port, hostname: host, tls: cert && key ? { cert, key } : undefined, fetch: _fetch, bun: { websocket: import.meta._websocket ? ws?.websocket : undefined, }, }); trapUnhandledErrors(); // Scheduled tasks if (import.meta._tasks) { startScheduleRunner({ waitUntil: server.waitUntil }); } export default {}; ================================================ FILE: src/presets/cleavr/preset.ts ================================================ import { defineNitroPreset } from "../_utils/preset.ts"; const cleavr = defineNitroPreset( { extends: "node-server", serveStatic: true, }, { name: "cleavr" as const, stdName: "cleavr", } ); export default [cleavr] as const; ================================================ FILE: src/presets/cloudflare/dev.ts ================================================ import { resolve } from "node:path"; import { promises as fs } from "node:fs"; import type { Nitro } from "nitro/types"; import { findFile } from "pkg-types"; import { resolveModulePath } from "exsolve"; import { presetsDir } from "nitro/meta"; export async function cloudflareDevModule(nitro: Nitro) { if (!nitro.options.dev) { return; // Production doesn't need this } nitro.options.unenv.push({ meta: { name: "nitro:cloudflare-dev", }, alias: { "cloudflare:workers": resolve(presetsDir, "cloudflare/runtime/shims/workers.dev.mjs"), }, }); // Try to resolve wrangler const wranglerPath = await resolveModulePath("wrangler", { from: nitro.options.rootDir, try: true, }); if (!wranglerPath) { nitro.logger.warn( "Wrangler is not installed. Please install it using `npx nypm i wrangler` to enable dev emulation." ); return; } const config = { // compatibility with legacy nitro-cloudflare-dev module ...(nitro.options as any).cloudflareDev, ...nitro.options.cloudflare?.dev, } as NonNullable["dev"]>; // Find wrangler.json > wrangler.jsonc > wrangler.toml let configPath = config.configPath; if (!configPath) { configPath = await findFile(["wrangler.json", "wrangler.jsonc", "wrangler.toml"], { startingFrom: nitro.options.rootDir, }).catch(() => undefined); } // Resolve the persist dir const persistDir = resolve(nitro.options.rootDir, config.persistDir || ".wrangler/state/v3"); // Add `.wrangler/state/v3` to `.gitignore` const gitIgnorePath = await findFile(".gitignore", { startingFrom: nitro.options.rootDir, }).catch(() => undefined); // let addedToGitIgnore = false; if (gitIgnorePath && persistDir === ".wrangler/state/v3") { const gitIgnore = await fs.readFile(gitIgnorePath, "utf8"); if (!gitIgnore.includes(".wrangler/state/v3")) { await fs.writeFile(gitIgnorePath, gitIgnore + "\n.wrangler/state/v3\n").catch(() => {}); // addedToGitIgnore = true; } } // Share config to the runtime nitro.options.runtimeConfig.wrangler = { ...nitro.options.runtimeConfig.wrangler, configPath, persistDir, environment: config.environment, }; // Add plugin to inject bindings to dev server nitro.options.plugins = nitro.options.plugins || []; nitro.options.plugins.unshift( resolveModulePath("./cloudflare/runtime/plugin.dev", { from: presetsDir, extensions: [".mjs", ".ts"], }) ); } ================================================ FILE: src/presets/cloudflare/entry-exports.ts ================================================ import type { Nitro } from "nitro/types"; import { resolveModulePath } from "exsolve"; import { prettyPath } from "../../utils/fs.ts"; const RESOLVE_EXTENSIONS = [".ts", ".js", ".mts", ".mjs"]; export async function setupEntryExports(nitro: Nitro) { const exportsEntry = resolveExportsEntry(nitro); if (!exportsEntry) return; const originalEntry = nitro.options.entry; const virtualEntryId = (nitro.options.entry = "#nitro/virtual/cloudflare-server-entry"); nitro.options.virtual[virtualEntryId] = /* ts */ ` export * from "${exportsEntry}"; export * from "${originalEntry}"; export { default } from "${originalEntry}"; `; } function resolveExportsEntry(nitro: Nitro) { const entry = resolveModulePath(nitro.options.cloudflare?.exports || "./exports.cloudflare.ts", { from: nitro.options.rootDir, extensions: RESOLVE_EXTENSIONS, try: true, }); if (!entry && nitro.options.cloudflare?.exports) { nitro.logger.warn( `Your custom Cloudflare entrypoint \`${prettyPath(nitro.options.cloudflare.exports)}\` file does not exist.` ); } else if (entry && !nitro.options.cloudflare?.exports) { nitro.logger.info(`Detected \`${prettyPath(entry)}\` as Cloudflare entrypoint.`); } return entry; } ================================================ FILE: src/presets/cloudflare/preset.ts ================================================ import { defineNitroPreset } from "../_utils/preset.ts"; import { writeFile } from "../_utils/fs.ts"; import type { Nitro } from "nitro/types"; import type { Plugin } from "rollup"; import { resolve } from "pathe"; import { unenvCfExternals } from "./unenv/preset.ts"; import { enableNodeCompat, writeWranglerConfig, writeCFRoutes, writeCFHeaders, writeCFPagesRedirects, } from "./utils.ts"; import { cloudflareDevModule } from "./dev.ts"; import { setupEntryExports } from "./entry-exports.ts"; // Some bundlers (e.g. rolldown-vite) emit `createRequire(import.meta.url)` in // shared chunks. On Cloudflare Workers `import.meta.url` is `undefined`, which // causes `createRequire` to throw at runtime. This output plugin rewrites those // call sites to fall back to a synthetic `file:///` URL so that `createRequire` // succeeds and any subsequent `require()` calls go through the normal Node.js // compat layer provided by the Workers runtime. // Ref: https://github.com/nitrojs/nitro/issues/4132 function guardCreateRequire(): Plugin { return { name: "nitro:cloudflare-guard-createRequire", generateBundle(_options, bundle) { for (const chunk of Object.values(bundle)) { if (chunk.type === "chunk" && chunk.code?.includes("createRequire(import.meta.url)")) { chunk.code = chunk.code.replace( /createRequire\(import\.meta\.url\)/g, 'createRequire(import.meta.url || "file:///")' ); } } }, }; } export type { CloudflareOptions as PresetOptions } from "./types.ts"; const cloudflarePages = defineNitroPreset( { extends: "base-worker", entry: "./cloudflare/runtime/cloudflare-pages", exportConditions: ["workerd"], minify: false, commands: { preview: "npx wrangler --cwd ./ pages dev", deploy: "npx wrangler --cwd ./ pages deploy", }, output: { dir: "{{ rootDir }}/dist", publicDir: "{{ output.dir }}/{{ baseURL }}", serverDir: "{{ output.dir }}/_worker.js", }, alias: { // Hotfix: Cloudflare appends /index.html if mime is not found and things like ico are not in standard lite.js! // https://github.com/nitrojs/nitro/pull/933 _mime: "mime/index.js", }, wasm: { lazy: false, esmImport: true, }, rollupConfig: { output: { entryFileNames: "index.js", format: "esm", inlineDynamicImports: false, }, plugins: [guardCreateRequire()], }, hooks: { "build:before": async (nitro) => { nitro.options.unenv.push(unenvCfExternals); await enableNodeCompat(nitro); await setupEntryExports(nitro); }, async compiled(nitro: Nitro) { await writeWranglerConfig(nitro, "pages"); await writeCFRoutes(nitro); await writeCFHeaders(nitro, "output"); await writeCFPagesRedirects(nitro); }, }, }, { name: "cloudflare-pages" as const, stdName: "cloudflare_pages", } ); const cloudflarePagesStatic = defineNitroPreset( { extends: "static", output: { dir: "{{ rootDir }}/dist", publicDir: "{{ output.dir }}/{{ baseURL }}", }, commands: { preview: "npx wrangler --cwd ./ pages dev", deploy: "npx wrangler --cwd ./ pages deploy", }, hooks: { async compiled(nitro: Nitro) { await writeCFHeaders(nitro, "output"); await writeCFPagesRedirects(nitro); }, }, }, { name: "cloudflare-pages-static" as const, stdName: "cloudflare_pages", static: true, } ); export const cloudflareDev = defineNitroPreset( { extends: "nitro-dev", modules: [cloudflareDevModule], }, { name: "cloudflare-dev" as const, aliases: ["cloudflare-module", "cloudflare-durable", "cloudflare-pages"], compatibilityDate: "2025-07-13", dev: true, } ); const cloudflareModule = defineNitroPreset( { extends: "base-worker", entry: "./cloudflare/runtime/cloudflare-module", output: { publicDir: "{{ output.dir }}/public/{{ baseURL }}", }, exportConditions: ["workerd"], minify: false, commands: { preview: "npx wrangler --cwd ./ dev", deploy: "npx wrangler --cwd ./ deploy", }, rollupConfig: { output: { format: "esm", exports: "named", inlineDynamicImports: false, }, plugins: [guardCreateRequire()], }, wasm: { lazy: false, esmImport: true, }, hooks: { "build:before": async (nitro) => { nitro.options.unenv.push(unenvCfExternals); await enableNodeCompat(nitro); await setupEntryExports(nitro); }, async compiled(nitro: Nitro) { await writeWranglerConfig(nitro, "module"); await writeCFHeaders(nitro, "public"); await writeFile( resolve(nitro.options.output.dir, "package.json"), JSON.stringify({ private: true, main: "./server/index.mjs" }, null, 2) ); await writeFile( resolve(nitro.options.output.dir, "package-lock.json"), JSON.stringify({ lockfileVersion: 1 }, null, 2) ); }, }, }, { name: "cloudflare-module" as const, stdName: "cloudflare_workers", } ); const cloudflareDurable = defineNitroPreset( { extends: "cloudflare-module", entry: "./cloudflare/runtime/cloudflare-durable", }, { name: "cloudflare-durable" as const, } ); export default [ cloudflarePages, cloudflarePagesStatic, cloudflareModule, cloudflareDurable, cloudflareDev, ]; ================================================ FILE: src/presets/cloudflare/runtime/_module-handler.ts ================================================ import "#nitro/virtual/polyfills"; import type * as CF from "@cloudflare/workers-types"; import type { ServerRequest, ServerRuntimeContext } from "srvx"; import { runCronTasks } from "#nitro/runtime/task"; import { useNitroApp, useNitroHooks } from "nitro/app"; type MaybePromise = T | Promise; export function createHandler(hooks: { fetch: ( ...params: [ ...Parameters["fetch"]>>, url: URL, cfContextExtras: any, ] ) => MaybePromise; }) { const nitroApp = useNitroApp(); const nitroHooks = useNitroHooks(); return { async fetch(request, env, context) { (globalThis as any).__env__ = env; augmentReq(request as any, { env: env as any, context }); const ctxExt = {}; const url = new URL(request.url); // Preset-specific logic if (hooks.fetch) { const res = await hooks.fetch(request, env, context, url, ctxExt); if (res) { return res; } } return (await nitroApp.fetch(request)) as any; }, scheduled(controller, env, context) { (globalThis as any).__env__ = env; context.waitUntil( nitroHooks.callHook("cloudflare:scheduled", { controller, env, context, }) || Promise.resolve() ); if (import.meta._tasks) { context.waitUntil( runCronTasks(controller.cron, { context: { cloudflare: { env, context, }, }, payload: {}, }) ); } }, email(message, env, context) { (globalThis as any).__env__ = env; context.waitUntil( nitroHooks.callHook("cloudflare:email", { message: message as any, event: message as any, // backward compat env, context, }) || Promise.resolve() ); }, queue(batch, env, context) { (globalThis as any).__env__ = env; context.waitUntil( nitroHooks.callHook("cloudflare:queue", { batch, event: batch, env, context, }) || Promise.resolve() ); }, tail(traces, env, context) { (globalThis as any).__env__ = env; context.waitUntil( nitroHooks.callHook("cloudflare:tail", { traces, env, context, }) || Promise.resolve() ); }, trace(traces, env, context) { (globalThis as any).__env__ = env; context.waitUntil( nitroHooks.callHook("cloudflare:trace", { traces, env, context, }) || Promise.resolve() ); }, } satisfies ExportedHandler; } export function augmentReq( cfReq: Request | CF.Request, ctx: NonNullable ) { const req = cfReq as ServerRequest; req.ip = cfReq.headers.get("cf-connecting-ip") || undefined; req.runtime ??= { name: "cloudflare" }; req.runtime.cloudflare = { ...req.runtime.cloudflare, ...ctx }; req.waitUntil = ctx.context?.waitUntil.bind(ctx.context); } ================================================ FILE: src/presets/cloudflare/runtime/cloudflare-durable.ts ================================================ import "#nitro/virtual/polyfills"; import type * as CF from "@cloudflare/workers-types"; import { DurableObject } from "cloudflare:workers"; import wsAdapter from "crossws/adapters/cloudflare"; import { createHandler, augmentReq } from "./_module-handler.ts"; import { useNitroApp, useNitroHooks } from "nitro/app"; import { isPublicAssetURL } from "#nitro/virtual/public-assets"; import { resolveWebsocketHooks } from "#nitro/runtime/app"; const DURABLE_BINDING = "$DurableObject"; const DURABLE_INSTANCE = "server"; interface Env { ASSETS?: { fetch: typeof CF.fetch }; [DURABLE_BINDING]?: CF.DurableObjectNamespace; } const nitroApp = useNitroApp(); const nitroHooks = useNitroHooks(); const getDurableStub = (env: Env) => { const binding = env[DURABLE_BINDING]; if (!binding) { throw new Error(`Durable Object binding "${DURABLE_BINDING}" not available.`); } const id = binding.idFromName(DURABLE_INSTANCE); return binding.get(id); }; const ws = import.meta._websocket ? wsAdapter({ resolve: resolveWebsocketHooks, instanceName: DURABLE_INSTANCE, bindingName: DURABLE_BINDING, }) : undefined; export default createHandler({ fetch(request, env, context, url, ctxExt) { // Static assets fallback (optional binding) if (env.ASSETS && isPublicAssetURL(url.pathname)) { return env.ASSETS.fetch(request as any); } // Expose stub fetch to the context ctxExt.durableFetch = (req = request) => getDurableStub(env).fetch(req as any); // Websocket upgrade // https://crossws.unjs.io/adapters/cloudflare#durable-objects if (import.meta._websocket && request.headers.get("upgrade") === "websocket") { return ws!.handleUpgrade(request, env, context); } }, }); export class $DurableObject extends DurableObject { constructor(state: DurableObjectState, env: Record) { super(state, env); state.waitUntil( nitroHooks.callHook("cloudflare:durable:init", this, { state, env, }) || Promise.resolve() ); if (import.meta._websocket) { ws!.handleDurableInit(this, state, env); } } override fetch(request: Request) { augmentReq(request, { env: this.env, context: this.ctx as any, }); if (import.meta._websocket && request.headers.get("upgrade") === "websocket") { return ws!.handleDurableUpgrade(this, request); } return nitroApp.fetch(request); } override alarm(): void | Promise { this.ctx.waitUntil(nitroHooks.callHook("cloudflare:durable:alarm", this) || Promise.resolve()); } override async webSocketMessage(client: WebSocket, message: ArrayBuffer | string) { if (import.meta._websocket) { return ws!.handleDurableMessage(this, client, message); } } override async webSocketClose( client: WebSocket, code: number, reason: string, wasClean: boolean ) { if (import.meta._websocket) { return ws!.handleDurableClose(this, client, code, reason, wasClean); } } } ================================================ FILE: src/presets/cloudflare/runtime/cloudflare-module.ts ================================================ import "#nitro/virtual/polyfills"; import type { fetch } from "@cloudflare/workers-types"; import wsAdapter from "crossws/adapters/cloudflare"; import { isPublicAssetURL } from "#nitro/virtual/public-assets"; import { createHandler } from "./_module-handler.ts"; import { resolveWebsocketHooks } from "#nitro/runtime/app"; const ws = import.meta._websocket ? wsAdapter({ resolve: resolveWebsocketHooks }) : undefined; interface Env { ASSETS?: { fetch: typeof fetch }; } export default createHandler({ fetch(cfRequest, env, context, url) { // Static assets fallback (optional binding) if (env.ASSETS && isPublicAssetURL(url.pathname)) { return env.ASSETS.fetch(cfRequest as any); } // Websocket upgrade // https://crossws.unjs.io/adapters/cloudflare if (import.meta._websocket && cfRequest.headers.get("upgrade") === "websocket") { return ws!.handleUpgrade(cfRequest, env, context); } }, }); ================================================ FILE: src/presets/cloudflare/runtime/cloudflare-pages.ts ================================================ import "#nitro/virtual/polyfills"; import type { Request as CFRequest, EventContext, ExecutionContext, } from "@cloudflare/workers-types"; import wsAdapter from "crossws/adapters/cloudflare"; import { useNitroApp } from "nitro/app"; import { isPublicAssetURL } from "#nitro/virtual/public-assets"; import { runCronTasks } from "#nitro/runtime/task"; import { resolveWebsocketHooks } from "#nitro/runtime/app"; import { augmentReq } from "./_module-handler.ts"; /** * Reference: https://developers.cloudflare.com/workers/runtime-apis/fetch-event/#parameters */ interface CFPagesEnv { ASSETS: { fetch: (request: CFRequest) => Promise }; CF_PAGES: "1"; CF_PAGES_BRANCH: string; CF_PAGES_COMMIT_SHA: string; CF_PAGES_URL: string; [key: string]: any; } const nitroApp = useNitroApp(); const ws = import.meta._websocket ? wsAdapter({ resolve: resolveWebsocketHooks }) : undefined; export default { async fetch(cfReq: CFRequest, env: CFPagesEnv, context: EventContext) { augmentReq(cfReq, { env, context: context as any, }); // Websocket upgrade // https://crossws.unjs.io/adapters/cloudflare if (import.meta._websocket && cfReq.headers.get("upgrade") === "websocket") { return ws!.handleUpgrade(cfReq, env, context as unknown as ExecutionContext); } const url = new URL(cfReq.url); if (env.ASSETS /* !miniflare */ && isPublicAssetURL(url.pathname)) { return env.ASSETS.fetch(cfReq); } return nitroApp.fetch(cfReq as any); }, scheduled(event: any, env: CFPagesEnv, context: ExecutionContext) { if (import.meta._tasks) { (globalThis as any).__env__ = env; context.waitUntil( runCronTasks(event.cron, { context: { cloudflare: { env, context, }, }, payload: {}, }) ); } }, }; ================================================ FILE: src/presets/cloudflare/runtime/plugin.dev.ts ================================================ import type { NitroAppPlugin } from "nitro/types"; import type { GetPlatformProxyOptions, PlatformProxy } from "wrangler"; import { useRuntimeConfig } from "nitro/runtime-config"; const proxy = await _getPlatformProxy().catch((error) => { console.error("Failed to initialize wrangler bindings proxy", error); return _createStubProxy(); }); (globalThis as any).__env__ = proxy.env; (globalThis as any).__wait_until__ = proxy.ctx.waitUntil.bind(proxy.ctx); const cloudflareDevPlugin: NitroAppPlugin = function (nitroApp) { nitroApp.hooks.hook("request", async (event) => { const request = event.req; (request as any).runtime ??= { name: "cloudflare" }; (request as any).runtime.cloudflare = { ...(request as any).runtime.cloudflare, env: proxy.env, context: proxy.ctx, }; (request as any).waitUntil = proxy.ctx.waitUntil.bind(proxy.ctx); (request as any).cf = proxy.cf; }); // https://github.com/pi0/nitro-cloudflare-dev/issues/5 // https://github.com/unjs/hookable/issues/98 // @ts-expect-error nitroApp.hooks._hooks.request.unshift(nitroApp.hooks._hooks.request.pop()); // Dispose proxy when Nitro is closed nitroApp.hooks.hook("close", () => { return proxy?.dispose(); }); }; export default cloudflareDevPlugin; async function _getPlatformProxy() { const pkg = "wrangler"; // bypass bundler const { getPlatformProxy } = (await import(/* @vite-ignore */ pkg).catch(() => { throw new Error( "Package `wrangler` not found, please install it with: `npx nypm@latest add -D wrangler`" ); })) as typeof import("wrangler"); const runtimeConfig: { wrangler: { configPath: string; persistDir: string; environment?: string; }; } = useRuntimeConfig() as any; const proxyOptions: GetPlatformProxyOptions = { configPath: runtimeConfig.wrangler.configPath, persist: { path: runtimeConfig.wrangler.persistDir }, }; // TODO: investigate why // https://github.com/pi0/nitro-cloudflare-dev/issues/51 if (runtimeConfig.wrangler.environment) { proxyOptions.environment = runtimeConfig.wrangler.environment; } const proxy = await getPlatformProxy(proxyOptions); return proxy; } function _createStubProxy(): PlatformProxy { return { env: {}, cf: {} as any, ctx: { waitUntil() {}, passThroughOnException() {}, props: {}, }, caches: { open(): Promise<_CacheStub> { const result = Promise.resolve(new _CacheStub()); return result; }, get default(): _CacheStub { return new _CacheStub(); }, }, dispose: () => Promise.resolve(), }; } class _CacheStub { delete(): Promise { const result = Promise.resolve(false); return result; } match() { const result = Promise.resolve(undefined); return result; } put(): Promise { const result = Promise.resolve(); return result; } } ================================================ FILE: src/presets/cloudflare/runtime/shims/workers.dev.mjs ================================================ // Shim for "cloudflare:workers" import in dev environment // unenv shim respects __env__ export { env } from "unenv/node/internal/process/env"; export async function waitUntil(promise) { await globalThis.__wait_until__?.(promise); } export function withEnv(newEnv, fn) { throw new Error("cf.withEnv is not implemented in dev env currently."); } class NotImplemented { constructor() { throw new Error("Not implemented in dev env currently."); } } export class DurableObject extends NotImplemented {} export class RpcPromise extends NotImplemented {} export class RpcProperty extends NotImplemented {} export class RpcStub extends NotImplemented {} export class RpcTarget extends NotImplemented {} export class ServiceStub extends NotImplemented {} export class WorkerEntrypoint extends NotImplemented {} export class WorkflowEntrypoint extends NotImplemented {} ================================================ FILE: src/presets/cloudflare/types.ts ================================================ import type { ExecutionContext, ForwardableEmailMessage, MessageBatch, ScheduledController, TraceItem, } from "@cloudflare/workers-types"; import type { DurableObject } from "cloudflare:workers"; import type { Config as _Config, ComputedFields as _ComputedFields } from "./wrangler/config.ts"; export type WranglerConfig = Partial>; /** * https://developers.cloudflare.com/pages/platform/functions/routing/#functions-invocation-routes */ export interface CloudflarePagesRoutes { /** Defines the version of the schema. Currently there is only one version of the schema (version 1), however, we may add more in the future and aim to be backwards compatible. */ version?: 1; /** Defines routes that will be invoked by Functions. Accepts wildcard behavior. */ include?: string[]; /** Defines routes that will not be invoked by Functions. Accepts wildcard behavior. `exclude` always take priority over `include`. */ exclude?: string[]; } export interface CloudflareOptions { /** * Configuration for the Cloudflare Deployments. * * **NOTE:** This option is only effective if `deployConfig` is enabled. */ wrangler?: WranglerConfig; /** * Enable automatic generation of `.wrangler/deploy/config.json`. * * **IMPORTANT:** Enabling this option will cause settings from cloudflare dashboard (including environment variables) to be disabled and discarded. * * More info: https://developers.cloudflare.com/workers/wrangler/configuration#generated-wrangler-configuration */ deployConfig?: boolean; /** * Enable native Node.js compatibility support. * * If this option disabled, pure unenv polyfills will be used instead. * * If not set, will be auto enabled if `nodejs_compat` or `nodejs_compat_v2` is detected in `wrangler.toml` or `wrangler.json`. */ nodeCompat?: boolean; /** * Options for dev emulation. */ dev?: { configPath?: string; environment?: string; persistDir?: string; }; pages?: { /** * Nitro will automatically generate a `_routes.json` that controls which files get served statically and * which get served by the Worker. Using this config will override the automatic `_routes.json`. Or, if the * `merge` options is set, it will merge the user-set routes with the auto-generated ones, giving priority * to the user routes. * * @see https://developers.cloudflare.com/pages/platform/functions/routing/#functions-invocation-routes * * There are a maximum of 100 rules, and you must have at least one include rule. Wildcards are accepted. * * If any fields are unset, they default to: * * ```json * { * "version": 1, * "include": ["/*"], * "exclude": [] * } * ``` */ routes?: CloudflarePagesRoutes; /** * If set to `false`, nitro will disable the automatically generated `_routes.json` and instead use the user-set only ones. * * @default true */ defaultRoutes?: boolean; }; /** * Custom Cloudflare exports additional classes such as WorkflowEntrypoint. */ exports?: string; } type DurableObjectState = ConstructorParameters[0]; declare module "nitro/types" { export interface NitroRuntimeHooks { // https://developers.cloudflare.com/workers/runtime-apis/handlers/scheduled/ "cloudflare:scheduled": (_: { controller: ScheduledController; env: unknown; context: ExecutionContext; }) => void; // https://developers.cloudflare.com/email-routing/email-workers/runtime-api "cloudflare:email": (_: { message: ForwardableEmailMessage; /** @deprecated please use `message` */ event: ForwardableEmailMessage; env: unknown; context: ExecutionContext; }) => void; // https://developers.cloudflare.com/queues/configuration/javascript-apis/#consumer "cloudflare:queue": (_: { batch: MessageBatch; /** @deprecated please use `batch` */ event: MessageBatch; env: unknown; context: ExecutionContext; }) => void; // https://developers.cloudflare.com/workers/runtime-apis/handlers/tail/ "cloudflare:tail": (_: { traces: TraceItem[]; env: unknown; context: ExecutionContext; }) => void; "cloudflare:trace": (_: { traces: TraceItem[]; env: unknown; context: ExecutionContext; }) => void; "cloudflare:durable:init": ( durable: DurableObject, _: { state: DurableObjectState; env: unknown; } ) => void; "cloudflare:durable:alarm": (durable: DurableObject) => void; } } ================================================ FILE: src/presets/cloudflare/unenv/node-compat.ts ================================================ // Auto generated at 2025-10-05 // Source: https://platform-node-compat.pi0.workers.dev/ // Do not edit this file manually // prettier-ignore export const builtnNodeModules = [ "node:_http_agent", "node:_http_client", "node:_http_common", // Missing exports: CRLF, HTTPParser, freeParser, isLenient, parsers, prepareError "node:_http_incoming", // Missing exports: readStart, readStop "node:_http_outgoing", "node:_http_server", "node:_stream_duplex", "node:_stream_passthrough", "node:_stream_readable", "node:_stream_transform", "node:_stream_writable", "node:_tls_common", "node:_tls_wrap", // Missing exports: Server, createServer "node:assert", // Missing exports: Assert, CallTracker "node:assert/strict", // Missing exports: Assert, CallTracker "node:async_hooks", "node:buffer", "node:constants", // Missing exports: O_DIRECT, O_NOATIME, RTLD_DEEPBIND, SIGPOLL, SIGPWR, SIGSTKFLT, defaultCipherList "node:crypto", // Missing exports: argon2, argon2Sync, decapsulate, encapsulate "node:diagnostics_channel", "node:dns", // Missing exports: resolveTlsa "node:dns/promises", // Missing exports: resolveTlsa "node:events", // Missing exports: captureRejections, init "node:fs", // Missing exports: Utf8Stream, mkdtempDisposableSync "node:fs/promises", // Missing exports: mkdtempDisposable "node:http", "node:http2", "node:https", "node:module", "node:net", "node:os", "node:path", "node:path/posix", "node:path/win32", "node:process", "node:querystring", "node:stream", "node:stream/consumers", "node:stream/promises", "node:stream/web", "node:string_decoder", "node:test", // Missing exports: after, afterEach, assert, before, beforeEach, describe, it, only, run, skip, snapshot, suite, test, todo "node:timers", "node:timers/promises", "node:tls", // Missing exports: getCACertificates, setDefaultCACertificates "node:url", // Missing exports: URLPattern, fileURLToPathBuffer "node:util", // Missing exports: diff, setTraceSigInt "node:util/types", "node:zlib", // Missing exports: ZstdCompress, ZstdDecompress, createZstdCompress, createZstdDecompress, zstdCompress, zstdCompressSync, zstdDecompress, zstdDecompressSync ]; // prettier-ignore export const unsupportedNodeModules = [ "node:_stream_wrap", "node:child_process", "node:cluster", "node:console", "node:dgram", "node:domain", "node:inspector", "node:inspector/promises", "node:perf_hooks", "node:punycode", "node:readline", "node:readline/promises", "node:repl", "node:sys", "node:trace_events", "node:tty", "node:v8", "node:vm", "node:wasi", "node:worker_threads", "node:sea", "node:sqlite", "node:test/reporters", ]; ================================================ FILE: src/presets/cloudflare/unenv/preset.ts ================================================ import type { Preset } from "unenv"; import * as workerdNodeCompat from "./node-compat.ts"; // https://platform-node-compat.pi0.workers.dev/ export const unenvCfNodeCompat: Preset = { meta: { name: "nitro:cloudflare-node-compat", }, external: workerdNodeCompat.builtnNodeModules, alias: { ...Object.fromEntries( workerdNodeCompat.builtnNodeModules.flatMap((m) => [ [m, m], [m.replace("node:", ""), m], ]) ), }, inject: { global: "unenv/polyfill/globalthis", process: "node:process", clearImmediate: ["node:timers", "clearImmediate"], setImmediate: ["node:timers", "setImmediate"], Buffer: ["node:buffer", "Buffer"], }, }; export const unenvCfExternals: Preset = { meta: { name: "nitro:cloudflare-externals", }, external: [ "cloudflare:email", "cloudflare:sockets", "cloudflare:workers", "cloudflare:workflows", ], }; ================================================ FILE: src/presets/cloudflare/utils.ts ================================================ import type { Nitro } from "nitro/types"; import type { WranglerConfig, CloudflarePagesRoutes } from "./types.ts"; import { existsSync } from "node:fs"; import { readFile } from "node:fs/promises"; import { relative, dirname, extname } from "node:path"; import { writeFile } from "../_utils/fs.ts"; import { parseTOML, parseJSONC } from "confbox"; import { readGitConfig, readPackageJSON, findNearestFile } from "pkg-types"; import { defu } from "defu"; import { glob } from "tinyglobby"; import { join, resolve } from "pathe"; import { joinURL, hasProtocol, withLeadingSlash, withTrailingSlash, withoutLeadingSlash, } from "ufo"; import { unenvCfNodeCompat } from "./unenv/preset.ts"; export async function writeCFRoutes(nitro: Nitro) { const _cfPagesConfig = nitro.options.cloudflare?.pages || {}; const routes: CloudflarePagesRoutes = { version: _cfPagesConfig.routes?.version || 1, include: _cfPagesConfig.routes?.include || ["/*"], exclude: _cfPagesConfig.routes?.exclude || [], }; const writeRoutes = () => writeFile( resolve(nitro.options.output.dir, "_routes.json"), JSON.stringify(routes, undefined, 2), true ); if (_cfPagesConfig.defaultRoutes === false) { await writeRoutes(); return; } // Exclude public assets from hitting the worker const explicitPublicAssets = nitro.options.publicAssets.filter((dir, index, array) => { if (dir.fallthrough || !dir.baseURL) { return false; } const normalizedBase = withoutLeadingSlash(dir.baseURL); return !array.some( (otherDir, otherIndex) => otherIndex !== index && normalizedBase.startsWith(withoutLeadingSlash(withTrailingSlash(otherDir.baseURL))) ); }); // Explicit prefixes routes.exclude!.push( ...explicitPublicAssets .map((asset) => joinURL(nitro.options.baseURL, asset.baseURL || "/", "*")) .sort(comparePaths) ); // Unprefixed assets const publicAssetFiles = await glob("**", { cwd: nitro.options.output.dir, absolute: false, dot: true, ignore: [ "_worker.js", "_worker.js.map", "nitro.json", ...routes.exclude!.map((path) => withoutLeadingSlash(path.replace(/\/\*$/, "/**"))), ], }); // Remove index.html or the .html extension to support pages pre-rendering routes.exclude!.push( ...publicAssetFiles .map( (i) => withLeadingSlash(i) .replace(/\/index\.html$/, "") .replace(/\.html$/, "") || "/" ) .sort(comparePaths) ); // Only allow 100 rules in total (include + exclude) routes.exclude!.splice(100 - routes.include!.length); await writeRoutes(); } function comparePaths(a: string, b: string) { return a.split("/").length - b.split("/").length || a.localeCompare(b); } export async function writeCFHeaders(nitro: Nitro, outdir: "public" | "output") { const headersPath = join( outdir === "public" ? nitro.options.output.publicDir : nitro.options.output.dir, "_headers" ); const contents = []; const rules = Object.entries(nitro.options.routeRules).sort( (a, b) => b[0].split(/\/(?!\*)/).length - a[0].split(/\/(?!\*)/).length ); for (const [path, routeRules] of rules.filter(([_, routeRules]) => routeRules.headers)) { const headers = [ joinURL(nitro.options.baseURL, path.replace("/**", "/*")), ...Object.entries({ ...routeRules.headers }).map( ([header, value]) => ` ${header}: ${value}` ), ].join("\n"); contents.push(headers); } if (existsSync(headersPath)) { const currentHeaders = await readFile(headersPath, "utf8"); if (/^\/\* /m.test(currentHeaders)) { nitro.logger.info( "Not adding Nitro fallback to `_headers` (as an existing fallback was found)." ); return; } nitro.logger.info("Adding Nitro fallback to `_headers` to handle all unmatched routes."); contents.unshift(currentHeaders); } await writeFile(headersPath, contents.join("\n"), true); } export async function writeCFPagesRedirects(nitro: Nitro) { const redirectsPath = join(nitro.options.output.dir, "_redirects"); const staticFallback = existsSync(join(nitro.options.output.publicDir, "404.html")) ? `${joinURL(nitro.options.baseURL, "/*")} ${joinURL(nitro.options.baseURL, "/404.html")} 404` : ""; const contents = [staticFallback]; const rules = Object.entries(nitro.options.routeRules).sort( (a, b) => a[0].split(/\/(?!\*)/).length - b[0].split(/\/(?!\*)/).length ); for (const [key, routeRules] of rules.filter(([_, routeRules]) => routeRules.redirect)) { const code = routeRules.redirect!.status; const from = joinURL(nitro.options.baseURL, key.replace("/**", "/*")); const to = hasProtocol(routeRules.redirect!.to, { acceptRelative: true }) ? routeRules.redirect!.to : joinURL(nitro.options.baseURL, routeRules.redirect!.to); contents.unshift(`${from}\t${to}\t${code}`); } if (existsSync(redirectsPath)) { const currentRedirects = await readFile(redirectsPath, "utf8"); if (/^\/\* /m.test(currentRedirects)) { nitro.logger.info( "Not adding Nitro fallback to `_redirects` (as an existing fallback was found)." ); return; } nitro.logger.info("Adding Nitro fallback to `_redirects` to handle all unmatched routes."); contents.unshift(currentRedirects); } await writeFile(redirectsPath, contents.join("\n"), true); } export async function enableNodeCompat(nitro: Nitro) { nitro.options.cloudflare ??= {}; nitro.options.cloudflare.deployConfig ??= true; nitro.options.cloudflare.nodeCompat ??= true; if (nitro.options.cloudflare.nodeCompat) { nitro.options.rolldownConfig ??= {}; nitro.options.rolldownConfig.platform ??= "node"; nitro.options.unenv.push(unenvCfNodeCompat); } } const extensionParsers = { ".json": parseJSONC, ".jsonc": parseJSONC, ".toml": parseTOML, } as const; async function readWranglerConfig( nitro: Nitro ): Promise<{ configPath?: string; config?: WranglerConfig }> { const configPath = await findNearestFile(["wrangler.json", "wrangler.jsonc", "wrangler.toml"], { startingFrom: nitro.options.rootDir, }).catch(() => undefined); if (!configPath) { return {}; } const userConfigText = await readFile(configPath, "utf8"); const parser = extensionParsers[extname(configPath) as keyof typeof extensionParsers]; if (!parser) { /* unreachable */ throw new Error(`Unsupported config file format: ${configPath}`); } const config = parser(userConfigText) as WranglerConfig; return { configPath, config }; } // https://developers.cloudflare.com/workers/wrangler/configuration/#generated-wrangler-configuration export async function writeWranglerConfig(nitro: Nitro, cfTarget: "pages" | "module") { // Skip if not enabled if (!nitro.options.cloudflare?.deployConfig) { return; } // Compute path to generated wrangler.json const wranglerConfigDir = nitro.options.output.serverDir; const wranglerConfigPath = join(wranglerConfigDir, "wrangler.json"); // Default configs const defaults: WranglerConfig = {}; // Config overrides const overrides: WranglerConfig = {}; // Compatibility date defaults.compatibility_date = nitro.options.compatibilityDate.cloudflare || nitro.options.compatibilityDate.default; if (cfTarget === "pages") { // Pages overrides.pages_build_output_dir = relative(wranglerConfigDir, nitro.options.output.dir); } else { // Modules overrides.main = relative(wranglerConfigDir, join(nitro.options.output.serverDir, "index.mjs")); overrides.assets = { binding: "ASSETS", directory: relative( wranglerConfigDir, resolve( nitro.options.output.publicDir, "..".repeat(nitro.options.baseURL.split("/").filter(Boolean).length) ) ), }; } // Read user config const { config: userConfig = {} } = await readWranglerConfig(nitro); // Nitro context config (from frameworks and modules) const ctxConfig = nitro.options.cloudflare?.wrangler || {}; // Validate and warn about overrides for (const key in overrides) { if (key in userConfig || key in ctxConfig) { nitro.logger.warn( `[cloudflare] Wrangler config \`${key}\`${key in ctxConfig ? "set by config or modules" : ""} is overridden and will be ignored.` ); } } // (first argument takes precedence) const wranglerConfig = defu(overrides, ctxConfig, userConfig, defaults) as WranglerConfig; // Name is required if (!wranglerConfig.name) { wranglerConfig.name = await generateWorkerName(nitro)!; nitro.logger.info(`Using auto generated worker name: \`${wranglerConfig.name}\``); } // Compatibility flags wranglerConfig.compatibility_flags ??= []; if ( nitro.options.cloudflare?.nodeCompat && !wranglerConfig.compatibility_flags.includes("nodejs_compat") ) { wranglerConfig.compatibility_flags.push("nodejs_compat"); } if (cfTarget === "module") { // Avoid double bundling if (wranglerConfig.no_bundle === undefined) { wranglerConfig.no_bundle = true; } // Scan all server/ chunks wranglerConfig.rules ??= []; if (!wranglerConfig.rules.some((rule) => rule.type === "ESModule")) { wranglerConfig.rules.push({ type: "ESModule", globs: ["**/*.mjs", "**/*.js"], }); } } // Nitro Tasks cron triggers if ( nitro.options.experimental.tasks && Object.keys(nitro.options.scheduledTasks || {}).length > 0 && cfTarget !== "pages" ) { const schedules = Object.keys(nitro.options.scheduledTasks!); wranglerConfig.triggers = defu(wranglerConfig.triggers, { crons: [] }); const existingCrons = new Set(wranglerConfig.triggers!.crons); for (const schedule of schedules) { if (!existingCrons.has(schedule)) { wranglerConfig.triggers!.crons!.push(schedule); } } } // Write wrangler.json await writeFile(wranglerConfigPath, JSON.stringify(wranglerConfig, null, 2), true); const configPath = join(nitro.options.rootDir, ".wrangler/deploy/config.json"); await writeFile( configPath, JSON.stringify({ configPath: relative(dirname(configPath), wranglerConfigPath), }), true ); } async function generateWorkerName(nitro: Nitro) { const gitConfig = await readGitConfig(nitro.options.rootDir).catch(() => undefined); const gitRepo = gitConfig?.remote?.origin?.url ?.replace(/\.git$/, "") .match(/[/:]([^/]+\/[^/]+)$/)?.[1]; const pkgJSON = await readPackageJSON(nitro.options.rootDir).catch(() => undefined); const pkgName = pkgJSON?.name; const subpath = relative(nitro.options.workspaceDir, nitro.options.rootDir); return `${gitRepo || pkgName}/${subpath}` .toLowerCase() .replace(/[^a-zA-Z0-9-]/g, "-") .replace(/-$/, ""); } ================================================ FILE: src/presets/cloudflare/wrangler/_utils.ts ================================================ // Extracted from @types/yargs type PascalCase = string extends S ? string : S extends `${infer T}-${infer U}` ? `${Capitalize}${PascalCase}` : Capitalize; type CamelCase = string extends S ? string : S extends `${infer T}-${infer U}` ? `${T}${PascalCase}` : S; export type CamelCaseKey = K extends string ? Exclude, ""> : K; ================================================ FILE: src/presets/cloudflare/wrangler/config.ts ================================================ /** * Copyright (c) 2020 Cloudflare, Inc. * https://github.com/cloudflare/workers-sdk/blob/main/LICENSE-MIT * https://github.com/cloudflare/workers-sdk/blob/main/LICENSE-APACHE * * Source: https://github.com/cloudflare/workers-sdk/blob/main/packages/wrangler/src/config/config.ts */ import type { Environment, RawEnvironment } from "./environment.ts"; import type { CamelCaseKey } from "./_utils.ts"; /** * This is the static type definition for the configuration object. * * It reflects a normalized and validated version of the configuration that you can write in a Wrangler configuration file, * and optionally augment with arguments passed directly to wrangler. * * For more information about the configuration object, see the * documentation at https://developers.cloudflare.com/workers/cli-wrangler/configuration * * Notes: * * - Fields that are only specified in `ConfigFields` and not `Environment` can only appear * in the top level config and should not appear in any environments. * - Fields that are specified in `PagesConfigFields` are only relevant for Pages projects * - All top level fields in config and environments are optional in the Wrangler configuration file. * * Legend for the annotations: * * - `@breaking`: the deprecation/optionality is a breaking change from Wrangler v1. * - `@todo`: there's more work to be done (with details attached). */ export type Config = ComputedFields & ConfigFields & PagesConfigFields & Environment; export type RawConfig = Partial> & PagesConfigFields & RawEnvironment & DeprecatedConfigFields & EnvironmentMap & { $schema?: string }; export interface ComputedFields { /** The path to the Wrangler configuration file (if any, and possibly redirected from the user Wrangler configuration) used to create this configuration. */ configPath: string | undefined; /** The path to the user's Wrangler configuration file (if any), which may have been redirected to another file that used to create this configuration. */ userConfigPath: string | undefined; /** * The original top level name for the Worker in the raw configuration. * * When a raw configuration has been flattened to a single environment the worker name may have been replaced or transformed. * It can be useful to know what the top-level name was before the flattening. */ topLevelName: string | undefined; } export interface ConfigFields { /** * A boolean to enable "legacy" style wrangler environments (from Wrangler v1). * These have been superseded by Services, but there may be projects that won't * (or can't) use them. If you're using a legacy environment, you can set this * to `true` to enable it. */ legacy_env: boolean; /** * Whether Wrangler should send usage metrics to Cloudflare for this project. * * When defined this will override any user settings. * Otherwise, Wrangler will use the user's preference. */ send_metrics: boolean | undefined; /** * Options to configure the development server that your worker will use. */ dev: Dev; /** * The definition of a Worker Site, a feature that lets you upload * static assets with your Worker. * * More details at https://developers.cloudflare.com/workers/platform/sites */ site: | { /** * The directory containing your static assets. * * It must be a path relative to your Wrangler configuration file. * Example: bucket = "./public" * * If there is a `site` field then it must contain this `bucket` field. */ bucket: string; /** * The location of your Worker script. * * @deprecated DO NOT use this (it's a holdover from Wrangler v1.x). Either use the top level `main` field, or pass the path to your entry file as a command line argument. * @breaking */ "entry-point"?: string; /** * An exclusive list of .gitignore-style patterns that match file * or directory names from your bucket location. Only matched * items will be uploaded. Example: include = ["upload_dir"] * * @optional * @default [] */ include?: string[]; /** * A list of .gitignore-style patterns that match files or * directories in your bucket that should be excluded from * uploads. Example: exclude = ["ignore_dir"] * * @optional * @default [] */ exclude?: string[]; } | undefined; /** * Old behaviour of serving a folder of static assets with your Worker, * without any additional code. * This can either be a string, or an object with additional config * fields. * Will be deprecated in the near future in favor of `assets`. */ legacy_assets: | { bucket: string; include: string[]; exclude: string[]; browser_TTL: number | undefined; serve_single_page_app: boolean; } | string | undefined; /** * A list of wasm modules that your worker should be bound to. This is * the "legacy" way of binding to a wasm module. ES module workers should * do proper module imports. */ wasm_modules: | { [key: string]: string; } | undefined; /** * A list of text files that your worker should be bound to. This is * the "legacy" way of binding to a text file. ES module workers should * do proper module imports. */ text_blobs: | { [key: string]: string; } | undefined; /** * A list of data files that your worker should be bound to. This is * the "legacy" way of binding to a data file. ES module workers should * do proper module imports. */ data_blobs: | { [key: string]: string; } | undefined; /** * A map of module aliases. Lets you swap out a module for any others. * Corresponds with esbuild's `alias` config */ alias: { [key: string]: string } | undefined; /** * By default, the Wrangler configuration file is the source of truth for your environment configuration, like a terraform file. * * If you change your vars in the dashboard, wrangler *will* override/delete them on its next deploy. * * If you want to keep your dashboard vars when wrangler deploys, set this field to true. * * @default false * @nonInheritable */ keep_vars?: boolean; } // Pages-specific configuration fields interface PagesConfigFields { /** * The directory of static assets to serve. * * The presence of this field in a Wrangler configuration file indicates a Pages project, * and will prompt the handling of the configuration file according to the * Pages-specific validation rules. */ pages_build_output_dir?: string; } export interface DevConfig { /** * IP address for the local dev server to listen on, * * @default localhost */ ip: string; /** * Port for the local dev server to listen on * * @default 8787 */ port: number | undefined; /** * Port for the local dev server's inspector to listen on * * @default 9229 */ inspector_port: number | undefined; /** * Protocol that local wrangler dev server listens to requests on. * * @default http */ local_protocol: "http" | "https"; /** * Protocol that wrangler dev forwards requests on * * Setting this to `http` is not currently implemented for remote mode. * See https://github.com/cloudflare/workers-sdk/issues/583 * * @default https */ upstream_protocol: "https" | "http"; /** * Host to forward requests to, defaults to the host of the first route of project */ host: string | undefined; } export type RawDevConfig = Partial; interface DeprecatedConfigFields { /** * The project "type". A holdover from Wrangler v1.x. * Valid values were "webpack", "javascript", and "rust". * * @deprecated DO NOT USE THIS. Most common features now work out of the box with wrangler, including modules, jsx, typescript, etc. If you need anything more, use a custom build. * @breaking */ type?: "webpack" | "javascript" | "rust"; /** * Path to the webpack config to use when building your worker. * A holdover from Wrangler v1.x, used with `type: "webpack"`. * * @deprecated DO NOT USE THIS. Most common features now work out of the box with wrangler, including modules, jsx, typescript, etc. If you need anything more, use a custom build. * @breaking */ webpack_config?: string; /** * Configuration only used by a standalone use of the miniflare binary. * @deprecated */ miniflare?: unknown; } interface EnvironmentMap { /** * The `env` section defines overrides for the configuration for different environments. * * All environment fields can be specified at the top level of the config indicating the default environment settings. * * - Some fields are inherited and overridable in each environment. * - But some are not inherited and must be explicitly specified in every environment, if they are specified at the top level. * * For more information, see the documentation at https://developers.cloudflare.com/workers/cli-wrangler/configuration#environments * * @default {} */ env?: { [envName: string]: RawEnvironment; }; } // API dev only passes in camel-cased versions of keys, so ensure // only camel-cased keys are used export type OnlyCamelCase> = { [key in keyof T as CamelCaseKey]: T[key]; }; ================================================ FILE: src/presets/cloudflare/wrangler/environment.ts ================================================ /** * Copyright (c) 2020 Cloudflare, Inc. * https://github.com/cloudflare/workers-sdk/blob/main/LICENSE-MIT * https://github.com/cloudflare/workers-sdk/blob/main/LICENSE-APACHE * * Source: https://github.com/cloudflare/workers-sdk/blob/main/packages/wrangler/src/config/environment.ts */ /** * The `Environment` interface declares all the configuration fields that * can be specified for an environment. * * This could be the top-level default environment, or a specific named environment. */ export interface Environment extends EnvironmentInheritable, EnvironmentNonInheritable {} type SimpleRoute = string; export type ZoneIdRoute = { pattern: string; zone_id: string; custom_domain?: boolean; }; export type ZoneNameRoute = { pattern: string; zone_name: string; custom_domain?: boolean; }; export type CustomDomainRoute = { pattern: string; custom_domain: boolean }; export type Route = SimpleRoute | ZoneIdRoute | ZoneNameRoute | CustomDomainRoute; /** * Configuration in wrangler for Cloudchamber */ export type CloudchamberConfig = { image?: string; location?: string; instance_type?: "dev" | "basic" | "standard"; vcpu?: number; memory?: string; ipv4?: boolean; }; /** * Configuration for a container application */ export type ContainerApp = { // TODO: fill out the entire type /** * Name of the application * @optional Defaults to `worker_name-class_name` if not specified. */ name?: string; /** * Number of application instances * @deprecated * @hidden */ instances?: number; /** * Number of maximum application instances. * @optional */ max_instances?: number; /** * The path to a Dockerfile, or an image URI for the Cloudflare registry. */ image: string; /** * Build context of the application. * @optional - defaults to the directory of `image`. */ image_build_context?: string; /** * Image variables to be passed along the image at build time. * @optional */ image_vars?: Record; /** * The class name of the Durable Object the container is connected to. */ class_name: string; /** * The scheduling policy of the application * @optional * @default "default" */ scheduling_policy?: "regional" | "moon" | "default"; /** * The instance type to be used for the container. This sets preconfigured options for vcpu and memory * @optional */ instance_type?: "dev" | "basic" | "standard"; /** * @deprecated Use top level `containers` fields instead. * `configuration.image` should be `image` * `configuration.disk` should be set via `instance_type` * @hidden */ configuration?: { image?: string; labels?: { name: string; value: string }[]; secrets?: { name: string; type: "env"; secret: string }[]; disk?: { size: string }; }; /** * Scheduling constraints * @hidden */ constraints?: { regions?: string[]; cities?: string[]; tier?: number; }; /** * @deprecated use the `class_name` field instead. * @hidden */ durable_objects?: { namespace_id: string; }; /** * How a rollout should be done, defining the size of it * @optional * @default 25 * */ rollout_step_percentage?: number; /** * How a rollout should be created. It supports the following modes: * - full_auto: The container application will be rolled out fully automatically. * - none: The container application won't have a roll out or update. * - manual: The container application will be rollout fully by manually actioning progress steps. * @optional * @default "full_auto" */ rollout_kind?: "full_auto" | "none" | "full_manual"; }; /** * Configuration in wrangler for Durable Object Migrations */ export type DurableObjectMigration = { /** A unique identifier for this migration. */ tag: string; /** The new Durable Objects being defined. */ new_classes?: string[]; /** The new SQLite Durable Objects being defined. */ new_sqlite_classes?: string[]; /** The Durable Objects being renamed. */ renamed_classes?: { from: string; to: string; }[]; /** The Durable Objects being removed. */ deleted_classes?: string[]; }; /** * The `EnvironmentInheritable` interface declares all the configuration fields for an environment * that can be inherited (and overridden) from the top-level environment. */ interface EnvironmentInheritable { /** * The name of your Worker. Alphanumeric + dashes only. * * @inheritable */ name: string | undefined; /** * This is the ID of the account associated with your zone. * You might have more than one account, so make sure to use * the ID of the account associated with the zone/route you * provide, if you provide one. It can also be specified through * the CLOUDFLARE_ACCOUNT_ID environment variable. * * @inheritable */ account_id: string | undefined; /** * A date in the form yyyy-mm-dd, which will be used to determine * which version of the Workers runtime is used. * * More details at https://developers.cloudflare.com/workers/configuration/compatibility-dates * * @inheritable */ compatibility_date: string | undefined; /** * A list of flags that enable features from upcoming features of * the Workers runtime, usually used together with compatibility_date. * * More details at https://developers.cloudflare.com/workers/configuration/compatibility-flags/ * * @default [] * @inheritable */ compatibility_flags: string[]; /** * The entrypoint/path to the JavaScript file that will be executed. * * @inheritable */ main: string | undefined; /** * If true then Wrangler will traverse the file tree below `base_dir`; * Any files that match `rules` will be included in the deployed Worker. * Defaults to true if `no_bundle` is true, otherwise false. * * @inheritable */ find_additional_modules: boolean | undefined; /** * Determines whether Wrangler will preserve bundled file names. * Defaults to false. * If left unset, files will be named using the pattern ${fileHash}-${basename}, * for example, `34de60b44167af5c5a709e62a4e20c4f18c9e3b6-favicon.ico`. * * @inheritable */ preserve_file_names: boolean | undefined; /** * The directory in which module rules should be evaluated when including additional files into a Worker deployment. * This defaults to the directory containing the `main` entry point of the Worker if not specified. * * @inheritable */ base_dir: string | undefined; // Carmen according to our tests the default is undefined // warning: you must force "workers_dev: true" in tests to match expected behavior /** * Whether we use ..workers.dev to * test and deploy your Worker. * * For reference, see https://developers.cloudflare.com/workers/wrangler/configuration/#workersdev * * @default true * @breaking * @inheritable */ workers_dev: boolean | undefined; /** * Whether we use -..workers.dev to * serve Preview URLs for your Worker. * * @default true * @inheritable */ preview_urls: boolean | undefined; /** * A list of routes that your Worker should be published to. * Only one of `routes` or `route` is required. * * Only required when workers_dev is false, and there's no scheduled Worker (see `triggers`) * * For reference, see https://developers.cloudflare.com/workers/wrangler/configuration/#types-of-routes * * @inheritable */ routes: Route[] | undefined; /** * A route that your Worker should be published to. Literally * the same as routes, but only one. * Only one of `routes` or `route` is required. * * Only required when workers_dev is false, and there's no scheduled Worker * * @inheritable */ route: Route | undefined; /** * Path to a custom tsconfig * * @inheritable */ tsconfig: string | undefined; /** * The function to use to replace jsx syntax. * * @default "React.createElement" * @inheritable */ jsx_factory: string; /** * The function to use to replace jsx fragment syntax. * * @default "React.Fragment" * @inheritable */ jsx_fragment: string; /** * A list of migrations that should be uploaded with your Worker. * * These define changes in your Durable Object declarations. * * More details at https://developers.cloudflare.com/workers/learning/using-durable-objects#configuring-durable-object-classes-with-migrations * * @default [] * @inheritable */ migrations: DurableObjectMigration[]; /** * "Cron" definitions to trigger a Worker's "scheduled" function. * * Lets you call Workers periodically, much like a cron job. * * More details here https://developers.cloudflare.com/workers/platform/cron-triggers * * For reference, see https://developers.cloudflare.com/workers/wrangler/configuration/#triggers * * @default {crons: undefined} * @inheritable */ triggers: { crons: string[] | undefined }; /** * Specify limits for runtime behavior. * Only supported for the "standard" Usage Model * * For reference, see https://developers.cloudflare.com/workers/wrangler/configuration/#limits * * @inheritable */ limits: UserLimits | undefined; /** * An ordered list of rules that define which modules to import, * and what type to import them as. You will need to specify rules * to use Text, Data, and CompiledWasm modules, or when you wish to * have a .js file be treated as an ESModule instead of CommonJS. * * For reference, see https://developers.cloudflare.com/workers/wrangler/configuration/#bundling * * @inheritable */ rules: Rule[]; /** * Configures a custom build step to be run by Wrangler when building your Worker. * * Refer to the [custom builds documentation](https://developers.cloudflare.com/workers/cli-wrangler/configuration#build) * for more details. * * For reference, see https://developers.cloudflare.com/workers/wrangler/configuration/#custom-builds * * @default {watch_dir:"./src"} */ build: { /** The command used to build your Worker. On Linux and macOS, the command is executed in the `sh` shell and the `cmd` shell for Windows. The `&&` and `||` shell operators may be used. */ command?: string; /** The directory in which the command is executed. */ cwd?: string; /** The directory to watch for changes while using wrangler dev, defaults to the current working directory */ watch_dir?: string | string[]; }; /** * Skip internal build steps and directly deploy script * @inheritable */ no_bundle: boolean | undefined; /** * Minify the script before uploading. * @inheritable */ minify: boolean | undefined; /** * Set the `name` property to the original name for functions and classes renamed during minification. * * See https://esbuild.github.io/api/#keep-names * * @default true * @inheritable */ keep_names: boolean | undefined; /** * Designates this Worker as an internal-only "first-party" Worker. * * @inheritable */ first_party_worker: boolean | undefined; /** * List of bindings that you will send to logfwdr * * @default {bindings:[]} * @inheritable */ logfwdr: { bindings: { /** The binding name used to refer to logfwdr */ name: string; /** The destination for this logged message */ destination: string; }[]; }; /** * Send Trace Events from this Worker to Workers Logpush. * * This will not configure a corresponding Logpush job automatically. * * For more information about Workers Logpush, see: * https://blog.cloudflare.com/logpush-for-workers/ * * @inheritable */ logpush: boolean | undefined; /** * Include source maps when uploading this worker. * * For reference, see https://developers.cloudflare.com/workers/wrangler/configuration/#source-maps * * @inheritable */ upload_source_maps: boolean | undefined; /** * Specify how the Worker should be located to minimize round-trip time. * * More details: https://developers.cloudflare.com/workers/platform/smart-placement/ * * @inheritable */ placement: { mode: "off" | "smart"; hint?: string } | undefined; /** * Specify the directory of static assets to deploy/serve * * More details at https://developers.cloudflare.com/workers/frameworks/ * * For reference, see https://developers.cloudflare.com/workers/wrangler/configuration/#assets * * @inheritable */ assets: Assets | undefined; /** * Specify the observability behavior of the Worker. * * For reference, see https://developers.cloudflare.com/workers/wrangler/configuration/#observability * * @inheritable */ observability: Observability | undefined; /** * Specify the compliance region mode of the Worker. * * Although if the user does not specify a compliance region, the default is `public`, * it can be set to `undefined` in configuration to delegate to the CLOUDFLARE_COMPLIANCE_REGION environment variable. */ compliance_region: "public" | "fedramp_high" | undefined; } export type DurableObjectBindings = { /** The name of the binding used to refer to the Durable Object */ name: string; /** The exported class name of the Durable Object */ class_name: string; /** The script where the Durable Object is defined (if it's external to this Worker) */ script_name?: string; /** The service environment of the script_name to bind to */ environment?: string; }[]; export type WorkflowBinding = { /** The name of the binding used to refer to the Workflow */ binding: string; /** The name of the Workflow */ name: string; /** The exported class name of the Workflow */ class_name: string; /** The script where the Workflow is defined (if it's external to this Worker) */ script_name?: string; /** Whether the Workflow should be remote or not (only available under `--x-remote-bindings`) */ experimental_remote?: boolean; }; /** * The `EnvironmentNonInheritable` interface declares all the configuration fields for an environment * that cannot be inherited from the top-level environment, and must be defined specifically. * * If any of these fields are defined at the top-level then they should also be specifically defined * for each named environment. */ export interface EnvironmentNonInheritable { /** * A map of values to substitute when deploying your Worker. * * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * * @default {} * @nonInheritable */ define: Record; /** * A map of environment variables to set when deploying your Worker. * * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * * For reference, see https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables * * @default {} * @nonInheritable */ vars: Record; /** * A list of durable objects that your Worker should be bound to. * * For more information about Durable Objects, see the documentation at * https://developers.cloudflare.com/workers/learning/using-durable-objects * * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * * For reference, see https://developers.cloudflare.com/workers/wrangler/configuration/#durable-objects * * @default {bindings:[]} * @nonInheritable */ durable_objects: { bindings: DurableObjectBindings; }; /** * A list of workflows that your Worker should be bound to. * * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * * @default [] * @nonInheritable */ workflows: WorkflowBinding[]; /** * Cloudchamber configuration * * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * * @default {} * @nonInheritable */ cloudchamber: CloudchamberConfig; /** * Container related configuration * * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * * @default [] * @nonInheritable */ containers?: ContainerApp[]; /** * These specify any Workers KV Namespaces you want to * access from inside your Worker. * * To learn more about KV Namespaces, * see the documentation at https://developers.cloudflare.com/workers/learning/how-kv-works * * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * * For reference, see https://developers.cloudflare.com/workers/wrangler/configuration/#kv-namespaces * * @default [] * @nonInheritable */ kv_namespaces: { /** The binding name used to refer to the KV Namespace */ binding: string; /** The ID of the KV namespace */ id?: string; /** The ID of the KV namespace used during `wrangler dev` */ preview_id?: string; /** Whether the KV namespace should be remote or not (only available under `--x-remote-bindings`) */ experimental_remote?: boolean; }[]; /** * These specify bindings to send email from inside your Worker. * * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * * For reference, see https://developers.cloudflare.com/workers/wrangler/configuration/#email-bindings * * @default [] * @nonInheritable */ send_email: { /** The binding name used to refer to the this binding */ name: string; /** If this binding should be restricted to a specific verified address */ destination_address?: string; /** If this binding should be restricted to a set of verified addresses */ allowed_destination_addresses?: string[]; }[]; /** * Specifies Queues that are bound to this Worker environment. * * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * * For reference, see https://developers.cloudflare.com/workers/wrangler/configuration/#queues * * @default {consumers:[],producers:[]} * @nonInheritable */ queues: { /** Producer bindings */ producers?: { /** The binding name used to refer to the Queue in the Worker. */ binding: string; /** The name of this Queue. */ queue: string; /** The number of seconds to wait before delivering a message */ delivery_delay?: number; /** Whether the Queue producer should be remote or not (only available under `--x-remote-bindings`) */ experimental_remote?: boolean; }[]; /** Consumer configuration */ consumers?: { /** The name of the queue from which this consumer should consume. */ queue: string; /** The consumer type, e.g., worker, http-pull, r2-bucket, etc. Default is worker. */ type?: string; /** The maximum number of messages per batch */ max_batch_size?: number; /** The maximum number of seconds to wait to fill a batch with messages. */ max_batch_timeout?: number; /** The maximum number of retries for each message. */ max_retries?: number; /** The queue to send messages that failed to be consumed. */ dead_letter_queue?: string; /** The maximum number of concurrent consumer Worker invocations. Leaving this unset will allow your consumer to scale to the maximum concurrency needed to keep up with the message backlog. */ max_concurrency?: number | null; /** The number of milliseconds to wait for pulled messages to become visible again */ visibility_timeout_ms?: number; /** The number of seconds to wait before retrying a message */ retry_delay?: number; }[]; }; /** * Specifies R2 buckets that are bound to this Worker environment. * * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * * For reference, see https://developers.cloudflare.com/workers/wrangler/configuration/#r2-buckets * * @default [] * @nonInheritable */ r2_buckets: { /** The binding name used to refer to the R2 bucket in the Worker. */ binding: string; /** The name of this R2 bucket at the edge. */ bucket_name?: string; /** The preview name of this R2 bucket at the edge. */ preview_bucket_name?: string; /** The jurisdiction that the bucket exists in. Default if not present. */ jurisdiction?: string; /** Whether the R2 bucket should be remote or not (only available under `--x-remote-bindings`) */ experimental_remote?: boolean; }[]; /** * Specifies D1 databases that are bound to this Worker environment. * * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * * For reference, see https://developers.cloudflare.com/workers/wrangler/configuration/#d1-databases * * @default [] * @nonInheritable */ d1_databases: { /** The binding name used to refer to the D1 database in the Worker. */ binding: string; /** The name of this D1 database. */ database_name?: string; /** The UUID of this D1 database (not required). */ database_id?: string; /** The UUID of this D1 database for Wrangler Dev (if specified). */ preview_database_id?: string; /** The name of the migrations table for this D1 database (defaults to 'd1_migrations'). */ migrations_table?: string; /** The path to the directory of migrations for this D1 database (defaults to './migrations'). */ migrations_dir?: string; /** Internal use only. */ database_internal_env?: string; /** Whether the D1 database should be remote or not (only available under `--x-remote-bindings`) */ experimental_remote?: boolean; }[]; /** * Specifies Vectorize indexes that are bound to this Worker environment. * * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * * For reference, see https://developers.cloudflare.com/workers/wrangler/configuration/#vectorize-indexes * * @default [] * @nonInheritable */ vectorize: { /** The binding name used to refer to the Vectorize index in the Worker. */ binding: string; /** The name of the index. */ index_name: string; /** Whether the Vectorize index should be remote or not (only available under `--x-remote-bindings`) */ experimental_remote?: boolean; }[]; /** * Specifies Hyperdrive configs that are bound to this Worker environment. * * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * * For reference, see https://developers.cloudflare.com/workers/wrangler/configuration/#hyperdrive * * @default [] * @nonInheritable */ hyperdrive: { /** The binding name used to refer to the project in the Worker. */ binding: string; /** The id of the database. */ id: string; /** The local database connection string for `wrangler dev` */ localConnectionString?: string; }[]; /** * Specifies service bindings (Worker-to-Worker) that are bound to this Worker environment. * * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * * For reference, see https://developers.cloudflare.com/workers/wrangler/configuration/#service-bindings * * @default [] * @nonInheritable */ services: | { /** The binding name used to refer to the bound service. */ binding: string; /** The name of the service. */ service: string; /** The environment of the service (e.g. production, staging, etc). */ environment?: string; /** Optionally, the entrypoint (named export) of the service to bind to. */ entrypoint?: string; /** Optional properties that will be made available to the service via ctx.props. */ props?: Record; /** Whether the service binding should be remote or not (only available under `--x-remote-bindings`) */ experimental_remote?: boolean; }[] | undefined; /** * Specifies analytics engine datasets that are bound to this Worker environment. * * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * * For reference, see https://developers.cloudflare.com/workers/wrangler/configuration/#analytics-engine-datasets * * @default [] * @nonInheritable */ analytics_engine_datasets: { /** The binding name used to refer to the dataset in the Worker. */ binding: string; /** The name of this dataset to write to. */ dataset?: string; }[]; /** * A browser that will be usable from the Worker. * * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * * For reference, see https://developers.cloudflare.com/workers/wrangler/configuration/#browser-rendering * * @default {} * @nonInheritable */ browser: | { binding: string; /** Whether the Browser binding should be remote or not (only available under `--x-remote-bindings`) */ experimental_remote?: boolean; } | undefined; /** * Binding to the AI project. * * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * * For reference, see https://developers.cloudflare.com/workers/wrangler/configuration/#workers-ai * * @default {} * @nonInheritable */ ai: | { binding: string; staging?: boolean; /** Whether the AI binding should be remote or not (only available under `--x-remote-bindings`) */ experimental_remote?: boolean; } | undefined; /** * Binding to Cloudflare Images * * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * * For reference, see https://developers.cloudflare.com/workers/wrangler/configuration/#images * * @default {} * @nonInheritable */ images: | { binding: string; /** Whether the Images binding should be remote or not (only available under `--x-remote-bindings`) */ experimental_remote?: boolean; } | undefined; /** * Binding to the Worker Version's metadata */ version_metadata: | { binding: string; } | undefined; /** * "Unsafe" tables for features that aren't directly supported by wrangler. * * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * * @default {} * @nonInheritable */ unsafe: { /** * A set of bindings that should be put into a Worker's upload metadata without changes. These * can be used to implement bindings for features that haven't released and aren't supported * directly by wrangler or miniflare. */ bindings?: { name: string; type: string; [key: string]: unknown; }[]; /** * Arbitrary key/value pairs that will be included in the uploaded metadata. Values specified * here will always be applied to metadata last, so can add new or override existing fields. */ metadata?: { [key: string]: unknown; }; /** * Used for internal capnp uploads for the Workers runtime */ capnp?: | { base_path: string; source_schemas: string[]; compiled_schema?: never; } | { base_path?: never; source_schemas?: never; compiled_schema: string; }; }; /** * Specifies a list of mTLS certificates that are bound to this Worker environment. * * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * * For reference, see https://developers.cloudflare.com/workers/wrangler/configuration/#mtls-certificates * * @default [] * @nonInheritable */ mtls_certificates: { /** The binding name used to refer to the certificate in the Worker */ binding: string; /** The uuid of the uploaded mTLS certificate */ certificate_id: string; /** Whether the mtls fetcher should be remote or not (only available under `--x-remote-bindings`) */ experimental_remote?: boolean; }[]; /** * Specifies a list of Tail Workers that are bound to this Worker environment * * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * * @default [] * @nonInheritable */ tail_consumers?: TailConsumer[]; /** * Specifies namespace bindings that are bound to this Worker environment. * * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * * For reference, see https://developers.cloudflare.com/workers/wrangler/configuration/#dispatch-namespace-bindings-workers-for-platforms * * @default [] * @nonInheritable */ dispatch_namespaces: { /** The binding name used to refer to the bound service. */ binding: string; /** The namespace to bind to. */ namespace: string; /** Details about the outbound Worker which will handle outbound requests from your namespace */ outbound?: DispatchNamespaceOutbound; /** Whether the Dispatch Namespace should be remote or not (only available under `--x-remote-bindings`) */ experimental_remote?: boolean; }[]; /** * Specifies list of Pipelines bound to this Worker environment * * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * * @default [] * @nonInheritable */ pipelines: { /** The binding name used to refer to the bound service. */ binding: string; /** Name of the Pipeline to bind */ pipeline: string; }[]; /** * Specifies Secret Store bindings that are bound to this Worker environment. * * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * * @default [] * @nonInheritable */ secrets_store_secrets: { /** The binding name used to refer to the bound service. */ binding: string; /** Id of the secret store */ store_id: string; /** Name of the secret */ secret_name: string; }[]; /** * **DO NOT USE**. Hello World Binding Config to serve as an explanatory example. * * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * * @default [] * @nonInheritable */ unsafe_hello_world: { /** The binding name used to refer to the bound service. */ binding: string; /** Whether the timer is enabled */ enable_timer?: boolean; }[]; } /** * The raw environment configuration that we read from the config file. * * All the properties are optional, and will be replaced with defaults in the configuration that * is used in the rest of the codebase. */ export type RawEnvironment = Partial; /** * A bundling resolver rule, defining the modules type for paths that match the specified globs. */ export type Rule = { type: ConfigModuleRuleType; globs: string[]; fallthrough?: boolean; }; /** * The possible types for a `Rule`. */ export type ConfigModuleRuleType = | "ESModule" | "CommonJS" | "CompiledWasm" | "Text" | "Data" | "PythonModule" | "PythonRequirement"; export type TailConsumer = { /** The name of the service tail events will be forwarded to. */ service: string; /** (Optional) The environment of the service. */ environment?: string; }; export interface DispatchNamespaceOutbound { /** Name of the service handling the outbound requests */ service: string; /** (Optional) Name of the environment handling the outbound requests. */ environment?: string; /** (Optional) List of parameter names, for sending context from your dispatch Worker to the outbound handler */ parameters?: string[]; } export interface UserLimits { /** Maximum allowed CPU time for a Worker's invocation in milliseconds */ cpu_ms: number; } export type Assets = { /** Absolute path to assets directory */ directory?: string; /** Name of `env` binding property in the User Worker. */ binding?: string; /** How to handle HTML requests. */ html_handling?: "auto-trailing-slash" | "force-trailing-slash" | "drop-trailing-slash" | "none"; /** How to handle requests that do not match an asset. */ not_found_handling?: "single-page-application" | "404-page" | "none"; /** * Matches will be routed to the User Worker, and matches to negative rules will go to the Asset Worker. * * Can also be `true`, indicating that every request should be routed to the User Worker. */ run_worker_first?: string[] | boolean; }; export interface Observability { /** If observability is enabled for this Worker */ enabled?: boolean; /** The sampling rate */ head_sampling_rate?: number; logs?: { enabled?: boolean; /** The sampling rate */ head_sampling_rate?: number; /** Set to false to disable invocation logs */ invocation_logs?: boolean; }; } export type DockerConfiguration = { /** Socket used by miniflare to communicate with Docker */ socketPath: string; }; export type ContainerEngine = | { localDocker: DockerConfiguration; } | string; ================================================ FILE: src/presets/deno/preset.ts ================================================ import { defineNitroPreset } from "../_utils/preset.ts"; import { writeFile } from "../_utils/fs.ts"; import { resolve } from "pathe"; import { unenvDeno } from "./unenv/preset.ts"; import { builtinModules } from "node:module"; const denoDeploy = defineNitroPreset( { entry: "./deno/runtime/deno-deploy", manifest: { // https://docs.deno.com/deploy/reference/env_vars_and_contexts/#predefined-environment-variables // https://docs.deno.com/deploy/classic/environment-variables/#default-environment-variables deploymentId: process.env.DENO_DEPLOYMENT_ID, }, exportConditions: ["deno"], node: false, serveStatic: "deno", commands: { preview: "", deploy: "cd ./ && deno run -A jsr:@deno/deployctl deploy server/index.ts", }, unenv: unenvDeno, rollupConfig: { preserveEntrySignatures: false, external: (id) => id.startsWith("https://") || id.startsWith("node:"), output: { entryFileNames: "index.ts", manualChunks: (id) => "index", format: "esm", }, }, }, { name: "deno-deploy" as const, } ); const denoServer = defineNitroPreset( { entry: "./deno/runtime/deno-server", serveStatic: true, exportConditions: ["deno"], commands: { preview: "deno -A ./server/index.mjs", }, rollupConfig: { external: (id) => id.startsWith("https://") || id.startsWith("node:") || builtinModules.includes(id), output: { hoistTransitiveImports: false, }, }, hooks: { async compiled(nitro) { // https://docs.deno.com/runtime/fundamentals/configuration/ const denoJSON = { tasks: { start: "deno run -A ./server/index.mjs", }, }; await writeFile( resolve(nitro.options.output.dir, "deno.json"), JSON.stringify(denoJSON, null, 2) ); }, }, }, { aliases: ["deno"], name: "deno-server" as const, } ); export default [denoDeploy, denoServer] as const; ================================================ FILE: src/presets/deno/runtime/deno-deploy.ts ================================================ import "#nitro/virtual/polyfills"; import type { ServerRequest } from "srvx"; import type { Deno as _Deno } from "@deno/types"; import wsAdapter from "crossws/adapters/deno"; import { useNitroApp } from "nitro/app"; import { resolveWebsocketHooks } from "#nitro/runtime/app"; declare global { var Deno: typeof _Deno; } const nitroApp = useNitroApp(); const ws = import.meta._websocket ? wsAdapter({ resolve: resolveWebsocketHooks }) : undefined; // TODO: Migrate to srvx to provide request IP Deno.serve((denoReq: Request, info: _Deno.ServeHandlerInfo) => { // srvx compatibility const req = denoReq as unknown as ServerRequest; req.runtime ??= { name: "deno" }; req.runtime.deno ??= { info } as any; // TODO: Support remoteAddr // https://crossws.unjs.io/adapters/deno if (import.meta._websocket && req.headers.get("upgrade") === "websocket") { return ws!.handleUpgrade(req, info); } return nitroApp.fetch(req); }); ================================================ FILE: src/presets/deno/runtime/deno-server.ts ================================================ import "#nitro/virtual/polyfills"; import type { ServerRequest } from "srvx"; import { serve } from "srvx/deno"; import wsAdapter from "crossws/adapters/deno"; import { useNitroApp } from "nitro/app"; import { startScheduleRunner } from "#nitro/runtime/task"; import { trapUnhandledErrors } from "#nitro/runtime/error/hooks"; import { resolveWebsocketHooks } from "#nitro/runtime/app"; const _parsedPort = Number.parseInt(process.env.NITRO_PORT ?? process.env.PORT ?? ""); const port = Number.isNaN(_parsedPort) ? 3000 : _parsedPort; const host = process.env.NITRO_HOST || process.env.HOST; const cert = process.env.NITRO_SSL_CERT; const key = process.env.NITRO_SSL_KEY; // const socketPath = process.env.NITRO_UNIX_SOCKET; // TODO const nitroApp = useNitroApp(); let _fetch = nitroApp.fetch; if (import.meta._websocket) { const { handleUpgrade } = wsAdapter({ resolve: resolveWebsocketHooks }); _fetch = (req: ServerRequest) => { if (req.headers.get("upgrade") === "websocket") { return handleUpgrade(req, req.runtime!.deno!.info); } return nitroApp.fetch(req); }; } const server = serve({ port, hostname: host, tls: cert && key ? { cert, key } : undefined, fetch: _fetch, }); trapUnhandledErrors(); // Scheduled tasks if (import.meta._tasks) { startScheduleRunner({ waitUntil: server.waitUntil }); } export default {}; ================================================ FILE: src/presets/deno/unenv/node-compat.ts ================================================ // Auto generated at 2025-10-05 // Source: https://platform-node-compat.deno.dev/ // Do not edit this file manually // prettier-ignore export const builtnNodeModules = [ "node:_http_agent", "node:_http_common", // Missing exports: freeParser, isLenient, parsers, prepareError "node:_http_outgoing", "node:_http_server", // Missing exports: Server, ServerResponse, httpServerPreClose, kConnectionsCheckingInterval, kServerResponse, setupConnectionsTracking, storeHTTPOptions "node:_stream_duplex", // Missing exports: from, fromWeb, toWeb "node:_stream_passthrough", "node:_stream_readable", // Missing exports: ReadableState, from, fromWeb, toWeb, wrap "node:_stream_transform", "node:_stream_writable", // Missing exports: WritableState, fromWeb, toWeb "node:_tls_common", // Missing exports: SecureContext, translatePeerCertificate "node:_tls_wrap", "node:assert", // Missing exports: Assert, CallTracker, partialDeepStrictEqual "node:assert/strict", // Missing exports: Assert, CallTracker, partialDeepStrictEqual "node:async_hooks", "node:buffer", // Missing exports: File, resolveObjectURL "node:child_process", "node:cluster", "node:console", // Missing exports: context, createTask "node:constants", // Missing exports: O_DIRECT, O_NOATIME, defaultCipherList "node:crypto", // Missing exports: argon2, argon2Sync, decapsulate, encapsulate "node:dgram", "node:diagnostics_channel", // Missing exports: Channel "node:dns", // Missing exports: getDefaultResultOrder, lookupService, resolveTlsa "node:dns/promises", // Missing exports: getDefaultResultOrder, lookupService, resolveTlsa "node:domain", "node:events", // Missing exports: captureRejections, getMaxListeners, init, usingDomains "node:fs", // Missing exports: FileReadStream, FileWriteStream, Utf8Stream, fchmod, fchmodSync, fchown, fchownSync, glob, globSync, lchmod, lchmodSync, lchown, lchownSync, mkdtempDisposableSync, openAsBlob "node:fs/promises", // Missing exports: glob, lchmod, lchown, lutimes, mkdtempDisposable, statfs "node:http", // Missing exports: CloseEvent, MessageEvent, WebSocket, setMaxIdleHTTPParsers "node:http2", // Missing exports: performServerHandshake "node:https", "node:inspector", // Missing exports: NetworkResources "node:inspector/promises", // Missing exports: NetworkResources "node:module", // Missing exports: SourceMap, constants, enableCompileCache, findPackageJSON, flushCompileCache, getCompileCacheDir, getSourceMapsSupport, registerHooks, runMain, setSourceMapsSupport, stripTypeScriptTypes, syncBuiltinESMExports "node:net", "node:os", "node:path", // Missing exports: matchesGlob "node:path/posix", // Missing exports: matchesGlob "node:path/win32", // Missing exports: matchesGlob "node:perf_hooks", // Missing exports: Performance, PerformanceMark, PerformanceMeasure, PerformanceObserverEntryList, PerformanceResourceTiming, createHistogram "node:process", // Missing exports: availableMemory, binding, config, constrainedMemory, debugPort, domain, execve, exitCode, features, finalization, getActiveResourcesInfo, getgroups, hasUncaughtExceptionCaptureCallback, initgroups, loadEnvFile, moduleLoadList, openStdin, ppid, reallyExit, ref, release, report, resourceUsage, setSourceMapsEnabled, setUncaughtExceptionCaptureCallback, setegid, seteuid, setgid, setgroups, setuid, sourceMapsEnabled, threadCpuUsage, title, unref, uptime "node:punycode", "node:querystring", "node:readline", "node:readline/promises", "node:repl", // Missing exports: Recoverable, isValidSyntax, writer "node:sqlite", // Missing exports: Session, StatementSync, backup "node:stream", // Missing exports: destroy, promises "node:stream/consumers", "node:stream/promises", "node:stream/web", "node:string_decoder", "node:sys", // Missing exports: MIMEParams, MIMEType, diff, getCallSite, getSystemErrorMap, getSystemErrorMessage, parseEnv, setTraceSigInt, transferableAbortController, transferableAbortSignal "node:test", // Missing exports: assert, only, skip, snapshot, todo "node:timers", "node:timers/promises", "node:tls", // Missing exports: SecureContext, convertALPNProtocols, getCACertificates, setDefaultCACertificates "node:trace_events", "node:tty", "node:url", // Missing exports: URLPattern, fileURLToPathBuffer "node:util", // Missing exports: MIMEParams, MIMEType, diff, getCallSite, getSystemErrorMap, getSystemErrorMessage, parseEnv, setTraceSigInt, transferableAbortController, transferableAbortSignal "node:util/types", // Missing exports: isExternal "node:v8", // Missing exports: GCProfiler, getCppHeapStatistics, isStringOneByteRepresentation, promiseHooks, queryObjects, setHeapSnapshotNearHeapLimit, startupSnapshot "node:vm", "node:wasi", "node:worker_threads", // Missing exports: isInternalThread, isMarkedAsUntransferable, locks, markAsUncloneable, postMessageToThread, threadName "node:zlib", // Missing exports: ZstdCompress, ZstdDecompress, createZstdCompress, createZstdDecompress, zstdCompress, zstdCompressSync, zstdDecompress, zstdDecompressSync ]; // prettier-ignore export const unsupportedNodeModules = [ "node:_http_client", "node:_http_incoming", "node:_stream_wrap", "node:sea", "node:test/reporters", ]; ================================================ FILE: src/presets/deno/unenv/preset.ts ================================================ import type { Preset } from "unenv"; import * as denoCompat from "./node-compat.ts"; // https://platform-node-compat.deno.dev/ // https://platform-node-compat.netlify.app/ export const unenvDeno: Preset = { meta: { name: "nitro:deno", }, external: denoCompat.builtnNodeModules.map((m) => `node:${m}`), alias: { ...Object.fromEntries( denoCompat.builtnNodeModules.flatMap((m) => [ [m, m], [m.replace("node:", ""), m], ]) ), }, inject: { global: "unenv/polyfill/globalthis", process: "node:process", clearImmediate: ["node:timers", "clearImmediate"], setImmediate: ["node:timers", "setImmediate"], Buffer: ["node:buffer", "Buffer"], }, }; ================================================ FILE: src/presets/digitalocean/preset.ts ================================================ import { defineNitroPreset } from "../_utils/preset.ts"; const digitalOcean = defineNitroPreset( { extends: "node-server", serveStatic: true, }, { name: "digital-ocean" as const, } ); export default [digitalOcean] as const; ================================================ FILE: src/presets/firebase/preset.ts ================================================ import { defineNitroPreset } from "../_utils/preset.ts"; import { writeFile } from "../_utils/fs.ts"; import { version as nitroVersion } from "nitro/meta"; import { join, relative } from "pathe"; import { stringifyYAML } from "confbox"; import type { AppHostingOutputBundleConfig } from "./types.ts"; export type { FirebaseOptions as PresetOptions } from "./types.ts"; const firebaseAppHosting = defineNitroPreset( { extends: "node-server", serveStatic: true, hooks: { async compiled(nitro) { const serverEntry = join(nitro.options.output.serverDir, "index.mjs"); await writeFile( join(nitro.options.rootDir, ".apphosting/bundle.yaml"), stringifyYAML({ version: "v1", runConfig: { runCommand: `node ${relative(nitro.options.rootDir, serverEntry)}`, ...nitro.options.firebase?.appHosting, }, metadata: { framework: nitro.options.framework.name || "nitro", frameworkVersion: nitro.options.framework.version || "2.x", adapterPackageName: "nitro", adapterVersion: nitroVersion, }, outputFiles: { serverApp: { include: [relative(nitro.options.rootDir, nitro.options.output.dir)], }, }, } satisfies AppHostingOutputBundleConfig), true ); }, }, }, { name: "firebase-app-hosting" as const, stdName: "firebase_app_hosting", } ); export default [firebaseAppHosting] as const; ================================================ FILE: src/presets/firebase/types.ts ================================================ export interface FirebaseOptions { appHosting: Partial; } // Source: https://github.com/FirebaseExtended/firebase-framework-tools/blob/main/packages/%40apphosting/common/src/index.ts export interface AppHostingOutputBundleConfig { version: "v1"; // Fields needed to configure the App Hosting server runConfig: { /** Command to start the server (e.g. "node dist/index.js"). Assume this command is run from the root dir of the workspace. */ runCommand: string; /** Environment variables set when the app is run. */ environmentVariables?: Array<{ /** Name of the variable. */ variable: string; /** Value associated with the variable. */ value: string; /** Where the variable will be available, for now only RUNTIME is supported. */ availability: "RUNTIME"[]; }>; // See https://firebase.google.com/docs/reference/apphosting/rest/v1beta/projects.locations.backends.builds#runconfig for documentation on the next fields /** The maximum number of concurrent requests that each server instance can receive. */ concurrency?: number; /** The number of CPUs used in a single server instance. */ cpu?: number; /** The amount of memory available for a server instance. */ memoryMiB?: number; /** The limit on the minimum number of function instances that may coexist at a given time. */ minInstances?: number; /** The limit on the maximum number of function instances that may coexist at a given time. */ maxInstances?: number; }; // Additional fields needed for identifying the framework and adapter being used metadata: { // Name of the adapter (this should be the official package name) e.g. "@apphosting/adapter-nextjs" adapterPackageName: string; // Version of the adapter, e.g. "18.0.1" adapterVersion: string; // Name of the framework that is being supported, e.g. "angular" framework: string; // Version of the framework that is being supported, e.g. "18.0.1" frameworkVersion?: string; }; // Optional outputFiles for frameworks to optimize server files + static assets. outputFiles?: { /** serverApp holds a list of directories + files relative to the app root dir that frameworks need to deploy to the App Hosting server. */ serverApp: { include: string[]; }; }; } ================================================ FILE: src/presets/flightcontrol/preset.ts ================================================ import { defineNitroPreset } from "../_utils/preset.ts"; const flightControl = defineNitroPreset( { extends: "node-server", serveStatic: true, }, { name: "flight-control" as const, } ); export default [flightControl] as const; ================================================ FILE: src/presets/genezio/preset.ts ================================================ import { defineNitroPreset } from "../_utils/preset.ts"; const genezio = defineNitroPreset( { extends: "aws_lambda", }, { name: "genezio" as const, } ); export default [genezio] as const; ================================================ FILE: src/presets/heroku/preset.ts ================================================ import { defineNitroPreset } from "../_utils/preset.ts"; const heroku = defineNitroPreset( { extends: "node-server", serveStatic: true, }, { name: "heroku" as const, } ); export default [heroku] as const; ================================================ FILE: src/presets/iis/preset.ts ================================================ import { defineNitroPreset } from "../_utils/preset.ts"; import type { Nitro } from "nitro/types"; import { writeIISFiles, writeIISNodeFiles } from "./utils.ts"; const iisHandler = defineNitroPreset( { extends: "node-server", serveStatic: true, hooks: { async compiled(nitro: Nitro) { await writeIISFiles(nitro); }, }, }, { name: "iis-handler" as const, } ); const iisNode = defineNitroPreset( { extends: "node-server", serveStatic: true, hooks: { async compiled(nitro: Nitro) { await writeIISNodeFiles(nitro); }, }, }, { name: "iis-node" as const, } ); export default [iisHandler, iisNode] as const; ================================================ FILE: src/presets/iis/utils.ts ================================================ import { existsSync } from "node:fs"; import { readFile } from "node:fs/promises"; import { defu } from "defu"; import { writeFile } from "../_utils/fs.ts"; import type { Nitro } from "nitro/types"; import { resolve } from "pathe"; export async function writeIISFiles(nitro: Nitro) { await writeFile(resolve(nitro.options.output.dir, "web.config"), await iisXmlTemplate(nitro)); } export async function writeIISNodeFiles(nitro: Nitro) { await writeFile(resolve(nitro.options.output.dir, "web.config"), await iisnodeXmlTemplate(nitro)); await writeFile( resolve(nitro.options.output.dir, "index.js"), /*js */ ` if (process.env.PORT.startsWith('\\\\')) { process.env.NITRO_UNIX_SOCKET = process.env.PORT delete process.env.PORT } import('./server/index.mjs'); ` ); } async function iisnodeXmlTemplate(nitro: Nitro) { const path = resolve(nitro.options.rootDir, "web.config"); const originalString = /* xml */ ` `; if (existsSync(path)) { const fileString = await readFile(path, "utf8"); const originalWebConfig: Record = await parseXmlDoc(originalString); const fileWebConfig: Record = await parseXmlDoc(fileString); if (nitro.options.iis?.mergeConfig && !nitro.options.iis.overrideConfig) { return buildNewXmlDoc(defu(fileWebConfig, originalWebConfig)); } if (nitro.options.iis?.overrideConfig) { return buildNewXmlDoc({ ...fileWebConfig }); } } return originalString; } async function iisXmlTemplate(nitro: Nitro) { const path = resolve(nitro.options.rootDir, "web.config"); const originalString = /* xml */ ` `; if (existsSync(path)) { const fileString = await readFile(path, "utf8"); const originalWebConfig: Record = await parseXmlDoc(originalString); const fileWebConfig: Record = await parseXmlDoc(fileString); if (nitro.options.iis?.mergeConfig && !nitro.options.iis.overrideConfig) { return buildNewXmlDoc(defu(fileWebConfig, originalWebConfig)); } if (nitro.options.iis?.overrideConfig) { return buildNewXmlDoc({ ...fileWebConfig }); } } return originalString; } // XML Helpers async function parseXmlDoc(xml: string): Promise> { const { Parser } = await import("xml2js"); if (xml === undefined || !xml) { return {}; } const parser = new Parser({ explicitArray: false }); let parsedRecord: Record = {}; parser.parseString(xml, (_, r) => { parsedRecord = r; }); return parsedRecord; } async function buildNewXmlDoc(xmlObj: Record): Promise { const { Builder } = await import("xml2js"); const builder = new Builder(); return builder.buildObject({ ...xmlObj }); } ================================================ FILE: src/presets/index.ts ================================================ export { resolvePreset } from "./_resolve.ts"; export type { PresetOptions, PresetName, PresetNameInput } from "./_types.gen.ts"; ================================================ FILE: src/presets/koyeb/preset.ts ================================================ import { defineNitroPreset } from "../_utils/preset.ts"; const koyeb = defineNitroPreset( { extends: "node-server", serveStatic: true, }, { name: "koyeb" as const, } ); export default [koyeb] as const; ================================================ FILE: src/presets/netlify/preset.ts ================================================ import { promises as fsp } from "node:fs"; import { defineNitroPreset } from "../_utils/preset.ts"; import type { Nitro } from "nitro/types"; import type { Config, Manifest } from "@netlify/edge-functions"; import { dirname, join } from "pathe"; import { unenvDeno } from "../deno/unenv/preset.ts"; import { generateNetlifyFunction, getGeneratorString, getStaticPaths, writeHeaders, writeRedirects, } from "./utils.ts"; export type { NetlifyOptions as PresetOptions } from "./types.ts"; // Netlify functions const netlify = defineNitroPreset( { entry: "./netlify/runtime/netlify", manifest: { deploymentId: process.env.DEPLOY_ID, }, output: { dir: "{{ rootDir }}/.netlify/functions-internal", publicDir: "{{ rootDir }}/dist/{{ baseURL }}", }, prerender: { // Prevents an unnecessary redirect from /page/ to /page when accessing prerendered content. // Reference: https://answers.netlify.com/t/support-guide-how-can-i-alter-trailing-slash-behaviour-in-my-urls-will-enabling-pretty-urls-help/31191 // Reference: https://nitro.build/config#prerender autoSubfolderIndex: false, }, rollupConfig: { output: { entryFileNames: "main.mjs", }, }, hooks: { async compiled(nitro: Nitro) { await writeHeaders(nitro); await writeRedirects(nitro); await fsp.writeFile( join(nitro.options.output.dir, "server", "server.mjs"), generateNetlifyFunction(nitro) ); if (nitro.options.netlify?.images) { nitro.options.netlify.config ||= {}; nitro.options.netlify.config.images ||= nitro.options.netlify?.images; } if (Object.keys(nitro.options.netlify?.config || {}).length > 0) { const configPath = join(nitro.options.output.dir, "../deploy/v1/config.json"); await fsp.mkdir(dirname(configPath), { recursive: true }); await fsp.writeFile(configPath, JSON.stringify(nitro.options.netlify?.config), "utf8"); } }, }, }, { name: "netlify" as const, stdName: "netlify", } ); // Netlify edge const netlifyEdge = defineNitroPreset( { extends: "base-worker", entry: "./netlify/runtime/netlify-edge", manifest: { deploymentId: process.env.DEPLOY_ID, }, exportConditions: ["netlify"], output: { serverDir: "{{ rootDir }}/.netlify/edge-functions/server", publicDir: "{{ rootDir }}/dist/{{ baseURL }}", }, prerender: { // Prevents an unnecessary redirect from /page/ to /page when accessing prerendered content. // Reference: https://answers.netlify.com/t/support-guide-how-can-i-alter-trailing-slash-behaviour-in-my-urls-will-enabling-pretty-urls-help/31191 // Reference: https://nitro.build/config#prerender autoSubfolderIndex: false, }, rollupConfig: { output: { entryFileNames: "server.js", format: "esm", }, }, unenv: unenvDeno, hooks: { async compiled(nitro: Nitro) { await writeHeaders(nitro); await writeRedirects(nitro); // https://docs.netlify.com/edge-functions/create-integration/ const manifest: Manifest = { version: 1, functions: [ { path: "/*", excludedPath: getStaticPaths( nitro.options.publicAssets, nitro.options.baseURL ) as Config["excludedPath"], name: "edge server handler", function: "server", generator: getGeneratorString(nitro), }, ], }; const manifestPath = join(nitro.options.rootDir, ".netlify/edge-functions/manifest.json"); await fsp.mkdir(dirname(manifestPath), { recursive: true }); await fsp.writeFile(manifestPath, JSON.stringify(manifest, null, 2)); }, }, }, { name: "netlify-edge" as const, } ); const netlifyStatic = defineNitroPreset( { extends: "static", manifest: { deploymentId: process.env.DEPLOY_ID, }, output: { dir: "{{ rootDir }}/dist", publicDir: "{{ rootDir }}/dist/{{ baseURL }}", }, prerender: { // Prevents an unnecessary redirect from /page/ to /page when accessing prerendered content. // Reference: https://answers.netlify.com/t/support-guide-how-can-i-alter-trailing-slash-behaviour-in-my-urls-will-enabling-pretty-urls-help/31191 // Reference: https://nitro.build/config#prerender autoSubfolderIndex: false, }, commands: { preview: "npx serve ./", }, hooks: { async compiled(nitro: Nitro) { await writeHeaders(nitro); await writeRedirects(nitro); }, }, }, { name: "netlify-static" as const, stdName: "netlify", static: true, } ); export default [netlify, netlifyEdge, netlifyStatic] as const; ================================================ FILE: src/presets/netlify/runtime/netlify-edge.ts ================================================ import "#nitro/virtual/polyfills"; import { useNitroApp } from "nitro/app"; import { isPublicAssetURL } from "#nitro/virtual/public-assets"; import type { Context } from "@netlify/edge-functions"; import type { ServerRequest } from "srvx"; const nitroApp = useNitroApp(); // https://docs.netlify.com/edge-functions/api/ export default async function netlifyEdge(netlifyReq: Request, context: Context) { // srvx compatibility const req = netlifyReq as unknown as ServerRequest; req.ip = context.ip; req.runtime ??= { name: "netlify-edge" }; req.runtime.netlify ??= { context } as any; const url = new URL(req.url); if (isPublicAssetURL(url.pathname)) { return; } if (!req.headers.has("x-forwarded-proto") && url.protocol === "https:") { req.headers.set("x-forwarded-proto", "https"); } return nitroApp.fetch(req); } ================================================ FILE: src/presets/netlify/runtime/netlify.ts ================================================ import "#nitro/virtual/polyfills"; import { useNitroApp } from "nitro/app"; import type { ServerRequest } from "srvx"; const nitroApp = useNitroApp(); const ONE_YEAR_IN_SECONDS = 365 * 24 * 60 * 60; const handler = async (req: ServerRequest): Promise => { req.runtime ??= { name: "netlify" }; req.ip ??= req.headers.get("x-nf-client-connection-ip") || undefined; const response = await nitroApp.fetch(req); const isr = (req.context?.routeRules || {})?.isr?.options; if (isr) { const maxAge = typeof isr === "number" ? isr : ONE_YEAR_IN_SECONDS; const revalidateDirective = typeof isr === "number" ? `stale-while-revalidate=${ONE_YEAR_IN_SECONDS}` : "must-revalidate"; if (!response.headers.has("Cache-Control")) { response.headers.set("Cache-Control", "public, max-age=0, must-revalidate"); } response.headers.set( "Netlify-CDN-Cache-Control", `public, max-age=${maxAge}, ${revalidateDirective}, durable` ); } return response; }; export default handler; ================================================ FILE: src/presets/netlify/types.ts ================================================ // https://docs.netlify.com/build/frameworks/frameworks-api/ export interface NetlifyOptions { /** @deprecated Use `config.images` */ images?: NetlifyImagesConfig; config?: NetlifyConfigJson; // skewProtection?: NetlifySkewProtectionJson; // edgeFunctionsImportMap?: NetlifyImportMapJson; // blobsMetadata?: Record; } export interface NetlifyConfigJson { edge_functions?: NetlifyEdgeFunctionDeclaration[]; functions?: NetlifyFunctionsConfig | NetlifyFunctionsConfigByPattern; headers?: NetlifyHeaderRule[]; images?: NetlifyImagesConfig; redirects?: NetlifyRedirectRule[]; "redirects!"?: NetlifyRedirectRule[]; } interface NetlifyEdgeFunctionDeclaration { function: string; path?: string; pattern?: string; excludedPath?: string; excludedPattern?: string; cache?: string; [key: string]: unknown; } interface NetlifyFunctionsConfig extends NetlifyFunctionInlineConfig { directory?: string; } export type NetlifyFunctionsConfigByPattern = Record; interface NetlifyFunctionInlineConfig { included_files?: string[]; [key: string]: unknown; } interface NetlifyHeaderRule { for: string; values: Record; [key: string]: unknown; } interface NetlifyImagesConfig { remote_images?: string[]; [key: string]: unknown; } interface NetlifyRedirectRule { from: string; to: string; status?: number; force?: boolean; conditions?: Record; query?: Record; [key: string]: unknown; } export interface NetlifySkewProtectionJson { patterns: string[]; sources: NetlifySkewProtectionSource[]; [key: string]: unknown; } interface NetlifySkewProtectionSource { type: "cookie" | "header" | "query"; name: string; [key: string]: unknown; } export interface NetlifyImportMapJson { imports?: Record; scopes?: Record>; [key: string]: unknown; } export interface NetlifyBlobMetadata { headers?: Record; [key: string]: unknown; } ================================================ FILE: src/presets/netlify/utils.ts ================================================ import { existsSync, promises as fsp } from "node:fs"; import type { Nitro, PublicAssetDir } from "nitro/types"; import { join } from "pathe"; import { joinURL } from "ufo"; export async function writeRedirects(nitro: Nitro) { const redirectsPath = join(nitro.options.output.publicDir, "_redirects"); let contents = ""; if (nitro.options.static) { const staticFallback = existsSync(join(nitro.options.output.publicDir, "404.html")) ? "/* /404.html 404" : ""; contents += staticFallback; } const rules = Object.entries(nitro.options.routeRules).sort( (a, b) => a[0].split(/\/(?!\*)/).length - b[0].split(/\/(?!\*)/).length ); for (const [key, routeRules] of rules.filter(([_, routeRules]) => routeRules.redirect)) { let code = routeRules.redirect!.status; // TODO: Remove map when netlify support 307/308 if (code === 307) { code = 302; } if (code === 308) { code = 301; } contents = `${key.replace("/**", "/*")}\t${routeRules.redirect!.to.replace( "/**", "/:splat" )}\t${code}\n` + contents; } if (existsSync(redirectsPath)) { const currentRedirects = await fsp.readFile(redirectsPath, "utf8"); if (/^\/\* /m.test(currentRedirects)) { nitro.logger.info( "Not adding Nitro fallback to `_redirects` (as an existing fallback was found)." ); return; } nitro.logger.info("Adding Nitro fallback to `_redirects` to handle all unmatched routes."); contents = currentRedirects + "\n" + contents; } await fsp.writeFile(redirectsPath, contents); } export async function writeHeaders(nitro: Nitro) { const headersPath = join(nitro.options.output.publicDir, "_headers"); let contents = ""; const rules = Object.entries(nitro.options.routeRules).sort( (a, b) => b[0].split(/\/(?!\*)/).length - a[0].split(/\/(?!\*)/).length ); for (const [path, routeRules] of rules.filter(([_, routeRules]) => routeRules.headers)) { const headers = [ path.replace("/**", "/*"), ...Object.entries({ ...routeRules.headers }).map( ([header, value]) => ` ${header}: ${value}` ), ].join("\n"); contents += headers + "\n"; } if (existsSync(headersPath)) { const currentHeaders = await fsp.readFile(headersPath, "utf8"); if (/^\/\* /m.test(currentHeaders)) { nitro.logger.info( "Not adding Nitro fallback to `_headers` (as an existing fallback was found)." ); return; } nitro.logger.info("Adding Nitro fallback to `_headers` to handle all unmatched routes."); contents = currentHeaders + "\n" + contents; } await fsp.writeFile(headersPath, contents); } export function getStaticPaths(publicAssets: PublicAssetDir[], baseURL: string): string[] { return [ "/.netlify/*", // TODO: should this be also be prefixed with baseURL? ...publicAssets .filter((a) => a.fallthrough !== true && a.baseURL && a.baseURL !== "/") .map((a) => joinURL(baseURL, a.baseURL!, "*")), ]; } // This is written to the functions directory. It just re-exports the compiled handler, // along with its config. We do this instead of compiling the entrypoint directly because // the Netlify platform actually statically analyzes the function file to read the config; // if we compiled the entrypoint directly, it would be chunked and wouldn't be analyzable. export function generateNetlifyFunction(nitro: Nitro) { return /* js */ ` export { default } from "./main.mjs"; export const config = { name: "server handler", generator: "${getGeneratorString(nitro)}", path: "/*", nodeBundler: "none", includedFiles: ["**"], excludedPath: ${JSON.stringify(getStaticPaths(nitro.options.publicAssets, nitro.options.baseURL))}, preferStatic: true, }; `.trim(); } export function getGeneratorString(nitro: Nitro) { return `${nitro.options.framework.name}@${nitro.options.framework.version}`; } ================================================ FILE: src/presets/node/cluster.ts ================================================ import { resolve } from "pathe"; import { writeFile } from "../../utils/fs.ts"; import { defineNitroPreset } from "../_utils/preset.ts"; export const nodeCluster = defineNitroPreset( { extends: "node-server", serveStatic: true, entry: "./node/runtime/node-cluster", rollupConfig: { output: { entryFileNames: "worker.mjs", }, }, hooks: { async compiled(nitro) { await writeFile(resolve(nitro.options.output.serverDir, "index.mjs"), nodeClusterEntry()); }, }, }, { name: "node-cluster" as const, } ); function nodeClusterEntry() { return /* js */ ` import cluster from "node:cluster"; import os from "node:os"; if (cluster.isPrimary) { const numberOfWorkers = Number.parseInt(process.env.NITRO_CLUSTER_WORKERS || "") || (os.cpus().length > 0 ? os.cpus().length : 1); for (let i = 0; i < numberOfWorkers; i++) { cluster.fork({ WORKER_ID: i + 1, }); } } else { import("./worker.mjs").catch((error) => { console.error(error); process.exit(1); }); } `; } ================================================ FILE: src/presets/node/preset.ts ================================================ import { defineNitroPreset } from "../_utils/preset.ts"; import { nodeCluster } from "./cluster.ts"; const nodeServer = defineNitroPreset( { entry: "./node/runtime/node-server", serveStatic: true, commands: { preview: "node ./server/index.mjs", }, }, { name: "node-server" as const, aliases: ["node"], } ); const nodeMiddleware = defineNitroPreset( { entry: "./node/runtime/node-middleware", }, { name: "node-middleware" as const, } ); export default [nodeServer, nodeCluster, nodeMiddleware] as const; ================================================ FILE: src/presets/node/runtime/node-cluster.ts ================================================ import "#nitro/virtual/polyfills"; import cluster from "node:cluster"; import { NodeRequest, serve } from "srvx/node"; import wsAdapter from "crossws/adapters/node"; import { useNitroApp } from "nitro/app"; import { startScheduleRunner } from "#nitro/runtime/task"; import { trapUnhandledErrors } from "#nitro/runtime/error/hooks"; import { resolveWebsocketHooks } from "#nitro/runtime/app"; const _parsedPort = Number.parseInt(process.env.NITRO_PORT ?? process.env.PORT ?? ""); const port = Number.isNaN(_parsedPort) ? 3000 : _parsedPort; const host = process.env.NITRO_HOST || process.env.HOST; const cert = process.env.NITRO_SSL_CERT; const key = process.env.NITRO_SSL_KEY; // const socketPath = process.env.NITRO_UNIX_SOCKET; // TODO const clusterId = cluster.isWorker && process.env.WORKER_ID; if (clusterId) { console.log(`Worker #${clusterId} started`); } const nitroApp = useNitroApp(); const server = serve({ port, hostname: host, tls: cert && key ? { cert, key } : undefined, node: { exclusive: false }, silent: clusterId ? clusterId !== "1" : undefined, fetch: nitroApp.fetch, }); if (import.meta._websocket) { const { handleUpgrade } = wsAdapter({ resolve: resolveWebsocketHooks }); server.node!.server!.on("upgrade", (req, socket, head) => { handleUpgrade( req, socket, head, // @ts-expect-error (upgrade is not typed) new NodeRequest({ req, upgrade: { socket, head } }) ); }); } trapUnhandledErrors(); // Scheduled tasks if (import.meta._tasks) { startScheduleRunner({ waitUntil: server.waitUntil }); } export default {}; ================================================ FILE: src/presets/node/runtime/node-middleware.ts ================================================ import "#nitro/virtual/polyfills"; import { toNodeHandler } from "srvx/node"; import wsAdapter from "crossws/adapters/node"; import { useNitroApp } from "nitro/app"; import { startScheduleRunner } from "#nitro/runtime/task"; import { resolveWebsocketHooks } from "#nitro/runtime/app"; const nitroApp = useNitroApp(); export const middleware = toNodeHandler(nitroApp.fetch); const ws = import.meta._websocket ? wsAdapter({ resolve: resolveWebsocketHooks }) : undefined; export const handleUpgrade = ws?.handleUpgrade; // Scheduled tasks if (import.meta._tasks) { startScheduleRunner(); } ================================================ FILE: src/presets/node/runtime/node-server.ts ================================================ import "#nitro/virtual/polyfills"; import { NodeRequest, serve } from "srvx/node"; import wsAdapter from "crossws/adapters/node"; import { useNitroApp } from "nitro/app"; import { startScheduleRunner } from "#nitro/runtime/task"; import { trapUnhandledErrors } from "#nitro/runtime/error/hooks"; import { resolveWebsocketHooks } from "#nitro/runtime/app"; const _parsedPort = Number.parseInt(process.env.NITRO_PORT ?? process.env.PORT ?? ""); const port = Number.isNaN(_parsedPort) ? 3000 : _parsedPort; const host = process.env.NITRO_HOST || process.env.HOST; const cert = process.env.NITRO_SSL_CERT; const key = process.env.NITRO_SSL_KEY; // const socketPath = process.env.NITRO_UNIX_SOCKET; // TODO const nitroApp = useNitroApp(); const server = serve({ port, hostname: host, tls: cert && key ? { cert, key } : undefined, fetch: nitroApp.fetch, }); if (import.meta._websocket) { const { handleUpgrade } = wsAdapter({ resolve: resolveWebsocketHooks }); server.node!.server!.on("upgrade", (req, socket, head) => { handleUpgrade( req, socket, head, // @ts-expect-error (upgrade is not typed) new NodeRequest({ req, upgrade: { socket, head } }) ); }); } trapUnhandledErrors(); // Scheduled tasks if (import.meta._tasks) { startScheduleRunner({ waitUntil: server.waitUntil }); } export default {}; ================================================ FILE: src/presets/platform.sh/preset.ts ================================================ import { defineNitroPreset } from "../_utils/preset.ts"; const platformSh = defineNitroPreset( { extends: "node-server", serveStatic: true, }, { name: "platform-sh" as const, } ); export default [platformSh] as const; ================================================ FILE: src/presets/render.com/preset.ts ================================================ import { defineNitroPreset } from "../_utils/preset.ts"; const renderCom = defineNitroPreset( { extends: "node-server", serveStatic: true, }, { name: "render-com" as const, } ); export default [renderCom] as const; ================================================ FILE: src/presets/standard/preset.ts ================================================ import { defineNitroPreset } from "../_utils/preset.ts"; const standard = defineNitroPreset( { entry: "./standard/runtime/server", serveStatic: false, output: { publicDir: "{{ output.dir }}/public/{{ baseURL }}", }, commands: { preview: "npx srvx --prod ./", }, alias: { srvx: "srvx/generic", "srvx/bun": "srvx/bun", "srvx/deno": "srvx/deno", "srvx/node": "srvx/node", "srvx/generic": "srvx/generic", }, }, { name: "standard" as const, } ); export default [standard] as const; ================================================ FILE: src/presets/standard/runtime/server.ts ================================================ import "#nitro/virtual/polyfills"; import { useNitroApp } from "nitro/app"; const nitroApp = useNitroApp(); export default { fetch: nitroApp.fetch, }; ================================================ FILE: src/presets/stormkit/preset.ts ================================================ import { defineNitroPreset } from "../_utils/preset.ts"; const stormkit = defineNitroPreset( { entry: "./stormkit/runtime/stormkit", output: { dir: "{{ rootDir }}/.stormkit", publicDir: "{{ rootDir }}/.stormkit/public/{{ baseURL }}", }, }, { name: "stormkit" as const, stdName: "stormkit", } ); export default [stormkit] as const; ================================================ FILE: src/presets/stormkit/runtime/stormkit.ts ================================================ import "#nitro/virtual/polyfills"; import { useNitroApp } from "nitro/app"; import { awsResponseBody } from "../../aws-lambda/runtime/_utils.ts"; import type { Handler } from "aws-lambda"; import type { ServerRequest } from "srvx"; type StormkitEvent = { url: string; // e.g. /my/path, /my/path?with=query path: string; method: string; body?: string; query?: Record>; headers?: Record; rawHeaders?: Array; }; type StormkitResponse = { headers?: Record; body?: string; buffer?: string; statusCode: number; errorMessage?: string; errorStack?: string; }; const nitroApp = useNitroApp(); export const handler: Handler = async function (event, context) { const req = new Request(event.url, { method: event.method || "GET", headers: event.headers, body: event.body, }) as ServerRequest; // srvx compatibility req.runtime ??= { name: "stormkit" }; req.runtime.stormkit ??= { event, context } as any; const response = await nitroApp.fetch(req); const { body, isBase64Encoded } = await awsResponseBody(response); return { statusCode: response.status, headers: normalizeOutgoingHeaders(response.headers), [isBase64Encoded ? "buffer" : "body"]: body, } satisfies StormkitResponse; }; function normalizeOutgoingHeaders(headers: Headers): Record { return Object.fromEntries( Object.entries(headers).map(([k, v]) => [k, Array.isArray(v) ? v.join(",") : String(v)]) ); } ================================================ FILE: src/presets/vercel/preset.ts ================================================ import { defineNitroPreset } from "../_utils/preset.ts"; import type { Nitro } from "nitro/types"; import { presetsDir } from "nitro/meta"; import { join } from "pathe"; import { deprecateSWR, generateFunctionFiles, generateStaticFiles, resolveVercelRuntime, } from "./utils.ts"; export type { VercelOptions as PresetOptions } from "./types.ts"; // https://vercel.com/docs/build-output-api/v3 const vercel = defineNitroPreset( { entry: "./vercel/runtime/vercel.{format}", manifest: { deploymentId: process.env.VERCEL_DEPLOYMENT_ID, }, vercel: { skewProtection: !!process.env.VERCEL_SKEW_PROTECTION_ENABLED, cronHandlerRoute: "/_vercel/cron", }, output: { dir: "{{ rootDir }}/.vercel/output", serverDir: "{{ output.dir }}/functions/__server.func", publicDir: "{{ output.dir }}/static/{{ baseURL }}", }, commands: { preview: "npx srvx --static ../../static ./functions/__server.func/index.mjs", deploy: "npx vercel deploy --prebuilt", }, hooks: { "build:before": async (nitro: Nitro) => { const logger = nitro.logger.withTag("vercel"); // Runtime const runtime = await resolveVercelRuntime(nitro); if (runtime.startsWith("bun") && !nitro.options.exportConditions!.includes("bun")) { nitro.options.exportConditions!.push("bun"); } logger.info(`Using \`${runtime}\` runtime.`); // Entry handler format let serverFormat = nitro.options.vercel?.entryFormat; if (!serverFormat) { const hasNodeHandler = nitro.routing.routes.routes .flatMap((r) => r.data) .some((h) => h.format === "node"); serverFormat = hasNodeHandler ? "node" : "web"; } logger.info(`Using \`${serverFormat}\` entry format.`); nitro.options.entry = nitro.options.entry.replace("{format}", serverFormat); // Cron tasks handler if ( nitro.options.experimental.tasks && Object.keys(nitro.options.scheduledTasks || {}).length > 0 ) { nitro.options.handlers.push({ route: nitro.options.vercel!.cronHandlerRoute || "/_vercel/cron", lazy: true, handler: join(presetsDir, "vercel/runtime/cron-handler"), }); } }, "rollup:before": (nitro: Nitro) => { deprecateSWR(nitro); }, async compiled(nitro: Nitro) { await generateFunctionFiles(nitro); }, }, }, { name: "vercel" as const, stdName: "vercel", } ); const vercelStatic = defineNitroPreset( { extends: "static", manifest: { deploymentId: process.env.VERCEL_DEPLOYMENT_ID, }, vercel: { skewProtection: !!process.env.VERCEL_SKEW_PROTECTION_ENABLED, }, output: { dir: "{{ rootDir }}/.vercel/output", publicDir: "{{ output.dir }}/static/{{ baseURL }}", }, commands: { preview: "npx serve ./static", }, hooks: { "rollup:before": (nitro: Nitro) => { deprecateSWR(nitro); }, async compiled(nitro: Nitro) { await generateStaticFiles(nitro); }, }, }, { name: "vercel-static" as const, stdName: "vercel", static: true, } ); export default [vercel, vercelStatic] as const; ================================================ FILE: src/presets/vercel/runtime/cron-handler.ts ================================================ import { timingSafeEqual } from "node:crypto"; import { defineHandler, HTTPError } from "nitro/h3"; import { runCronTasks } from "#nitro/runtime/task"; export default defineHandler(async (event) => { // Validate CRON_SECRET if set - https://vercel.com/docs/cron-jobs/manage-cron-jobs#securing-cron-jobs const cronSecret = process.env.CRON_SECRET; if (cronSecret) { const authHeader = event.req.headers.get("authorization") || ""; const expected = `Bearer ${cronSecret}`; const a = Buffer.from(authHeader); const b = Buffer.from(expected); if (a.length !== b.length || !timingSafeEqual(a, b)) { throw new HTTPError("Unauthorized", { status: 401 }); } } const cron = event.req.headers.get("x-vercel-cron-schedule"); if (!cron) { throw new HTTPError("Missing x-vercel-cron-schedule header", { status: 400 }); } await runCronTasks(cron, { context: { waitUntil: event.req.waitUntil }, payload: { scheduledTime: Date.now(), }, }); return { success: true }; }); ================================================ FILE: src/presets/vercel/runtime/isr.ts ================================================ export const ISR_URL_PARAM = "__isr_route"; export function isrRouteRewrite( reqUrl: string, xNowRouteMatches: string | null ): [pathname: string, search: string] | undefined { if (xNowRouteMatches) { const isrURL = new URLSearchParams(xNowRouteMatches).get(ISR_URL_PARAM); if (isrURL) { return [decodeURIComponent(isrURL), ""]; } } else { const queryIndex = reqUrl.indexOf("?"); if (queryIndex !== -1) { const params = new URLSearchParams(reqUrl.slice(queryIndex + 1)); const isrURL = params.get(ISR_URL_PARAM); if (isrURL) { params.delete(ISR_URL_PARAM); return [decodeURIComponent(isrURL), params.toString()]; } } } } ================================================ FILE: src/presets/vercel/runtime/vercel.node.ts ================================================ import "#nitro/virtual/polyfills"; import type { NodeServerRequest, NodeServerResponse } from "srvx"; import { toNodeHandler } from "srvx/node"; import { useNitroApp, getRouteRules } from "nitro/app"; import { isrRouteRewrite } from "./isr.ts"; const nitroApp = useNitroApp(); const handler = toNodeHandler(nitroApp.fetch); export default function nodeHandler(req: NodeServerRequest, res: NodeServerResponse) { // https://vercel.com/docs/headers/request-headers#x-forwarded-for // srvx node adapter uses req.socket.remoteAddress for req.ip let ip: string | undefined; Object.defineProperty(req.socket, "remoteAddress", { get() { const h = req.headers["x-forwarded-for"] as string; return (ip ??= h?.split?.(",").shift()?.trim()); }, }); // ISR route rewrite const isrURL = isrRouteRewrite(req.url!, req.headers["x-now-route-matches"] as string); if (isrURL) { const { routeRules } = getRouteRules("", isrURL[0]); if (routeRules?.isr) { req.url = isrURL[0] + (isrURL[1] ? `?${isrURL[1]}` : ""); } } return handler(req as any, res as any); } ================================================ FILE: src/presets/vercel/runtime/vercel.web.ts ================================================ import "#nitro/virtual/polyfills"; import { useNitroApp, getRouteRules } from "nitro/app"; import type { ServerRequest } from "srvx"; import { isrRouteRewrite } from "./isr.ts"; const nitroApp = useNitroApp(); export default { fetch(req: ServerRequest, context: { waitUntil: (promise: Promise) => void }) { // ISR route rewrite const isrURL = isrRouteRewrite(req.url, req.headers.get("x-now-route-matches")); if (isrURL) { const { routeRules } = getRouteRules("", isrURL[0]); if (routeRules?.isr) { req = new Request( new URL(isrURL[0] + (isrURL[1] ? `?${isrURL[1]}` : ""), req.url).href, req ); } } req.runtime ??= { name: "vercel" }; req.runtime.vercel = { context }; let ip: string | undefined; Object.defineProperty(req, "ip", { get() { const h = req.headers.get("x-forwarded-for"); return (ip ??= h?.split(",").shift()?.trim()); }, }); req.waitUntil = context?.waitUntil; return nitroApp.fetch(req); }, }; ================================================ FILE: src/presets/vercel/types.ts ================================================ /** * Vercel Build Output Configuration * @see https://vercel.com/docs/build-output-api/v3 */ export interface VercelBuildConfigV3 { version: 3; routes?: ( | { src: string; dest?: string; headers?: Record; continue?: boolean; status?: number; } | { handle: string; } )[]; images?: { sizes: number[]; domains: string[]; remotePatterns?: { protocol?: "http" | "https"; hostname: string; port?: string; pathname?: string; }[]; minimumCacheTTL?: number; formats?: ("image/avif" | "image/webp")[]; dangerouslyAllowSVG?: boolean; contentSecurityPolicy?: string; }; wildcard?: Array<{ domain: string; value: string; }>; overrides?: Record< string, { path?: string; contentType?: string; } >; cache?: string[]; bypassToken?: string; framework?: { version: string; }; crons?: { path: string; schedule: string; }[]; } /** * https://vercel.com/docs/build-output-api/primitives#serverless-function-configuration * https://vercel.com/docs/build-output-api/primitives#node.js-config */ export interface VercelServerlessFunctionConfig { /** * Amount of memory (RAM in MB) that will be allocated to the Serverless Function. */ memory?: number; /** * Specifies the instruction set "architecture" the Vercel Function supports. * * Either `x86_64` or `arm64`. The default value is `x86_64` */ architecture?: "x86_64" | "arm64"; /** * Maximum execution duration (in seconds) that will be allowed for the Serverless Function. */ maxDuration?: number; /** * Map of additional environment variables that will be available to the Vercel Function, * in addition to the env vars specified in the Project Settings. */ environment?: Record; /** * List of Vercel Regions where the Vercel Function will be deployed to. */ regions?: string[]; /** * True if a custom runtime has support for Lambda runtime wrappers. */ supportsWrapper?: boolean; /** * When true, the Serverless Function will stream the response to the client. */ supportsResponseStreaming?: boolean; /** * Enables source map generation. */ shouldAddSourcemapSupport?: boolean; /** * The runtime to use. Defaults to the auto-detected Node.js version. */ runtime?: "nodejs20.x" | "nodejs22.x" | "bun1.x" | (string & {}); [key: string]: unknown; } export interface VercelOptions { config?: VercelBuildConfigV3; /** * If you have enabled skew protection in the Vercel dashboard, it will * be enabled by default. * * You can disable the Nitro integration by setting this option to `false`. */ skewProtection?: boolean; /** * If you are using `vercel-edge`, you can specify the region(s) for your edge function. * @see https://vercel.com/docs/concepts/functions/edge-functions#edge-function-regions */ regions?: string[]; functions?: VercelServerlessFunctionConfig; /** * Handler format to use for Vercel Serverless Functions. * * Using `node` format enables compatibility with Node.js specific APIs in your Nitro application (e.g., `req.runtime.node`). * * Possible values are: `web` (default) and `node`. */ entryFormat?: "web" | "node"; /** * The route path for the Vercel cron handler endpoint. * * When `experimental.tasks` and `scheduledTasks` are configured, * Nitro registers a cron handler at this path that Vercel invokes * on each scheduled cron trigger. * * @default "/_vercel/cron" * @see https://vercel.com/docs/cron-jobs */ cronHandlerRoute?: string; } /** * https://vercel.com/docs/build-output-api/v3/primitives#prerender-configuration-file */ export type PrerenderFunctionConfig = { /** * Expiration time (in seconds) before the cached asset will be re-generated by invoking the Serverless Function. Setting the value to `false` means it will never expire. */ expiration: number | false; /** * Option group number of the asset. Prerender assets with the same group number will all be re-validated at the same time. */ group?: number; /** Random token assigned to the `__prerender_bypass` cookie when Draft Mode is enabled, in order to safely bypass the Edge Network cache */ bypassToken?: string; /** * Name of the optional fallback file relative to the configuration file. */ fallback?: string; /** * List of query string parameter names that will be cached independently. If an empty array, query values are not considered for caching. If undefined each unique query value is cached independently */ allowQuery?: string[]; /** * When `true`, the query string will be present on the `request` argument passed to the invoked function. The `allowQuery` filter still applies. */ passQuery?: boolean; /** * (vercel) * * When `true`, expose the response body regardless of status code including error status codes. (default `false`) */ exposeErrBody?: boolean; }; ================================================ FILE: src/presets/vercel/utils.ts ================================================ import fsp from "node:fs/promises"; import { defu } from "defu"; import { writeFile } from "../_utils/fs.ts"; import type { Nitro, NitroRouteRules } from "nitro/types"; import { dirname, relative, resolve } from "pathe"; import { joinURL, withLeadingSlash, withoutLeadingSlash } from "ufo"; import type { PrerenderFunctionConfig, VercelBuildConfigV3, VercelServerlessFunctionConfig, } from "./types.ts"; import { isTest } from "std-env"; import { ISR_URL_PARAM } from "./runtime/isr.ts"; // https://vercel.com/docs/build-output-api/configuration // https://vercel.com/docs/functions/runtimes/node-js/node-js-versions const SUPPORTED_NODE_VERSIONS = [20, 22, 24]; // h3 ProxyOptions that Vercel CDN rewrites cannot handle at the edge. // https://vercel.com/docs/rewrites const UNSUPPORTED_PROXY_OPTIONS = [ "headers", // headers added to the outgoing request to the upstream "forwardHeaders", "filterHeaders", "fetchOptions", "cookieDomainRewrite", "cookiePathRewrite", "onResponse", ] as const; const FALLBACK_ROUTE = "/__server"; const ISR_SUFFIX = "-isr"; // Avoid using . as it can conflict with routing const SAFE_FS_CHAR_RE = /[^a-zA-Z0-9_.[\]/]/g; function getSystemNodeVersion() { const systemNodeVersion = Number.parseInt(process.versions.node.split(".")[0]); return Number.isNaN(systemNodeVersion) ? 22 : systemNodeVersion; } export async function generateFunctionFiles(nitro: Nitro) { const o11Routes = getObservabilityRoutes(nitro); const buildConfigPath = resolve(nitro.options.output.dir, "config.json"); const buildConfig = generateBuildConfig(nitro, o11Routes); await writeFile(buildConfigPath, JSON.stringify(buildConfig, null, 2)); const functionConfigPath = resolve(nitro.options.output.serverDir, ".vc-config.json"); const functionConfig: VercelServerlessFunctionConfig = { handler: "index.mjs", launcherType: "Nodejs", shouldAddHelpers: false, supportsResponseStreaming: true, ...nitro.options.vercel?.functions, }; await writeFile(functionConfigPath, JSON.stringify(functionConfig, null, 2)); // Write ISR functions for (const [key, value] of Object.entries(nitro.options.routeRules)) { if (!value.isr) { continue; } const funcPrefix = resolve( nitro.options.output.serverDir, "..", normalizeRouteDest(key) + ISR_SUFFIX ); await fsp.mkdir(dirname(funcPrefix), { recursive: true }); await fsp.symlink( "./" + relative(dirname(funcPrefix), nitro.options.output.serverDir), funcPrefix + ".func", "junction" ); await writePrerenderConfig( funcPrefix + ".prerender-config.json", value.isr, nitro.options.vercel?.config?.bypassToken ); } // Write observability routes if (o11Routes.length === 0) { return; } const _getRouteRules = (path: string) => defu({}, ...nitro.routing.routeRules.matchAll("", path).reverse()) as NitroRouteRules; for (const route of o11Routes) { const routeRules = _getRouteRules(route.src); if (routeRules.isr) { continue; // #3563 } const funcPrefix = resolve(nitro.options.output.serverDir, "..", route.dest); await fsp.mkdir(dirname(funcPrefix), { recursive: true }); await fsp.symlink( "./" + relative(dirname(funcPrefix), nitro.options.output.serverDir), funcPrefix + ".func", "junction" ); } } export async function generateEdgeFunctionFiles(nitro: Nitro) { const buildConfigPath = resolve(nitro.options.output.dir, "config.json"); const buildConfig = generateBuildConfig(nitro); await writeFile(buildConfigPath, JSON.stringify(buildConfig, null, 2)); const functionConfigPath = resolve(nitro.options.output.serverDir, ".vc-config.json"); const functionConfig = { runtime: "edge", entrypoint: "index.mjs", regions: nitro.options.vercel?.regions, }; await writeFile(functionConfigPath, JSON.stringify(functionConfig, null, 2)); } export async function generateStaticFiles(nitro: Nitro) { const buildConfigPath = resolve(nitro.options.output.dir, "config.json"); const buildConfig = generateBuildConfig(nitro); await writeFile(buildConfigPath, JSON.stringify(buildConfig, null, 2)); } function generateBuildConfig(nitro: Nitro, o11Routes?: ObservabilityRoute[]) { const rules = Object.entries(nitro.options.routeRules).sort( (a, b) => b[0].split(/\/(?!\*)/).length - a[0].split(/\/(?!\*)/).length ); // Determine which proxy rules can be offloaded to Vercel CDN rewrites const cdnProxyPaths = new Set( rules .filter(([_, routeRules]) => routeRules.proxy && canUseVercelRewrite(routeRules.proxy)) .map(([path]) => path) ); const config = defu(nitro.options.vercel?.config, { version: 3, framework: { name: nitro.options.framework.name, version: nitro.options.framework.version, }, overrides: { // Nitro static prerendered route overrides ...Object.fromEntries( (nitro._prerenderedRoutes?.filter((r) => r.fileName !== r.route) || []).map( ({ route, fileName }) => [ withoutLeadingSlash(fileName), { path: route.replace(/^\//, "") }, ] ) ), }, routes: [ // Redirect and header rules (excluding paths handled as CDN proxy rewrites) ...rules .filter( ([path, routeRules]) => (routeRules.redirect || routeRules.headers) && !cdnProxyPaths.has(path) ) .map(([path, routeRules]) => { let route = { src: path.replace("/**", "/(.*)"), }; if (routeRules.redirect) { route = defu(route, { status: routeRules.redirect.status, headers: { Location: routeRules.redirect.to.replace("/**", "/$1"), }, }); } if (routeRules.headers) { route = defu(route, { headers: routeRules.headers }); } return route; }), // Proxy rewrite rules (CDN-level reverse proxy) // https://vercel.com/docs/rewrites ...rules .filter(([path]) => cdnProxyPaths.has(path)) .map(([path, routeRules]) => { const proxy = routeRules.proxy!; const route: Record = { src: path.replace("/**", "/(.*)"), dest: proxy.to.replace("/**", "/$1"), }; if (routeRules.headers) { route.headers = routeRules.headers; } return route; }), // Skew protection ...(nitro.options.vercel?.skewProtection && nitro.options.manifest?.deploymentId ? [ { src: "/.*", has: [ { type: "header", key: "Sec-Fetch-Dest", value: "document", }, ], headers: { "Set-Cookie": `__vdpl=${nitro.options.manifest.deploymentId}; Path=${nitro.options.baseURL}; SameSite=Strict; Secure; HttpOnly`, }, continue: true, }, ] : []), // Public asset rules ...nitro.options.publicAssets .filter((asset) => !asset.fallthrough) .map((asset) => joinURL(nitro.options.baseURL, asset.baseURL || "/")) .map((baseURL) => ({ src: baseURL + "(.*)", headers: { "cache-control": "public,max-age=31536000,immutable", }, continue: true, })), { handle: "filesystem" }, ], } as VercelBuildConfigV3); // Cron jobs from scheduledTasks if ( nitro.options.experimental.tasks && Object.keys(nitro.options.scheduledTasks || {}).length > 0 ) { const cronPath = nitro.options.vercel!.cronHandlerRoute || "/_vercel/cron"; const cronEntries = Object.keys(nitro.options.scheduledTasks).map((schedule) => ({ path: cronPath, schedule, })); config.crons = [...cronEntries, ...(config.crons || [])]; } // Early return if we are building a static site if (nitro.options.static) { return config; } config.routes!.push( // ISR rules // ...If we are using an ISR function for /, then we need to write this explicitly ...(nitro.options.routeRules["/"]?.isr ? [ { src: `(?<${ISR_URL_PARAM}>/)`, dest: `/index${ISR_SUFFIX}?${ISR_URL_PARAM}=$${ISR_URL_PARAM}`, }, ] : []), // ...Add rest of the ISR routes ...rules .filter(([key, value]) => value.isr !== undefined && key !== "/") .map(([key, value]) => { const src = `(?<${ISR_URL_PARAM}>${normalizeRouteSrc(key)})`; if (value.isr === false) { // We need to write a rule to avoid route being shadowed by another cache rule elsewhere return { src, dest: FALLBACK_ROUTE, }; } return { src, dest: withLeadingSlash( normalizeRouteDest(key) + ISR_SUFFIX + `?${ISR_URL_PARAM}=$${ISR_URL_PARAM}` ), }; }), // Observability routes ...(o11Routes || []).map((route) => ({ src: joinURL(nitro.options.baseURL, route.src), dest: withLeadingSlash(route.dest), })), // If we are using an ISR function as a fallback // then we do not need to output the below fallback route as well ...(nitro.options.routeRules["/**"]?.isr ? [] : [ { src: "/(.*)", dest: FALLBACK_ROUTE, }, ]) ); return config; } export function deprecateSWR(nitro: Nitro) { if (nitro.options.future.nativeSWR) { return; } let hasLegacyOptions = false; for (const [_key, value] of Object.entries(nitro.options.routeRules)) { if (_hasProp(value, "isr")) { continue; } if (value.cache === false) { value.isr = false; } if (_hasProp(value, "static")) { value.isr = !(value as { static: boolean }).static; hasLegacyOptions = true; } if (value.cache && _hasProp(value.cache, "swr")) { value.isr = value.cache.swr; hasLegacyOptions = true; } } if (hasLegacyOptions && !isTest) { nitro.logger.warn( "Nitro now uses `isr` option to configure ISR behavior on Vercel. Backwards-compatible support for `static` and `swr` options within the Vercel Build Options API will be removed in the future versions. Set `future.nativeSWR: true` nitro config disable this warning." ); } } // --- vercel.json --- // https://vercel.com/docs/project-configuration // https://openapi.vercel.sh/vercel.json export interface VercelConfig { bunVersion?: string; } export async function resolveVercelRuntime(nitro: Nitro) { // 1. Respect explicit runtime from nitro config let runtime: VercelServerlessFunctionConfig["runtime"] = nitro.options.vercel?.functions?.runtime; if (runtime) { // Already specified return runtime; } // 2. Read runtime from vercel.json if specified const vercelConfig = await readVercelConfig(nitro.options.rootDir); // 3. Use bun runtime if bunVersion is specified or bun used to build if (vercelConfig.bunVersion || "Bun" in globalThis) { runtime = "bun1.x"; } else { // 3. Auto-detect runtime based on system Node.js version const systemNodeVersion = getSystemNodeVersion(); const usedNodeVersion = SUPPORTED_NODE_VERSIONS.find((version) => version >= systemNodeVersion) ?? SUPPORTED_NODE_VERSIONS.at(-1); runtime = `nodejs${usedNodeVersion}.x`; } // Synchronize back to nitro config nitro.options.vercel ??= {} as any; nitro.options.vercel!.functions ??= {} as any; nitro.options.vercel!.functions!.runtime = runtime; return runtime; } export async function readVercelConfig(rootDir: string): Promise { const vercelConfigPath = resolve(rootDir, "vercel.json"); const vercelConfig = await fsp .readFile(vercelConfigPath) .then((config) => JSON.parse(config.toString())) .catch(() => ({})); return vercelConfig as VercelConfig; } function _hasProp(obj: any, prop: string) { return obj && typeof obj === "object" && prop in obj; } /** * Check if a proxy rule can be offloaded to a Vercel CDN rewrite. * A proxy is eligible when it targets an external URL and uses no * ProxyOptions that Vercel's routing layer cannot handle at the edge. */ function canUseVercelRewrite(proxy: NitroRouteRules["proxy"]): proxy is { to: string } { if (!proxy?.to) { return false; } // Must be an external URL if (!/^https?:\/\//.test(proxy.to.replace(/\/\*\*$/, ""))) { return false; } // Must not use any ProxyOptions unsupported by Vercel rewrites for (const key of UNSUPPORTED_PROXY_OPTIONS) { if ((proxy as any)[key] !== undefined) { return false; } } return true; } // --- utils for observability --- type ObservabilityRoute = { src: string; // route pattern dest: string; // function name }; function getObservabilityRoutes(nitro: Nitro): ObservabilityRoute[] { const compatDate = nitro.options.compatibilityDate.vercel || nitro.options.compatibilityDate.default; if (compatDate < "2025-07-15") { return []; } // Sort routes by how much specific they are const routePatterns = [ ...new Set([ ...(nitro.options.ssrRoutes || []), ...[...nitro.scannedHandlers, ...nitro.options.handlers] .filter((h) => !h.middleware && h.route) .map((h) => h.route!), ]), ]; const staticRoutes: string[] = []; const dynamicRoutes: string[] = []; const catchAllRoutes: string[] = []; for (const route of routePatterns) { if (route.includes("**")) { catchAllRoutes.push(route); } else if (route.includes(":") || route.includes("*")) { dynamicRoutes.push(route); } else { staticRoutes.push(route); } } return [ ...normalizeRoutes(staticRoutes), ...normalizeRoutes(dynamicRoutes), ...normalizeRoutes(catchAllRoutes), ]; } function normalizeRoutes(routes: string[]) { return routes .sort((a, b) => // a.split("/").length - b.split("/").length || b.localeCompare(a) ) .map((route) => ({ src: normalizeRouteSrc(route), dest: normalizeRouteDest(route), })); } // Input is a rou3/radix3 compatible route pattern // Output is a PCRE-compatible regular expression that matches each incoming pathname // Reference: https://github.com/h3js/rou3/blob/main/src/regexp.ts function normalizeRouteSrc(route: string): string { let idCtr = 0; return route .split("/") .map((segment) => { if (segment.startsWith("**")) { return segment === "**" ? "(?:.*)" : `?(?<${namedGroup(segment.slice(3))}>.+)`; } if (segment === "*") { return `(?<_${idCtr++}>[^/]*)`; } if (segment.includes(":")) { return segment .replace(/:(\w+)/g, (_, id) => `(?<${namedGroup(id)}>[^/]+)`) .replace(/\./g, String.raw`\.`); } return segment; }) .join("/"); } // Valid PCRE capture group name function namedGroup(input = "") { if (/\d/.test(input[0])) { input = `_${input}`; } return input.replace(/[^a-zA-Z0-9_]/g, "") || "_"; } // Output is a destination pathname to function name function normalizeRouteDest(route: string) { return ( route .split("/") .slice(1) .map((segment) => { if (segment.startsWith("**")) { return `[...${segment.replace(/[*:]/g, "")}]`; } if (segment === "*") { return "[-]"; } if (segment.startsWith(":")) { return `[${segment.slice(1)}]`; } if (segment.includes(":")) { return `[${segment.replace(/:/g, "_")}]`; } return segment; }) // Only use filesystem-safe characters .map((segment) => segment.replace(SAFE_FS_CHAR_RE, "-")) .join("/") || "index" ); } async function writePrerenderConfig( filename: string, isrConfig: NitroRouteRules["isr"], bypassToken?: string ) { // Normalize route rule if (typeof isrConfig === "number") { isrConfig = { expiration: isrConfig }; } else if (isrConfig === true) { isrConfig = { expiration: false }; } else { isrConfig = { ...isrConfig }; } // Generate prerender config const prerenderConfig: PrerenderFunctionConfig = { expiration: isrConfig.expiration ?? false, bypassToken, ...isrConfig, }; if (prerenderConfig.allowQuery && !prerenderConfig.allowQuery.includes(ISR_URL_PARAM)) { prerenderConfig.allowQuery.push(ISR_URL_PARAM); } await writeFile(filename, JSON.stringify(prerenderConfig, null, 2)); } ================================================ FILE: src/presets/winterjs/preset.ts ================================================ import { defineNitroPreset } from "../_utils/preset.ts"; const winterjs = defineNitroPreset( { extends: "base-worker", entry: "./winterjs/runtime/winterjs", minify: false, serveStatic: "inline", wasm: { lazy: true, }, commands: { preview: "wasmer run wasmer/winterjs --forward-host-env --net --mapdir app:./ app/server/index.mjs", }, }, { name: "winterjs" as const, } ); export default [winterjs] as const; ================================================ FILE: src/presets/winterjs/runtime/winterjs.ts ================================================ // @ts-nocheck TODO: Remove after removing polyfills import "#nitro/virtual/polyfills"; import { useNitroApp } from "nitro/app"; import { hasProtocol, joinURL } from "ufo"; // Types are reverse engineered from runtime interface ExecuteRequest { url: URL; body?: ReadableStream; bodyUsed: boolean; headers: Headers; getMethod: () => string; } interface FetchEvent extends Event { request: ExecuteRequest; respondWith(response: Promise | Response): Promise; waitUntil: (promise: Promise) => void; } const nitroApp = useNitroApp(); // Use plain handler as winterjs Web API is incomplete // TODO: Migrate to toWebHandler const _handler = toPlainHandler(nitroApp.h3App); async function _handleEvent(event: FetchEvent) { try { const res = await _handler({ path: event.request.url.pathname + (event.request.url.search ? `?${event.request.url.search}` : ""), method: event.request.getMethod() || "GET", body: event.request.body, headers: event.request.headers, context: { waitUntil: (promise: Promise) => event.waitUntil(promise), _platform: { winterjs: { event, }, }, }, }); const body = typeof res.body === "string" ? res.body : await toBuffer(res.body as any); return new Response(body, { status: res.status, statusText: res.statusText, headers: res.headers, }); } catch (error: unknown) { const errString = (error as Error)?.message + "\n" + (error as Error)?.stack; console.error(errString); return new Response(errString, { status: 500 }); } } addEventListener("fetch" as any, async (event: FetchEvent) => { event.respondWith(await _handleEvent(event)); }); // ------------------------------ // Polyfills for missing APIs // ------------------------------ function toBuffer(data: ReadableStream): Promise { return new Promise((resolve, reject) => { const chunks: Buffer[] = []; data .pipeTo( new WritableStream({ write(chunk) { chunks.push(chunk); }, close() { resolve(Buffer.concat(chunks)); }, abort(reason) { reject(reason); }, }) ) .catch(reject); }); } // Headers.entries if (!Headers.prototype.entries) { // @ts-ignore Headers.prototype.entries = function () { return [...this]; }; } // URL.pathname if (!URL.prototype.pathname) { Object.defineProperty(URL.prototype, "pathname", { get() { return this.path || "/"; }, }); } // URL constructor (relative support) const _URL = globalThis.URL; globalThis.URL = class URL extends _URL { constructor(url: string | URL, base: string | URL) { if (!base || hasProtocol(url)) { super(url); return; } super(joinURL(base, url)); } }; // Response (avoid Promise body) const _Response = globalThis.Response; globalThis.Response = class Response extends _Response { _body: BodyInit; constructor(body, init) { super(body, init); this._body = body; } get body() { // TODO: Return ReadableStream (should be iterable) return this._body as any; } }; ================================================ FILE: src/presets/zeabur/preset.ts ================================================ import fsp from "node:fs/promises"; import { writeFile } from "../_utils/fs.ts"; import { defineNitroPreset } from "../_utils/preset.ts"; import type { Nitro } from "nitro/types"; import { dirname, relative, resolve } from "pathe"; // https://zeabur.com/docs/advanced/serverless-output-format const zeabur = defineNitroPreset( { entry: "./zeabur/runtime/zeabur", output: { dir: "{{ rootDir }}/.zeabur/output", serverDir: "{{ output.dir }}/functions/__nitro.func", publicDir: "{{ output.dir }}/static", }, hooks: { async compiled(nitro: Nitro) { const buildConfigPath = resolve(nitro.options.output.dir, "config.json"); const cfg = { containerized: false, routes: [{ src: ".*", dest: "/__nitro" }], }; await writeFile(buildConfigPath, JSON.stringify(cfg, null, 2)); // Write ISR functions for (const [key, value] of Object.entries(nitro.options.routeRules)) { if (!value.isr) { continue; } const funcPrefix = resolve(nitro.options.output.serverDir, ".." + key); await fsp.mkdir(dirname(funcPrefix), { recursive: true }); await fsp.symlink( "./" + relative(dirname(funcPrefix), nitro.options.output.serverDir), funcPrefix + ".func", "junction" ); await writeFile( funcPrefix + ".prerender-config.json", JSON.stringify({ type: "Prerender" }) ); } }, }, }, { name: "zeabur" as const, stdName: "zeabur", } ); const zeaburStatic = defineNitroPreset( { extends: "static", output: { dir: "{{ rootDir }}/.zeabur/output", publicDir: "{{ output.dir }}/static", }, commands: { preview: "npx serve ./static", }, }, { name: "zeabur-static" as const, static: true, } ); export default [zeabur, zeaburStatic] as const; ================================================ FILE: src/presets/zeabur/runtime/zeabur.ts ================================================ import "#nitro/virtual/polyfills"; import { toNodeHandler } from "srvx/node"; import { useNitroApp } from "nitro/app"; export default toNodeHandler(useNitroApp().fetch); ================================================ FILE: src/presets/zephyr/preset.ts ================================================ import { defineNitroPreset } from "../_utils/preset.ts"; import type { Nitro } from "nitro/types"; import { unenvCfExternals, unenvCfNodeCompat } from "../cloudflare/unenv/preset.ts"; import { resolve } from "pathe"; import { importDep } from "../../utils/dep.ts"; export type { ZephyrOptions as PresetOptions } from "./types.ts"; const LOGGER_TAG = "zephyr-nitro-preset"; type ZephyrAgentModule = Pick; const zephyr = defineNitroPreset( { extends: "base-worker", entry: "./zephyr/runtime/server", output: { publicDir: "{{ output.dir }}/client/{{ baseURL }}", }, exportConditions: ["node"], minify: false, rollupConfig: { output: { format: "esm", exports: "named", inlineDynamicImports: false, }, }, wasm: { lazy: false, esmImport: true, }, hooks: { "build:before": (nitro: Nitro) => { nitro.options.unenv.push(unenvCfExternals, unenvCfNodeCompat); }, compiled: async (nitro: Nitro) => { try { if (!(globalThis as any).__nitroDeploying__ && !nitro.options.zephyr?.deployOnBuild) { nitro.logger.info(`[${LOGGER_TAG}] Zephyr deploy skipped on build.`); return; } const zephyrAgent = await importDep({ id: "zephyr-agent", reason: "deploying to Zephyr", dir: nitro.options.rootDir, }); const { deploymentUrl } = await zephyrAgent.uploadOutputToZephyr({ rootDir: nitro.options.rootDir, outputDir: nitro.options.output.dir, baseURL: nitro.options.baseURL, publicDir: resolve(nitro.options.output.dir, nitro.options.output.publicDir), }); if (deploymentUrl) { nitro.logger.success(`[${LOGGER_TAG}] Zephyr deployment succeeded: ${deploymentUrl}`); } else { nitro.logger.success(`[${LOGGER_TAG}] Zephyr deployment succeeded.`); } (globalThis as any).__nitroDeployed__ = true; } catch (error) { if (error instanceof Error) { throw error; } throw new TypeError(`[${LOGGER_TAG}] ${String(error)}`); } }, }, }, { name: "zephyr" as const, } ); export default [zephyr] as const; ================================================ FILE: src/presets/zephyr/runtime/server.ts ================================================ import "#nitro/virtual/polyfills"; import { createHandler } from "../../cloudflare/runtime/_module-handler.ts"; export default createHandler({ fetch() {}, }); ================================================ FILE: src/presets/zephyr/types.ts ================================================ export interface ZephyrOptions { /** * Deploy to Zephyr during `nitro build` when using the `zephyr` preset. * * @default false */ deployOnBuild?: boolean; } ================================================ FILE: src/presets/zerops/preset.ts ================================================ import { defineNitroPreset } from "../_utils/preset.ts"; const zerops = defineNitroPreset( { extends: "node-server", serveStatic: true, }, { name: "zerops" as const, } ); const zeropsStatic = defineNitroPreset( { extends: "static", output: { dir: "{{ rootDir }}/.zerops/output", publicDir: "{{ output.dir }}/static", }, }, { name: "zerops-static" as const, static: true, } ); export default [zerops, zeropsStatic] as const; ================================================ FILE: src/preview.ts ================================================ import type { ServerHandler, ServerRequest } from "srvx"; import type { LoadOptions } from "srvx/loader"; import { spawn } from "node:child_process"; import consola from "consola"; import { join, resolve } from "pathe"; import { proxyFetch, proxyUpgrade, type ProxyUpgradeOptions } from "httpxy"; import { prettyPath } from "./utils/fs.ts"; import { getBuildInfo } from "./build/info.ts"; import type { IncomingMessage } from "node:http"; import type { Duplex } from "node:stream"; export interface PreviewInstance { fetch: ServerHandler; upgrade?: ( req: IncomingMessage, socket: Duplex, head?: Buffer, opts?: ProxyUpgradeOptions ) => Promise; close: () => Promise; } export async function startPreview(opts: { rootDir: string; loader?: LoadOptions; }): Promise { const { outputDir, buildInfo } = await getBuildInfo(opts.rootDir); if (!buildInfo) { throw new Error("Cannot load nitro build info. Make sure to build first."); } const info = [ ["Build Directory:", prettyPath(outputDir)], ["Date:", buildInfo.date && new Date(buildInfo.date).toLocaleString()], ["Nitro Version:", buildInfo.versions.nitro], ["Nitro Preset:", buildInfo.preset], buildInfo.framework?.name !== "nitro" && [ "Framework:", buildInfo.framework?.name + (buildInfo.framework?.version ? ` (v${buildInfo.framework.version})` : ""), ], ].filter((i) => i && i[1]) as [string, string][]; consola.box({ title: " [Build Info] ", message: info.map((i) => `- ${i[0]} ${i[1]}`).join("\n"), }); // Load .env files for preview mode const dotEnvEntries = await loadPreviewDotEnv(opts.rootDir); if (dotEnvEntries.length > 0) { consola.box({ title: " [Environment Variables] ", message: [ "Loaded variables from .env files (preview mode only).", "Set platform environment variables for production:", ...dotEnvEntries.map(([key, val]) => ` - ${key}`), ].join("\n"), }); } // Currently cloudflare preset strictly requires preview command if (buildInfo.preset.includes("cloudflare")) { if (!buildInfo.commands?.preview) { throw new Error(`No nitro build preview command found for the "${buildInfo.preset}" preset.`); } return await runPreviewCommand({ command: buildInfo.commands.preview, rootDir: opts.rootDir, env: dotEnvEntries, }); } let fetchHandler: ServerHandler = () => Promise.resolve(new Response("Not Found", { status: 404 })); if (buildInfo.serverEntry) { for (const [key, val] of dotEnvEntries) { if (!process.env[key]) { process.env[key] = val; } } const { loadServerEntry } = await import("srvx/loader"); const entryPath = resolve(outputDir, buildInfo.serverEntry); const entry = await loadServerEntry({ entry: entryPath, ...opts.loader }); if (entry.fetch) { fetchHandler = entry.fetch; } } if (buildInfo.publicDir) { const { serveStatic } = await import("srvx/static"); const staticHandler = serveStatic({ dir: join(outputDir, buildInfo.publicDir) }); const originalFetchHandler = fetchHandler; fetchHandler = async (req) => { const staticRes: Response | undefined = await staticHandler(req, () => undefined as any); if (staticRes) { return staticRes; } return originalFetchHandler(req); }; } return { fetch: fetchHandler, async close() { // No-op for in-process preview for now }, }; } async function loadPreviewDotEnv(root: string): Promise<[string, string][]> { const { loadDotenv } = await import("c12"); const env = await loadDotenv({ cwd: root, fileName: [".env.preview", ".env.production", ".env"], }); return Object.entries(env).filter(([_key, val]) => val) as [string, string][]; } async function runPreviewCommand(opts: { command: string; rootDir: string; env: [string, string][]; }): Promise { const [arg0, ...args] = opts.command.split(" "); consola.info(`Spawning preview server...`); consola.info(opts.command); console.log(""); const { getRandomPort, waitForPort } = await import("get-port-please"); const randomPort = await getRandomPort(); const child = spawn(arg0, [...args, "--port", String(randomPort), "--host", "localhost"], { stdio: "inherit", cwd: opts.rootDir, env: { ...process.env, ...Object.fromEntries(opts.env ?? []), PORT: String(randomPort), }, }); const killChild = (signal: NodeJS.Signals) => { if (child && !child.killed) { child.kill(signal); } }; for (const sig of ["SIGINT", "SIGHUP"] as const) { process.once(sig, () => { killChild(sig); }); } child.once("exit", (code) => { if (code && code !== 0) { consola.error(`[nitro] Preview server exited with code ${code}`); } }); await waitForPort(randomPort, { retries: 20, delay: 500, host: "localhost" }); return { fetch(req: ServerRequest) { return proxyFetch({ port: randomPort, host: "localhost" }, req); }, async upgrade(req, socket, head, opts) { await proxyUpgrade({ port: randomPort, host: "localhost" }, req, socket, head, opts); }, async close() { killChild("SIGTERM"); }, }; } ================================================ FILE: src/routing.ts ================================================ import type { Nitro, NitroEventHandler, NitroRouteRules } from "nitro/types"; import type { RouterContext } from "rou3"; import type { RouterCompilerOptions } from "rou3/compiler"; import { join } from "pathe"; import { runtimeDir } from "nitro/meta"; import { addRoute, createRouter, findRoute, findAllRoutes } from "rou3"; import { compileRouterToString } from "rou3/compiler"; import { hash } from "ohash"; const isGlobalMiddleware = (h: NitroEventHandler) => !h.method && (!h.route || h.route === "/**"); export function initNitroRouting(nitro: Nitro) { const envConditions = new Set( [ nitro.options.dev ? "dev" : "prod", nitro.options.preset, nitro.options.preset === "nitro-prerender" ? "prerender" : undefined, ].filter(Boolean) as string[] ); const matchesEnv = (h: NitroEventHandler) => { const hEnv = Array.isArray(h.env) ? h.env : [h.env]; const envs = hEnv.filter(Boolean) as string[]; return envs.length === 0 || envs.some((env) => envConditions.has(env)); }; type MaybeArray = T | T[]; const routes = new Router>( nitro.options.baseURL ); const routeRules = new Router(nitro.options.baseURL); const globalMiddleware: (NitroEventHandler & { _importHash: string })[] = []; const routedMiddleware = new Router( nitro.options.baseURL ); const sync = () => { // Update route rules routeRules._update( Object.entries(nitro.options.routeRules).map(([route, data]) => ({ route, method: "", data: { ...data, _route: route, }, })) ); // Update routes const _routes = [ ...Object.entries(nitro.options.routes).flatMap(([route, handler]) => { if (typeof handler === "string") { handler = { handler }; } return { ...handler, route, middleware: false }; }), ...nitro.options.handlers, ...nitro.scannedHandlers, ].filter((h) => h && !h.middleware && matchesEnv(h)); if (nitro.options.serverEntry && nitro.options.serverEntry.handler) { _routes.push({ route: "/**", lazy: false, format: nitro.options.serverEntry.format, handler: nitro.options.serverEntry.handler, }); } if (nitro.options.renderer?.handler) { _routes.push({ route: "/**", lazy: true, handler: nitro.options.renderer?.handler, }); } routes._update( _routes.map((h) => ({ ...h, method: h.method || "", data: handlerWithImportHash(h), })), { merge: true } ); // Update middleware const _middleware = [...nitro.scannedHandlers, ...nitro.options.handlers].filter( (h) => h && h.middleware && matchesEnv(h) ); if (nitro.options.serveStatic) { _middleware.unshift({ route: "/**", middleware: true, handler: join(runtimeDir, "internal/static"), }); } globalMiddleware.splice( 0, globalMiddleware.length, ..._middleware.filter((h) => isGlobalMiddleware(h)).map((m) => handlerWithImportHash(m)) ); routedMiddleware._update( _middleware .filter((h) => !isGlobalMiddleware(h)) .map((h) => ({ ...h, method: h.method || "", data: handlerWithImportHash(h), })) ); }; nitro.routing = Object.freeze({ sync, routes, routeRules, globalMiddleware, routedMiddleware, }); } function handlerWithImportHash(h: NitroEventHandler) { const id = (h.lazy ? "_lazy_" : "_") + hash(h.handler).replace(/-/g, "").slice(0, 6); return { ...h, _importHash: id }; } // --- Router --- export interface Route { route: string; method: string; data: T; } export class Router { _routes?: Route[]; _router?: RouterContext; _compiled?: Record; _baseURL: string; constructor(baseURL?: string) { this._update([]); this._baseURL = baseURL || ""; if (this._baseURL.endsWith("/")) { this._baseURL = this._baseURL.slice(0, -1); } } get routes() { return this._routes!; } _update(routes: Route[], opts?: { merge?: boolean }) { this._routes = routes; this._router = createRouter(); this._compiled = undefined; for (const route of routes) { addRoute(this._router, route.method, this._baseURL + route.route, route.data); } if (opts?.merge) { mergeCatchAll(this._router); } } hasRoutes() { return this._routes!.length > 0; } compileToString(opts?: RouterCompilerOptions) { const key = opts ? hash(opts) : ""; this._compiled ||= {}; if (this._compiled[key]) { return this._compiled[key]; } this._compiled[key] = compileRouterToString(this._router!, undefined, opts); // TODO: Upstream to rou3 compiler const onlyWildcard = this.routes.length === 1 && this.routes[0].route === "/**" && this.routes[0].method === ""; if (onlyWildcard) { // Optimize for single wildcard route const data = (opts?.serialize || JSON.stringify)(this.routes[0].data); let retCode = `{data,params:{"_":p.slice(1)}}`; if (opts?.matchAll) { retCode = `[${retCode}]`; } this._compiled[key] = /* js */ `/* @__PURE__ */ (() => {const data=${data};return ((_m, p)=>{return ${retCode};})})()`; } return this._compiled[key]; } match(method: string, path: string): undefined | T { return findRoute(this._router!, method, path)?.data; } matchAll(method: string, path: string): T[] { // Returns from less specific to more specific matches return findAllRoutes(this._router!, method, path).map((route) => route.data); } } function mergeCatchAll(router: RouterContext) { const handlers = router.root?.wildcard?.methods?.[""]; if (!handlers || handlers.length < 2) { return; } handlers.splice(0, handlers.length, { ...handlers[0], data: handlers.map((h) => h.data), }); } ================================================ FILE: src/runtime/app.ts ================================================ export { useNitroApp, useNitroHooks, serverFetch, getRouteRules, fetch } from "./internal/app.ts"; ================================================ FILE: src/runtime/cache.ts ================================================ export { defineCachedFunction, defineCachedHandler } from "./internal/cache.ts"; ================================================ FILE: src/runtime/config.ts ================================================ import type { NitroConfig } from "nitro/types"; export function defineConfig(config: Omit): Omit { return config; } export { defineConfig as defineNitroConfig }; ================================================ FILE: src/runtime/context.ts ================================================ export { useRequest } from "./internal/context.ts"; ================================================ FILE: src/runtime/database.ts ================================================ export { useDatabase } from "./internal/database.ts"; ================================================ FILE: src/runtime/internal/app.ts ================================================ import type { CaptureError, MatchedRouteRules, NitroApp, NitroAsyncContext, NitroRuntimeHooks, } from "nitro/types"; import type { ServerRequest, ServerRequestContext } from "srvx"; import type { H3Config, H3EventContext, Middleware, WebSocketHooks } from "h3"; import { H3Core, toRequest } from "h3"; import { HookableCore } from "hookable"; import { nitroAsyncContext } from "./context.ts"; // IMPORTANT: virtual imports and user code should be imported last to avoid initialization order issues import errorHandler from "#nitro/virtual/error-handler"; import { plugins } from "#nitro/virtual/plugins"; import { findRoute, findRouteRules, globalMiddleware, findRoutedMiddleware, } from "#nitro/virtual/routing"; import { hasRouteRules, hasRoutedMiddleware, hasGlobalMiddleware, hasRoutes, hasHooks, hasPlugins, } from "#nitro/virtual/feature-flags"; declare global { var __nitro__: | Partial> | undefined; } const APP_ID = import.meta.prerender ? "prerender" : "default"; export function useNitroApp(): NitroApp { let instance: NitroApp | undefined = (useNitroApp as any)._instance; if (instance) { return instance; } instance = (useNitroApp as any)._instance = createNitroApp(); globalThis.__nitro__ = globalThis.__nitro__ || {}; globalThis.__nitro__[APP_ID] = instance; if (hasPlugins) { initNitroPlugins(instance); } return instance; } export function useNitroHooks(): HookableCore { const nitroApp = useNitroApp(); const hooks = nitroApp.hooks; if (hooks) { return hooks; } return (nitroApp.hooks = new HookableCore()); } export function serverFetch( resource: string | URL | Request, init?: RequestInit, context?: ServerRequestContext | H3EventContext ): Promise { const req = toRequest(resource, init); req.context = { ...req.context, ...context }; const appHandler = useNitroApp().fetch; try { return Promise.resolve(appHandler(req)); } catch (error) { return Promise.reject(error); } } export async function resolveWebsocketHooks(req: ServerRequest): Promise> { // https://github.com/h3js/h3/blob/c11ca743d476e583b3b47de1717e6aae92114357/src/utils/ws.ts#L37 const hooks = ((await serverFetch(req)) as any).crossws as Partial; return hooks || {}; } export function fetch( resource: string | URL | Request, init?: RequestInit, context?: ServerRequestContext | H3EventContext ): Promise { if (typeof resource === "string" && resource.charCodeAt(0) === 47) { return serverFetch(resource, init, context); } resource = (resource as any)._request || resource; // unwrap srvx request return fetch(resource, init); } function createNitroApp(): NitroApp { const hooks = hasHooks ? new HookableCore() : undefined; const captureError: CaptureError = (error, errorCtx) => { const promise = hasHooks && hooks!.callHook("error", error, errorCtx)?.catch?.((hookError: any) => { console.error("Error while capturing another error", hookError); }); if (errorCtx?.event) { const errors = errorCtx.event.req.context?.nitro?.errors; if (errors) { errors.push({ error, context: errorCtx }); } if (hasHooks && promise && typeof errorCtx.event.req.waitUntil === "function") { errorCtx.event.req.waitUntil(promise); } } }; const h3App = createH3App({ onError(error, event) { hasHooks && captureError(error, { event }); return errorHandler(error, event); }, }); if (hasHooks) { h3App.config.onRequest = (event) => { return hooks!.callHook("request", event)?.catch?.((error: any) => { captureError(error, { event, tags: ["request"] }); }); }; h3App.config.onResponse = (res, event) => { return hooks!.callHook("response", res, event)?.catch?.((error: any) => { captureError(error, { event, tags: ["response"] }); }); }; } let appHandler = (req: ServerRequest): Response | Promise => { req.context ||= {}; req.context.nitro = req.context.nitro || { errors: [] }; return h3App.fetch(req); }; // Experimental async context support if (import.meta._asyncContext) { const originalHandler = appHandler; appHandler = (req: ServerRequest): Promise => { const asyncCtx: NitroAsyncContext = { request: req as Request }; return nitroAsyncContext.callAsync(asyncCtx, () => originalHandler(req)); }; } const app: NitroApp = { fetch: appHandler, h3: h3App, hooks, captureError, }; return app; } function initNitroPlugins(app: NitroApp) { for (const plugin of plugins) { try { plugin(app as NitroApp & { hooks: NonNullable }); } catch (error: any) { app.captureError?.(error, { tags: ["plugin"] }); throw error; } } return app; } function createH3App(config: H3Config) { // Create H3 app const h3App = new H3Core(config); // Compiled route matching hasRoutes && (h3App["~findRoute"] = (event) => findRoute(event.req.method, event.url.pathname)); hasGlobalMiddleware && h3App["~middleware"].push(...globalMiddleware); if (hasRouteRules || hasRoutedMiddleware) { h3App["~getMiddleware"] = (event, route) => { const needsRouting = hasRouteRules || hasRoutedMiddleware; const pathname = needsRouting ? event.url.pathname : undefined; const method = needsRouting ? event.req.method : undefined; const middleware: Middleware[] = []; if (hasRouteRules) { const routeRules = getRouteRules(method!, pathname!); event.context.routeRules = routeRules?.routeRules; if (routeRules?.routeRuleMiddleware.length) { middleware.push(...routeRules.routeRuleMiddleware); } } hasGlobalMiddleware && middleware.push(...h3App["~middleware"]); hasRoutedMiddleware && middleware.push(...findRoutedMiddleware(method!, pathname!).map((r) => r.data)); if (hasRoutes && route?.data?.middleware?.length) { middleware.push(...route.data.middleware); } return middleware; }; } return h3App; } export function getRouteRules( method: string, pathname: string ): { routeRules?: MatchedRouteRules; routeRuleMiddleware: Middleware[]; } { const m = findRouteRules(method, pathname); if (!m?.length) { return { routeRuleMiddleware: [] }; } const routeRules: MatchedRouteRules = {}; for (const layer of m) { for (const rule of layer.data) { const currentRule = routeRules[rule.name]; if (currentRule) { if (rule.options === false) { // Remove/Reset existing rule with `false` value delete routeRules[rule.name]; continue; } if (typeof currentRule.options === "object" && typeof rule.options === "object") { // Merge nested rule objects currentRule.options = { ...currentRule.options, ...rule.options }; } else { // Override rule if non object currentRule.options = rule.options; } // Routing (route and params) currentRule.route = rule.route; currentRule.params = { ...currentRule.params, ...layer.params }; } else if (rule.options !== false) { routeRules[rule.name] = { ...rule, params: layer.params }; } } } const middleware = []; for (const rule of Object.values(routeRules)) { if (rule.options === false || !rule.handler) { continue; } middleware.push(rule.handler(rule)); } return { routeRules, routeRuleMiddleware: middleware, }; } ================================================ FILE: src/runtime/internal/cache.ts ================================================ import { defineHandler, handleCacheHeaders, toResponse } from "h3"; import { FastResponse } from "srvx"; import { defineCachedFunction as _defineCachedFunction, defineCachedHandler as _defineCachedHandler, setStorage, } from "ocache"; import { useNitroApp } from "./app.ts"; import { useStorage } from "./storage.ts"; import type { EventHandler, H3Event } from "h3"; import type { CacheOptions, CachedEventHandlerOptions } from "nitro/types"; let _storageReady = false; function ensureStorage() { if (_storageReady) { return; } _storageReady = true; const storage = useStorage(); setStorage({ get: (key) => storage.getItem(key) as any, set: (key, value, opts) => storage.setItem(key, value as any, opts?.ttl ? { ttl: opts.ttl } : undefined), }); } function defaultOnError(error: unknown) { console.error("[cache]", error); useNitroApp().captureError?.(error as Error, { tags: ["cache"] }); } export function defineCachedFunction( fn: (...args: ArgsT) => T | Promise, opts: CacheOptions = {} ): (...args: ArgsT) => Promise { ensureStorage(); return _defineCachedFunction(fn, { group: "nitro/functions", onError: defaultOnError, ...opts, }); } export function defineCachedHandler( handler: EventHandler, opts: CachedEventHandlerOptions = {} ): EventHandler { ensureStorage(); const ocacheHandler = _defineCachedHandler(handler as any, { group: "nitro/handlers", onError: defaultOnError, toResponse: (value, event) => toResponse(value, event as H3Event), createResponse: (body, init) => new FastResponse(body, init), handleCacheHeaders: (event, conditions) => handleCacheHeaders(event as H3Event, conditions), ...opts, }); return defineHandler((event) => ocacheHandler(event as any)); } ================================================ FILE: src/runtime/internal/context.ts ================================================ import type { NitroAsyncContext } from "nitro/types"; import type { ServerRequest } from "srvx"; import { AsyncLocalStorage } from "node:async_hooks"; import { HTTPError } from "h3"; import { getContext, type UseContext } from "unctx"; export const nitroAsyncContext: UseContext = /* @__PURE__ */ (() => getContext("nitro-app", { asyncContext: import.meta._asyncContext, AsyncLocalStorage: import.meta._asyncContext ? AsyncLocalStorage : undefined, }))(); /** * * Access to the current Nitro request. * * @experimental * - Requires `experimental.asyncContext: true` config to work. * - Works in Node.js and limited runtimes only * */ export function useRequest(): ServerRequest { try { return nitroAsyncContext.use().request; } catch { const hint = import.meta._asyncContext ? "Note: This is an experimental feature and might be broken on non-Node.js environments." : "Enable the experimental flag using `experimental.asyncContext: true`."; throw new HTTPError({ message: `Nitro request context is not available. ${hint}`, }); } } ================================================ FILE: src/runtime/internal/database.ts ================================================ import type { Database } from "db0"; import { createDatabase } from "db0"; import { connectionConfigs } from "#nitro/virtual/database"; const instances: Record = Object.create(null); export function useDatabase(name = "default"): Database { if (instances[name]) { return instances[name]; } if (!connectionConfigs[name]) { throw new Error(`Database connection "${name}" not configured.`); } return (instances[name] = createDatabase( connectionConfigs[name].connector(connectionConfigs[name].options || {}) )); } ================================================ FILE: src/runtime/internal/empty.ts ================================================ ================================================ FILE: src/runtime/internal/error/dev.ts ================================================ import { HTTPError, type HTTPEvent } from "h3"; import { getRequestURL } from "h3"; import { readFile } from "node:fs/promises"; import { resolve, dirname } from "node:path"; import consola from "consola"; import type { ErrorParser } from "youch-core"; import { defineNitroErrorHandler } from "./utils.ts"; import type { InternalHandlerResponse } from "./utils.ts"; import { FastResponse } from "srvx"; import type { NitroErrorHandler } from "nitro/types"; const errorHandler: NitroErrorHandler = defineNitroErrorHandler( async function defaultNitroErrorHandler(error, event) { const res = await defaultHandler(error, event); return new FastResponse( typeof res.body === "string" ? res.body : JSON.stringify(res.body, null, 2), res ); } ); export default errorHandler; export async function defaultHandler( error: HTTPError, event: HTTPEvent, opts?: { silent?: boolean; json?: boolean } ): Promise { const unhandled = error.unhandled ?? !HTTPError.isError(error); const { status = 500, statusText = "" } = unhandled ? {} : error; const url = getRequestURL(event, { xForwardedHost: true, xForwardedProto: true }); // Redirects with base URL if (status === 404) { const baseURL = import.meta.baseURL || "/"; if (/^\/[^/]/.test(baseURL) && !url.pathname.startsWith(baseURL)) { return { status: 302, statusText: "Found", headers: new Headers({ location: `${baseURL}${url.pathname.slice(1)}${url.search}` }), body: `Redirecting...`, }; } } // Load stack trace with source maps await loadStackTrace(error).catch(consola.error); const { Youch } = await import("youch"); // https://github.com/poppinss/youch const youch = new Youch(); // Console output if (unhandled && !opts?.silent) { const ansiError = (await youch.toANSI(error)).replaceAll(process.cwd(), "."); consola.error(`[request error] [${event.req.method}] ${url}\n\n`, ansiError); } // Use HTML response only when user-agent expects it (browsers) const useJSON = opts?.json ?? !event.req.headers.get("accept")?.includes("text/html"); const headers = new Headers(unhandled ? {} : error.headers); if (useJSON) { headers.set("Content-Type", "application/json; charset=utf-8"); const jsonBody = typeof error.toJSON === "function" ? error.toJSON() : { status, statusText, message: error.message }; return { status, statusText, headers, body: { error: true, stack: error.stack?.split("\n").map((line) => line.trim()), ...jsonBody, }, }; } // HTML response headers.set("Content-Type", "text/html; charset=utf-8"); return { status, statusText: unhandled ? "" : error.statusText, headers, body: await youch.toHTML(error, { request: { url: url.href, method: event.req.method, headers: Object.fromEntries(event.req.headers.entries()), }, }), }; } // ---- Source Map support ---- export async function loadStackTrace(error: any): Promise { if (!(error instanceof Error)) { return; } const { ErrorParser } = await import("youch-core"); const parsed = await new ErrorParser().defineSourceLoader(sourceLoader).parse(error); const stack = error.message + "\n" + parsed.frames.map((frame) => fmtFrame(frame)).join("\n"); Object.defineProperty(error, "stack", { value: stack }); if (error.cause) { await loadStackTrace(error.cause).catch(consola.error); } } type SourceLoader = Parameters[0]; type StackFrame = Parameters[0]; async function sourceLoader(frame: StackFrame) { if (!frame.fileName || frame.fileType !== "fs" || frame.type === "native") { return; } if (frame.type === "app") { // prettier-ignore const rawSourceMap = await readFile(`${frame.fileName}.map`, "utf8").catch(() => {}); if (rawSourceMap) { const { SourceMapConsumer } = await import("source-map"); const consumer = await new SourceMapConsumer(rawSourceMap); // prettier-ignore const originalPosition = consumer.originalPositionFor({ line: frame.lineNumber!, column: frame.columnNumber! }); if (originalPosition.source && originalPosition.line) { // prettier-ignore frame.fileName = resolve(dirname(frame.fileName), originalPosition.source); frame.lineNumber = originalPosition.line; frame.columnNumber = originalPosition.column || 0; } } } const contents = await readFile(frame.fileName, "utf8").catch(() => {}); return contents ? { contents } : undefined; } function fmtFrame(frame: StackFrame) { if (frame.type === "native") { return frame.raw; } const src = `${frame.fileName || ""}:${frame.lineNumber}:${frame.columnNumber})`; return frame.functionName ? `at ${frame.functionName} (${src}` : `at ${src}`; } ================================================ FILE: src/runtime/internal/error/hooks.ts ================================================ import { useNitroApp } from "../app.ts"; function _captureError(error: Error, type: string): void { console.error(`[${type}]`, error); useNitroApp().captureError?.(error, { tags: [type] }); } export function trapUnhandledErrors(): void { process.on("unhandledRejection", (error: Error) => _captureError(error, "unhandledRejection")); process.on("uncaughtException", (error: Error) => _captureError(error, "uncaughtException")); } ================================================ FILE: src/runtime/internal/error/prod.ts ================================================ import { HTTPError, type H3Event, type HTTPEvent } from "h3"; import type { InternalHandlerResponse } from "./utils.ts"; import { FastResponse } from "srvx"; import type { NitroErrorHandler } from "nitro/types"; const errorHandler: NitroErrorHandler = (error, event) => { const res = defaultHandler(error, event); return new FastResponse( typeof res.body === "string" ? res.body : JSON.stringify(res.body, null, 2), res ); }; export default errorHandler; export function defaultHandler(error: HTTPError, event: HTTPEvent): InternalHandlerResponse { const unhandled = error.unhandled ?? !HTTPError.isError(error); const { status = 500, statusText = "" } = unhandled ? {} : error; if (status === 404) { const url = (event as H3Event).url || new URL(event.req.url); const baseURL = import.meta.baseURL || "/"; if (/^\/[^/]/.test(baseURL) && !url.pathname.startsWith(baseURL)) { return { status: 302, headers: new Headers({ location: `${baseURL}${url.pathname.slice(1)}${url.search}` }), }; } } const headers = new Headers(unhandled ? {} : error.headers); headers.set("content-type", "application/json; charset=utf-8"); const jsonBody = unhandled ? { status, unhandled: true } : typeof error.toJSON === "function" ? error.toJSON() : { status, statusText, message: error.message }; return { status, statusText, headers, body: { error: true, ...jsonBody, }, }; } ================================================ FILE: src/runtime/internal/error/utils.ts ================================================ import type { NitroErrorHandler } from "nitro/types"; export function defineNitroErrorHandler(handler: NitroErrorHandler): NitroErrorHandler { return handler; } export type InternalHandlerResponse = { status?: number; statusText?: string | undefined; headers?: HeadersInit; body?: string | Record; }; ================================================ FILE: src/runtime/internal/meta.ts ================================================ import type { NitroRouteMeta } from "nitro/types"; export function defineRouteMeta(meta: NitroRouteMeta): NitroRouteMeta { return meta; } ================================================ FILE: src/runtime/internal/plugin.ts ================================================ import type { NitroAppPlugin } from "nitro/types"; export function defineNitroPlugin(def: NitroAppPlugin): NitroAppPlugin { return def; } export const nitroPlugin: (def: NitroAppPlugin) => NitroAppPlugin = defineNitroPlugin; ================================================ FILE: src/runtime/internal/route-rules.ts ================================================ import { proxyRequest, redirect as sendRedirect, requireBasicAuth } from "h3"; import type { BasicAuthOptions, EventHandler, Middleware } from "h3"; import type { MatchedRouteRule, NitroRouteRules } from "nitro/types"; import { joinURL, withQuery, withoutBase } from "ufo"; import { defineCachedHandler } from "./cache.ts"; // Note: Remember to update RuntimeRouteRules in src/build/virtual/routing.ts when adding new route rules type RouteRuleCtor = (m: MatchedRouteRule) => Middleware; // Headers route rule export const headers: RouteRuleCtor<"headers"> = ((m) => function headersRouteRule(event) { for (const [key, value] of Object.entries(m.options || {})) { event.res.headers.set(key, value); } }) satisfies RouteRuleCtor<"headers">; // Redirect route rule export const redirect: RouteRuleCtor<"redirect"> = ((m) => function redirectRouteRule(event) { let target = m.options?.to; if (!target) { return; } if (target.endsWith("/**")) { let targetPath = event.url.pathname + event.url.search; const strpBase = (m.options as any)._redirectStripBase; if (strpBase) { targetPath = withoutBase(targetPath, strpBase); } target = joinURL(target.slice(0, -3), targetPath); } else if (event.url.search) { target = withQuery(target, Object.fromEntries(event.url.searchParams)); } return sendRedirect(target, m.options?.status); }) satisfies RouteRuleCtor<"redirect">; // Proxy route rule export const proxy: RouteRuleCtor<"proxy"> = ((m) => function proxyRouteRule(event) { let target = m.options?.to; if (!target) { return; } if (target.endsWith("/**")) { let targetPath = event.url.pathname + event.url.search; const strpBase = (m.options as any)._proxyStripBase; if (strpBase) { targetPath = withoutBase(targetPath, strpBase); } target = joinURL(target.slice(0, -3), targetPath); } else if (event.url.search) { target = withQuery(target, Object.fromEntries(event.url.searchParams)); } return proxyRequest(event, target, { ...m.options, }); }) satisfies RouteRuleCtor<"proxy">; // Cache route rule export const cache: RouteRuleCtor<"cache"> = ((m) => function cacheRouteRule(event, next) { if (!event.context.matchedRoute) { return next(); } const cachedHandlers: Map = ((globalThis as any).__nitroCachedHandlers ??= new Map()); const { handler, route } = event.context.matchedRoute; const key = `${m.route}:${route}`; let cachedHandler = cachedHandlers.get(key); if (!cachedHandler) { cachedHandler = defineCachedHandler(handler, { group: "nitro/route-rules", name: key, ...m.options, }); cachedHandlers.set(key, cachedHandler); } return cachedHandler(event); }) satisfies RouteRuleCtor<"cache">; // basicAuth auth route rule export const basicAuth: RouteRuleCtor<"auth"> = ((m) => async function authRouteRule(event, next) { if (!m.options) { return; } await requireBasicAuth(event, m.options as BasicAuthOptions); return next(); }) satisfies RouteRuleCtor<"auth">; ================================================ FILE: src/runtime/internal/routes/dev-tasks.ts ================================================ import { H3 } from "h3"; import { runTask } from "../task.ts"; import { scheduledTasks, tasks } from "#nitro/virtual/tasks"; const app: H3 = new H3() .get("/_nitro/tasks", async () => { const _tasks = await Promise.all( Object.entries(tasks).map(async ([name, task]) => { const _task = await task.resolve?.(); return [name, { description: _task?.meta?.description }]; }) ); return { tasks: Object.fromEntries(_tasks), scheduledTasks, }; }) .get("/_nitro/tasks/:name", async (event) => { const name = event.context.params?.name; const body = (await event.req.json().catch(() => ({}))) as Record; const payload = { ...Object.fromEntries(event.url.searchParams.entries()), ...body, }; return await runTask(name!, { context: { waitUntil: event.req.waitUntil }, payload, }); }); export default app; ================================================ FILE: src/runtime/internal/routes/openapi.ts ================================================ import { defineHandler, getRequestURL } from "h3"; import type { EventHandler, HTTPMethod } from "h3"; import type { Extensable, OpenAPI3, OperationObject, ParameterObject, PathItemObject, PathsObject, } from "../../../types/openapi-ts.ts"; import { joinURL } from "ufo"; import { defu } from "defu"; import { handlersMeta } from "#nitro/virtual/routing-meta"; import { useRuntimeConfig } from "../runtime-config.ts"; // Served as /_openapi.json export default defineHandler((event) => { const runtimeConfig = useRuntimeConfig(); const base = runtimeConfig.app?.baseURL; const url = joinURL(getRequestURL(event).origin, base); const meta = { title: "Nitro Server Routes", ...runtimeConfig.nitro?.openAPI?.meta, }; const { paths, globals: { components, ...globalsRest }, } = getHandlersMeta(); const extensible: Extensable = Object.fromEntries( Object.entries(globalsRest).filter(([key]) => key.startsWith("x-")) ); return { openapi: "3.1.0", info: { title: meta?.title, version: meta?.version || "1.0.0", description: meta?.description, }, servers: [ { url, description: "Local Development Server", variables: {}, }, ], paths, components, ...extensible, } satisfies OpenAPI3; }) as EventHandler; type OpenAPIGlobals = Pick & Extensable; function getHandlersMeta(): { paths: PathsObject; globals: OpenAPIGlobals; } { const paths: PathsObject = {}; let globals: OpenAPIGlobals = {}; for (const h of handlersMeta) { const { route, parameters } = normalizeRoute(h.route || ""); const tags = defaultTags(h.route || ""); const method = (h.method || "get").toLowerCase() as Lowercase; const { $global, ...openAPI } = h.meta?.openAPI || {}; const item: PathItemObject = { [method]: { tags, parameters, responses: { 200: { description: "OK" }, }, ...openAPI, } satisfies OperationObject, }; if ($global) { // TODO: Warn on conflicting global definitions? globals = defu($global, globals); } if (paths[route] === undefined) { paths[route] = item; } else { Object.assign(paths[route], item); } } return { paths, globals }; } function normalizeRoute(_route: string) { const parameters: ParameterObject[] = []; let anonymousCtr = 0; const route = _route .replace(/:(\w+)/g, (_, name) => `{${name}}`) .replace(/\/(\*)\//g, () => `/{param${++anonymousCtr}}/`) .replace(/\*\*{/, "{") .replace(/\/(\*\*)$/g, () => `/{*param${++anonymousCtr}}`); const paramMatches = route.matchAll(/{(\*?\w+)}/g); for (const match of paramMatches) { const name = match[1]; if (!parameters.some((p) => p.name === name)) { parameters.push({ name, in: "path", required: true, schema: { type: "string" }, }); } } return { route, parameters, }; } function defaultTags(route: string) { const tags: string[] = []; if (route.startsWith("/api/")) { tags.push("API Routes"); } else if (route.startsWith("/_")) { tags.push("Internal"); } else { tags.push("App Routes"); } return tags; } ================================================ FILE: src/runtime/internal/routes/renderer-template.dev.ts ================================================ import type { H3Event } from "h3"; import { serverFetch } from "../app.ts"; import { rendererTemplate, rendererTemplateFile, isStaticTemplate, } from "#nitro/virtual/renderer-template"; import { HTTPResponse } from "h3"; import { hasTemplateSyntax, renderToResponse, compileTemplate } from "rendu"; export default async function renderIndexHTML(event: H3Event): Promise { let html = await rendererTemplate(event.req as Request); if ((globalThis as any).__transform_html__) { html = await (globalThis as any).__transform_html__(html); } const isStatic = isStaticTemplate ?? !hasTemplateSyntax(html); if (isStatic) { return new HTTPResponse(html, { headers: { "content-type": "text/html; charset=utf-8" }, }); } const template = compileTemplate(html, { filename: rendererTemplateFile }); return renderToResponse(template, { request: event.req as Request, context: { serverFetch }, }); } ================================================ FILE: src/runtime/internal/routes/renderer-template.ts ================================================ import type { H3Event } from "h3"; import { rendererTemplate } from "#nitro/virtual/renderer-template"; export default function renderIndexHTML(event: H3Event): any { return rendererTemplate(event.req as Request); } ================================================ FILE: src/runtime/internal/routes/scalar.ts ================================================ import type { ApiReferenceConfiguration } from "@scalar/api-reference"; import { defineHandler, type EventHandler } from "h3"; import { useRuntimeConfig } from "../runtime-config.ts"; // Served as /_scalar export default defineHandler((event) => { const runtimeConfig = useRuntimeConfig(); const title = runtimeConfig.nitro?.openAPI?.meta?.title || "API Reference"; const description = runtimeConfig.nitro?.openAPI?.meta?.description || ""; const openAPIEndpoint = runtimeConfig.nitro?.openAPI?.route || "./_openapi.json"; // https://github.com/scalar/scalar const _config = runtimeConfig.nitro?.openAPI?.ui?.scalar as ApiReferenceConfiguration; const scalarConfig: ApiReferenceConfiguration = { ..._config, url: openAPIEndpoint, // @ts-expect-error spec: { url: openAPIEndpoint, ..._config?.spec }, }; // The default page title event.res.headers.set("Content-Type", "text/html"); return /* html */ ` ${title} `; }) as EventHandler; const customTheme = /* css */ `/* basic theme */ .light-mode, .light-mode .dark-mode { --theme-background-1: #fff; --theme-background-2: #fafafa; --theme-background-3: rgb(245 245 245); --theme-color-1: #2a2f45; --theme-color-2: #757575; --theme-color-3: #8e8e8e; --theme-color-accent: #ef4444; --theme-background-accent: transparent; --theme-border-color: rgba(0, 0, 0, 0.1); } .dark-mode { --theme-background-1: #171717; --theme-background-2: #262626; --theme-background-3: #2e2e2e; --theme-color-1: rgba(255, 255, 255, 0.9); --theme-color-2: rgba(255, 255, 255, 0.62); --theme-color-3: rgba(255, 255, 255, 0.44); --theme-color-accent: #f87171; --theme-background-accent: transparent; --theme-border-color: rgba(255, 255, 255, 0.1); } /* Document Sidebar */ .light-mode .t-doc__sidebar, .dark-mode .t-doc__sidebar { --sidebar-background-1: var(--theme-background-1); --sidebar-color-1: var(--theme-color-1); --sidebar-color-2: var(--theme-color-2); --sidebar-border-color: var(--theme-border-color); --sidebar-item-hover-background: transparent; --sidebar-item-hover-color: var(--sidebar-color-1); --sidebar-item-active-background: var(--theme-background-accent); --sidebar-color-active: var(--theme-color-accent); --sidebar-search-background: transparent; --sidebar-search-color: var(--theme-color-3); --sidebar-search-border-color: var(--theme-border-color); } /* advanced */ .light-mode .dark-mode, .light-mode { --theme-color-green: #91b859; --theme-color-red: #e53935; --theme-color-yellow: #e2931d; --theme-color-blue: #6182b8; --theme-color-orange: #f76d47; --theme-color-purple: #9c3eda; } .dark-mode { --theme-color-green: #c3e88d; --theme-color-red: #f07178; --theme-color-yellow: #ffcb6b; --theme-color-blue: #82aaff; --theme-color-orange: #f78c6c; --theme-color-purple: #c792ea; } /* custom-theme */ .section-container:nth-of-type(2) ~ .section-container .scalar-card .scalar-card-header { --theme-background-2: var(--theme-background-1) !important; } .section-flare { width: 100%; height: 100%; position: absolute; top: 0; left: 0; animation: spin 39s linear infinite; transition: all 0.3s ease-in-out; opacity: 1; } .section-flare-item:nth-of-type(1), .section-flare-item:nth-of-type(2), .section-flare-item:nth-of-type(3) { content: ""; width: 1000px; height: 1000px; position: absolute; top: 0; right: 0; border-radius: 50%; background: var(--default-theme-background-1); display: block; opacity: 1; filter: blur(48px); -webkit-backface-visibility: hidden; -webkit-perspective: 1000; -webkit-transform: translate3d(0, 0, 0); -webkit-transform: translateZ(0); perspective: 1000; transform: translate3d(0, 0, 0); transform: translateZ(0); } .section-flare-item:nth-of-type(2) { top: initial; right: initial; background: #ef4444; width: 700px; height: 700px; opacity: 0.3; animation: sectionflare 37s linear infinite; } .section-flare-item:nth-of-type(1) { top: initial; right: initial; bottom: 0; left: -20px; background: #ef4444; width: 500px; height: 500px; opacity: 0.3; animation: sectionflare2 38s linear infinite; } @keyframes sectionflare { 0%, 100% { transform: translate3d(0, 0, 0); } 50% { transform: translate3d(525px, 525px, 0); } } @keyframes sectionflare2 { 0%, 100% { transform: translate3d(700px, 700px, 0); } 50% { transform: translate3d(0, 0, 0); } } @keyframes spin { 100% { transform: rotate(360deg); } } .section-container:nth-of-type(2) { overflow: hidden; }`; ================================================ FILE: src/runtime/internal/routes/swagger.ts ================================================ import { defineHandler } from "h3"; import type { EventHandler } from "h3"; import { useRuntimeConfig } from "../runtime-config.ts"; // https://github.com/swagger-api/swagger-ui export default defineHandler((event) => { const runtimeConfig = useRuntimeConfig(); const title = runtimeConfig.nitro?.openAPI?.meta?.title || "API Reference"; const description = runtimeConfig.nitro?.openAPI?.meta?.description || ""; const openAPIEndpoint = runtimeConfig.nitro?.openAPI?.route || "./_openapi.json"; const CDN_BASE = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@^5"; event.res.headers.set("Content-Type", "text/html"); return /* html */ ` ${title}

    `; }) as EventHandler; ================================================ FILE: src/runtime/internal/runtime-config.ts ================================================ import type { NitroRuntimeConfig } from "nitro/types"; import { snakeCase } from "scule"; import { runtimeConfig } from "#nitro/virtual/runtime-config"; export function useRuntimeConfig(): NitroRuntimeConfig { return ((useRuntimeConfig as any)._cached ||= getRuntimeConfig()); } function getRuntimeConfig() { const env = globalThis.process?.env || {}; applyEnv(runtimeConfig, { prefix: "NITRO_", altPrefix: runtimeConfig.nitro?.envPrefix ?? env?.NITRO_ENV_PREFIX ?? "_", envExpansion: Boolean(runtimeConfig.nitro?.envExpansion ?? env?.NITRO_ENV_EXPANSION ?? false), }); return runtimeConfig; } // ---- utils ---- type EnvOptions = { prefix?: string; altPrefix?: string; envExpansion?: boolean; }; export function applyEnv( obj: Record, opts: EnvOptions, parentKey = "" ): Record { for (const key in obj) { const subKey = parentKey ? `${parentKey}_${key}` : key; const envValue = getEnv(subKey, opts); if (_isObject(obj[key])) { // Same as before if (_isObject(envValue)) { obj[key] = { ...(obj[key] as any), ...(envValue as any) }; applyEnv(obj[key], opts, subKey); } // If envValue is undefined // Then proceed to nested properties else if (envValue === undefined) { applyEnv(obj[key], opts, subKey); } // If envValue is a primitive other than undefined // Then set objValue and ignore the nested properties else { obj[key] = envValue ?? obj[key]; } } else { obj[key] = envValue ?? obj[key]; } // Experimental env expansion if (opts.envExpansion && typeof obj[key] === "string") { obj[key] = _expandFromEnv(obj[key]); } } return obj; } const envExpandRx = /\{\{([^{}]*)\}\}/g; function _expandFromEnv(value: string) { return value.replace(envExpandRx, (match, key) => { return process.env[key] || match; }); } function getEnv(key: string, opts: EnvOptions) { const envKey = snakeCase(key).toUpperCase(); return process.env[opts.prefix + envKey] ?? process.env[opts.altPrefix + envKey]; } function _isObject(input: unknown) { return input !== null && typeof input === "object" && !Array.isArray(input); } ================================================ FILE: src/runtime/internal/static.ts ================================================ import { HTTPError, defineHandler } from "h3"; import type { EventHandler, HTTPMethod } from "h3"; import type { PublicAsset } from "nitro/types"; import { decodePath, joinURL, withLeadingSlash, withoutTrailingSlash } from "ufo"; import { getAsset, isPublicAssetURL, readAsset } from "#nitro/virtual/public-assets"; const METHODS = new Set(["HEAD", "GET"] as HTTPMethod[]); const EncodingMap = { gzip: ".gz", br: ".br", zstd: ".zst" } as const; export default defineHandler((event) => { if (event.req.method && !METHODS.has(event.req.method as HTTPMethod)) { return; } let id = decodePath(withLeadingSlash(withoutTrailingSlash(event.url.pathname))); let asset: PublicAsset | undefined; const encodingHeader = event.req.headers.get("accept-encoding") || ""; const encodings = [ ...encodingHeader .split(",") .map((e) => EncodingMap[e.trim() as keyof typeof EncodingMap]) .filter(Boolean) .sort(), "", ]; for (const encoding of encodings) { for (const _id of [id + encoding, joinURL(id, "index.html" + encoding)]) { const _asset = getAsset(_id); if (_asset) { asset = _asset; id = _id; break; } } } if (!asset) { if (isPublicAssetURL(id)) { event.res.headers.delete("Cache-Control"); throw new HTTPError({ status: 404 }); } return; } if (encodings.length > 1) { event.res.headers.append("Vary", "Accept-Encoding"); } const ifNotMatch = event.req.headers.get("if-none-match") === asset.etag; if (ifNotMatch) { event.res.status = 304; event.res.statusText = "Not Modified"; return ""; } const ifModifiedSinceH = event.req.headers.get("if-modified-since"); const mtimeDate = new Date(asset.mtime); if (ifModifiedSinceH && asset.mtime && new Date(ifModifiedSinceH) >= mtimeDate) { event.res.status = 304; event.res.statusText = "Not Modified"; return ""; } if (asset.type) { event.res.headers.set("Content-Type", asset.type); } if (asset.etag && !event.res.headers.has("ETag")) { event.res.headers.set("ETag", asset.etag); } if (asset.mtime && !event.res.headers.has("Last-Modified")) { event.res.headers.set("Last-Modified", mtimeDate.toUTCString()); } if (asset.encoding && !event.res.headers.has("Content-Encoding")) { event.res.headers.set("Content-Encoding", asset.encoding); } if (asset.size > 0 && !event.res.headers.has("Content-Length")) { event.res.headers.set("Content-Length", asset.size.toString()); } return readAsset(id); }) as EventHandler; ================================================ FILE: src/runtime/internal/storage.ts ================================================ import type { Storage, StorageValue } from "unstorage"; import { prefixStorage } from "unstorage"; import { initStorage } from "#nitro/virtual/storage"; export function useStorage(base = ""): Storage { const storage = ((useStorage as any)._storage ??= initStorage()); return (base ? prefixStorage(storage, base) : storage) as unknown as Storage; } ================================================ FILE: src/runtime/internal/task.ts ================================================ import { Cron } from "croner"; import { HTTPError } from "h3"; import type { Task, TaskContext, TaskEvent, TaskPayload, TaskResult } from "nitro/types"; import { scheduledTasks, tasks } from "#nitro/virtual/tasks"; /** @experimental */ export function defineTask(def: Task): Task { if (typeof def.run !== "function") { def.run = () => { throw new TypeError("Task must implement a `run` method!"); }; } return def; } const __runningTasks__: { [name: string]: ReturnType["run"]> } = {}; /** @experimental */ export async function runTask( name: string, { payload = {}, context = {} }: { payload?: TaskPayload; context?: TaskContext } = {} ): Promise> { if (__runningTasks__[name]) { return __runningTasks__[name]; } if (!(name in tasks)) { throw new HTTPError({ message: `Task \`${name}\` is not available!`, status: 404, }); } if (!tasks[name].resolve) { throw new HTTPError({ message: `Task \`${name}\` is not implemented!`, status: 501, }); } const handler = (await tasks[name].resolve!()) as Task; const taskEvent: TaskEvent = { name, payload, context }; __runningTasks__[name] = handler.run(taskEvent); try { const res = await __runningTasks__[name]; return res; } finally { delete __runningTasks__[name]; } } /** @experimental */ export function startScheduleRunner({ waitUntil, }: { waitUntil?: ((promise: Promise) => void) | undefined; } = {}): void { if (!scheduledTasks || scheduledTasks.length === 0 || process.env.TEST) { return; } const payload: TaskPayload = { scheduledTime: Date.now(), }; for (const schedule of scheduledTasks) { new Cron(schedule.cron, async () => { await Promise.all( schedule.tasks.map((name) => runTask(name, { payload, context: { waitUntil }, }).catch((error) => { console.error(`Error while running scheduled task "${name}"`, error); }) ) ); }); } } /** @experimental */ export function getCronTasks(cron: string): string[] { return (scheduledTasks || []).find((task) => task.cron === cron)?.tasks || []; } /** @experimental */ export function runCronTasks( cron: string, ctx: { payload?: TaskPayload; context?: TaskContext } ): Promise { return Promise.all(getCronTasks(cron).map((name) => runTask(name, ctx))); } ================================================ FILE: src/runtime/internal/vite/dev-entry.mjs ================================================ import "#nitro/virtual/polyfills"; import wsAdapter from "crossws/adapters/node"; import { useNitroApp } from "nitro/app"; import { resolveWebsocketHooks } from "#nitro/runtime/app"; import { startScheduleRunner } from "#nitro/runtime/task"; const nitroApp = useNitroApp(); export const fetch = nitroApp.fetch; const ws = import.meta._websocket ? wsAdapter({ resolve: resolveWebsocketHooks }) : undefined; if (import.meta._tasks) { startScheduleRunner({}); } export const handleUpgrade = ws?.handleUpgrade; ================================================ FILE: src/runtime/internal/vite/dev-worker.mjs ================================================ import { ModuleRunner, ESModulesEvaluator } from "vite/module-runner"; import { createViteTransport } from "env-runner/vite"; // Custom evaluator for workerd where `new AsyncFunction()` is disallowed. // Uses the unsafeEvalBinding exposed by the env-runner miniflare wrapper. class WorkerdModuleEvaluator { startOffset = 0; async runInlinedModule(context, code) { const unsafeEval = globalThis.__ENV_RUNNER_UNSAFE_EVAL__; const keys = Object.keys(context); const fn = unsafeEval.newAsyncFunction('"use strict";' + code, "runInlinedModule", ...keys); await fn(...keys.map((k) => context[k])); Object.seal(context[Object.keys(context)[0]]); } runExternalModule(filepath) { return import(filepath); } } // ----- IPC ----- let sendMessage; const messageListeners = new Set(); // ----- Environment runners ----- const envs = (globalThis.__nitro_vite_envs__ ??= { nitro: undefined, ssr: undefined, }); class ViteEnvRunner { constructor({ name, entry }) { this.name = name; this.entryPath = entry; this.entry = undefined; this.entryError = undefined; // Create Vite Module Runner // https://vite.dev/guide/api-environment-runtimes.html#modulerunner const onMessage = (listener) => messageListeners.add(listener); const transport = createViteTransport((data) => sendMessage?.(data), onMessage, name); const evaluator = globalThis.__ENV_RUNNER_UNSAFE_EVAL__ ? new WorkerdModuleEvaluator() : new ESModulesEvaluator(); const debug = typeof process !== "undefined" && process.env?.NITRO_DEBUG ? console.debug : undefined; this.runner = new ModuleRunner({ transport }, evaluator, debug); this.reload(); } async reload() { try { this.entry = await this.runner.import(this.entryPath); this.entryError = undefined; } catch (error) { console.error(error); this.entryError = error; } } async fetch(req, init) { if (this.entryError) { return renderError(req, this.entryError); } for (let i = 0; i < 5 && !(this.entry || this.entryError); i++) { await new Promise((r) => setTimeout(r, 100 * Math.pow(2, i))); } if (this.entryError) { return renderError(req, this.entryError); } if (!this.entry) { throw httpError(503, `Vite environment "${this.name}" is unavailable`); } try { const entryFetch = this.entry.fetch || this.entry.default?.fetch; if (!entryFetch) { throw httpError(500, `No fetch handler exported from ${this.entryPath}`); } return await entryFetch(req, init); } catch (error) { return renderError(req, error); } } } // ----- RPC ----- const rpcRequests = new Map(); function rpc(name, data, timeout = 3000) { const id = Math.random().toString(36).slice(2); return new Promise((resolve, reject) => { const timer = setTimeout(() => { rpcRequests.delete(id); reject(new Error(`RPC "${name}" timed out`)); }, timeout); rpcRequests.set(id, { resolve, reject, timer }); sendMessage?.({ __rpc: name, __rpc_id: id, data }); }); } // Trap unhandled errors to avoid worker crash if (typeof process !== "undefined" && typeof process.on === "function") { process.on("unhandledRejection", (error) => console.error(error)); process.on("uncaughtException", (error) => console.error(error)); } // ----- RSC Support ----- // define __VITE_ENVIRONMENT_RUNNER_IMPORT__ for RSC support // https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-rsc/README.md#__vite_environment_runner_import__ globalThis.__VITE_ENVIRONMENT_RUNNER_IMPORT__ = async function (environmentName, id) { const env = envs[environmentName]; if (!env) { throw new Error(`Vite environment "${environmentName}" is not registered`); } return env.runner.import(id); }; // ----- Reload ----- async function reload() { try { await Promise.all(Object.values(envs).map((env) => env?.reload())); } catch (error) { console.error(error); } } // eslint-disable-next-line unicorn/prefer-top-level-await reload(); // ----- HTML Transform ----- globalThis.__transform_html__ = async function (html) { html = await rpc("transformHTML", html).catch((error) => { console.warn("Failed to transform HTML via Vite:", error); return html; }); return html; }; // ----- Exports (env-runner AppEntry) ----- export function fetch(req) { const viteEnv = req?.headers.get("x-vite-env") || "nitro"; const env = envs[viteEnv]; if (!env) { return renderError(req, httpError(500, `Unknown vite environment "${viteEnv}"`)); } return env.fetch(req); } export function upgrade(context) { const handleUpgrade = envs.nitro?.entry?.handleUpgrade; if (handleUpgrade) { handleUpgrade(context.node.req, context.node.socket, context.node.head); } } export const ipc = { onOpen(ctx) { sendMessage = ctx.sendMessage; }, onMessage(message) { if (message?.__rpc_id) { const req = rpcRequests.get(message.__rpc_id); if (req) { clearTimeout(req.timer); rpcRequests.delete(message.__rpc_id); if (message.error) { req.reject(typeof message.error === "string" ? new Error(message.error) : message.error); } else { req.resolve(message.data); } } return; } if (message?.type === "custom") { if (message.event === "nitro:vite-env") { const { name, entry } = message.data; if (!envs[name]) { envs[name] = new ViteEnvRunner({ name, entry }); } return; } } if (message?.type === "full-reload") { reload(); return; } for (const listener of messageListeners) { listener(message); } }, onClose() {}, }; // ----- Error handling ----- function httpError(status, message) { const error = new Error(message || `HTTP Error ${status}`); error.status = status; error.name = "NitroViteError"; return error; } async function renderError(req, error) { if (req.headers.get("accept")?.includes("application/json")) { return new Response( JSON.stringify( { status: error.status || 500, name: error.name || "Error", message: error.message, stack: (error.stack || "") .split("\n") .splice(1) .map((l) => l.trim()), }, null, 2 ), { status: error.status || 500, headers: { "Content-Type": "application/json", "Cache-Control": "no-store, max-age=0, must-revalidate", Pragma: "no-cache", Expires: "0", }, } ); } try { const { Youch } = await import("youch"); const youch = new Youch(); return new Response(await youch.toHTML(error), { status: error.status || 500, headers: { "Content-Type": "text/html", "Cache-Control": "no-store, max-age=0, must-revalidate", Pragma: "no-cache", Expires: "0", }, }); } catch { return new Response(`
    ${error.stack || error.message || error}
    `, { status: error.status || 500, headers: { "Content-Type": "text/html", "Cache-Control": "no-store, max-age=0, must-revalidate", Pragma: "no-cache", Expires: "0", }, }); } } ================================================ FILE: src/runtime/internal/vite/ssr-renderer.mjs ================================================ import { fetchViteEnv } from "nitro/vite/runtime"; /** @param {{ req: Request }} HTTPEvent */ export default function ssrRenderer({ req }) { return fetchViteEnv("ssr", req); } ================================================ FILE: src/runtime/meta.ts ================================================ import { fileURLToPath } from "node:url"; import packageJson from "../../package.json" with { type: "json" }; export const version: string = packageJson.version; const resolve = (path: string) => fileURLToPath(new URL(path, import.meta.url)); export const runtimeDir: string = /* @__PURE__ */ resolve("./"); export const presetsDir: string = /* @__PURE__ */ resolve("../presets/"); export const pkgDir: string = /* @__PURE__ */ resolve("../../"); export const runtimeDependencies: string[] = [ "crossws", // dep "croner", // traced "db0", // dep "defu", // traced "destr", // traced "h3", // dep "rou3", // sub-dep of h3 "hookable", // traced "ofetch", // dep "ocache", // dep "ohash", // traced "rendu", // traced "scule", // traced "srvx", // dep "ufo", // traced "unctx", // traced "unenv", // dep "unstorage", // dep ]; ================================================ FILE: src/runtime/nitro.ts ================================================ // Config import type { NitroConfig } from "nitro/types"; import type { ServerRequestContext } from "srvx"; import { toRequest, type H3EventContext } from "h3"; export function defineConfig(config: Omit): Omit { return config; } // Type (only) helpers export { defineNitroPlugin as definePlugin } from "./internal/plugin.ts"; export { defineRouteMeta } from "./internal/meta.ts"; export { defineNitroErrorHandler as defineErrorHandler } from "./internal/error/utils.ts"; // H3 export { defineHandler, defineMiddleware, defineWebSocketHandler, html, HTTPError, HTTPResponse, } from "h3"; export type { H3Event } from "h3"; // Runtime export function serverFetch( resource: string | URL | Request, init?: RequestInit, context?: ServerRequestContext | H3EventContext ): Promise { const nitro = globalThis.__nitro__?.default || globalThis.__nitro__?.prerender || globalThis.__nitro_builder__; if (!nitro) { return Promise.reject(new Error("Nitro instance is not available.")); } const req = toRequest(resource, init); req.context = { ...req.context, ...context }; try { return Promise.resolve(nitro.fetch(req)); } catch (error) { return Promise.reject(error); } } export function fetch( resource: string | URL | Request, init?: RequestInit, context?: ServerRequestContext | H3EventContext ): Promise { if (typeof resource === "string" && resource.charCodeAt(0) === 47) { return serverFetch(resource, init, context); } resource = (resource as any)._request || resource; // unwrap srvx request return globalThis.fetch(resource, init); } ================================================ FILE: src/runtime/runtime-config.ts ================================================ export { useRuntimeConfig } from "./internal/runtime-config.ts"; ================================================ FILE: src/runtime/storage.ts ================================================ export { useStorage } from "./internal/storage.ts"; ================================================ FILE: src/runtime/task.ts ================================================ export { defineTask, runTask } from "./internal/task.ts"; ================================================ FILE: src/runtime/virtual/_runtime_warn.ts ================================================ import consola from "consola"; import { isTest } from "std-env"; if (!isTest) { consola.warn( "Nitro runtime imports detected without a builder or Nitro plugin. A stub implementation will be used." ); } ================================================ FILE: src/runtime/virtual/database.ts ================================================ import "./_runtime_warn.ts"; import type { Connector } from "db0"; export const connectionConfigs: { [name: string]: { connector: (options: any) => Connector; options: any; }; } = {}; ================================================ FILE: src/runtime/virtual/error-handler.ts ================================================ import "./_runtime_warn.ts"; import { H3Event, toResponse } from "h3"; import type { NitroErrorHandler } from "nitro/types"; type EParams = Parameters; type EReturn = ReturnType; const errorHandler: (error: EParams[0], event: EParams[1]) => EReturn = (error, event) => { if (error.status !== 404) { console.error(error as any); } return toResponse(error, event as H3Event); }; export default errorHandler; ================================================ FILE: src/runtime/virtual/feature-flags.ts ================================================ import "./_runtime_warn.ts"; export const hasRoutes: boolean = true; export const hasRouteRules: boolean = true; export const hasGlobalMiddleware: boolean = true; export const hasRoutedMiddleware: boolean = true; export const hasPlugins: boolean = true; export const hasHooks: boolean = true; ================================================ FILE: src/runtime/virtual/plugins.ts ================================================ import "./_runtime_warn.ts"; import type { NitroAppPlugin } from "nitro/types"; export const plugins: NitroAppPlugin[] = []; ================================================ FILE: src/runtime/virtual/polyfills.ts ================================================ import "./_runtime_warn.ts"; export default {}; ================================================ FILE: src/runtime/virtual/public-assets.ts ================================================ import "./_runtime_warn.ts"; import type { PublicAsset } from "nitro/types"; export const publicAssetBases: string[] = []; export const isPublicAssetURL: (id: string) => boolean = () => false; export const getPublicAssetMeta: (id: string) => { maxAge?: number } | null = () => null; export const readAsset: (id: string) => Promise = async () => { throw new Error("Asset not found"); }; export const getAsset: (id: string) => PublicAsset | null = () => null; ================================================ FILE: src/runtime/virtual/renderer-template.ts ================================================ import "./_runtime_warn.ts"; export function rendererTemplate(_req: Request): string | Promise { return ``; } // dev only export const rendererTemplateFile: string | undefined = undefined; export const isStaticTemplate: boolean | undefined = undefined; ================================================ FILE: src/runtime/virtual/routing-meta.ts ================================================ import "./_runtime_warn.ts"; import type { NitroRouteMeta } from "nitro/types"; export const handlersMeta: { route?: string; method?: string; meta?: NitroRouteMeta; }[] = []; ================================================ FILE: src/runtime/virtual/routing.ts ================================================ import "./_runtime_warn.ts"; import type { Middleware, H3Route } from "h3"; import type { MatchedRoute } from "rou3"; import type { MatchedRouteRule } from "nitro/types"; export function findRoute(_method: string, _path: string): MatchedRoute | undefined { return undefined; } export function findRouteRules(_method: string, _path: string): MatchedRoute[] { return []; } export const globalMiddleware: Middleware[] = []; export function findRoutedMiddleware(_method: string, _path: string): MatchedRoute[] { return []; } ================================================ FILE: src/runtime/virtual/runtime-config.ts ================================================ import "./_runtime_warn.ts"; import type { NitroRuntimeConfig } from "nitro/types"; export const runtimeConfig: NitroRuntimeConfig = { app: {}, nitro: {}, }; ================================================ FILE: src/runtime/virtual/server-assets.ts ================================================ import "./_runtime_warn.ts"; import { createStorage, type Storage } from "unstorage"; import type { AssetMeta } from "nitro/types"; export const assets: Storage = createStorage(); export function readAsset(_id: string): Promise { return Promise.resolve({} as T); } export function statAsset(_id: string): Promise { return Promise.resolve({}); } export function getKeys(): Promise { return Promise.resolve([]); } ================================================ FILE: src/runtime/virtual/storage.ts ================================================ import "./_runtime_warn.ts"; import { type Storage, createStorage } from "unstorage"; export function initStorage(): Storage { return createStorage(); } ================================================ FILE: src/runtime/virtual/tasks.ts ================================================ import "./_runtime_warn.ts"; import type { Task, TaskMeta } from "nitro/types"; export const tasks: Record Promise; meta: TaskMeta }> = {}; export const scheduledTasks: false | { cron: string; tasks: string[] }[] = []; ================================================ FILE: src/runtime/vite.ts ================================================ import { HTTPError, toRequest } from "h3"; type FetchableEnv = { fetch: (request: Request) => Response | Promise; }; declare global { var __nitro_vite_envs__: Record; } export function fetchViteEnv( viteEnvName: string, input: RequestInfo | URL, init?: RequestInit ): Promise { const envs = globalThis.__nitro_vite_envs__ || {}; const viteEnv = envs[viteEnvName as keyof typeof envs] as FetchableEnv; if (!viteEnv) { throw HTTPError.status(404); } return Promise.resolve(viteEnv.fetch(toRequest(input, init))); } ================================================ FILE: src/scan.ts ================================================ import { glob } from "tinyglobby"; import type { Nitro } from "nitro/types"; import { join, relative } from "pathe"; import { withBase, withLeadingSlash, withoutTrailingSlash } from "ufo"; export const GLOB_SCAN_PATTERN = "**/*.{js,mjs,cjs,ts,mts,cts,tsx,jsx}"; type FileInfo = { path: string; fullPath: string }; const suffixRegex = /(\.(?connect|delete|get|head|options|patch|post|put|trace))?(\.(?dev|prod|prerender))?$/; // prettier-ignore type MatchedMethodSuffix = "connect" | "delete" | "get" | "head" | "options" | "patch" | "post" | "put" | "trace"; type MatchedEnvSuffix = "dev" | "prod" | "prerender"; export async function scanAndSyncOptions(nitro: Nitro) { // Scan plugins const scannedPlugins = await scanPlugins(nitro); for (const plugin of scannedPlugins) { if (!nitro.options.plugins.includes(plugin)) { nitro.options.plugins.push(plugin); } } // Scan tasks if (nitro.options.experimental.tasks) { const scannedTasks = await scanTasks(nitro); for (const scannedTask of scannedTasks) { if (scannedTask.name in nitro.options.tasks) { if (!nitro.options.tasks[scannedTask.name].handler) { nitro.options.tasks[scannedTask.name].handler = scannedTask.handler; } } else { nitro.options.tasks[scannedTask.name] = { handler: scannedTask.handler, description: "", }; } } } // Scan modules const scannedModules = await scanModules(nitro); nitro.options.modules = nitro.options.modules || []; for (const modPath of scannedModules) { if (!nitro.options.modules.includes(modPath)) { nitro.options.modules.push(modPath); } } } export async function scanHandlers(nitro: Nitro) { const middleware = await scanMiddleware(nitro); const handlers = await Promise.all([ scanServerRoutes(nitro, nitro.options.apiDir || "api", nitro.options.apiBaseURL || "/api"), scanServerRoutes(nitro, nitro.options.routesDir || "routes"), ]).then((r) => r.flat()); const seenHandlers = new Set(); nitro.scannedHandlers = [ ...middleware, ...handlers.filter((h) => { const key = `${h.route}\0${h.method}\0${h.env}`; return seenHandlers.has(key) ? false : (seenHandlers.add(key), true); }), ]; nitro.routing.sync(); return handlers; } export async function scanMiddleware(nitro: Nitro) { const files = await scanFiles(nitro, "middleware"); return files.map((file) => { return { route: "/**", middleware: true, handler: file.fullPath, }; }); } export async function scanServerRoutes(nitro: Nitro, dir: string, prefix = "/") { const files = await scanFiles(nitro, dir); return files.map((file) => { let route = file.path .replace(/\.[A-Za-z]+$/, "") .replace(/\(([^(/\\]+)\)[/\\]/g, "") .replace(/\[\.{3}]/g, "**") .replace(/\[\.{3}([^\]]+)]/g, (_, p) => "**:" + p.replace(/[^\w-]/g, "_")) .replace(/\[([^/\]]+)]/g, (_, p) => ":" + p.replace(/[^\w-]/g, "_")); route = withLeadingSlash(withoutTrailingSlash(withBase(route, prefix))); const suffixMatch = route.match(suffixRegex); let method: MatchedMethodSuffix | undefined; let env: MatchedEnvSuffix | undefined; if (suffixMatch?.index && suffixMatch?.index >= 0) { route = route.slice(0, suffixMatch.index); method = suffixMatch.groups?.method as MatchedMethodSuffix | undefined; env = suffixMatch.groups?.env as MatchedEnvSuffix | undefined; } route = route.replace(/\/index$/, "") || "/"; return { handler: file.fullPath, lazy: true, middleware: false, route, method, env, }; }); } export async function scanPlugins(nitro: Nitro) { const files = await scanFiles(nitro, "plugins"); return files.map((f) => f.fullPath); } export async function scanTasks(nitro: Nitro) { const files = await scanFiles(nitro, "tasks"); return files.map((f) => { const name = f.path .replace(/\/index$/, "") .replace(/\.[A-Za-z]+$/, "") .replace(/\//g, ":"); return { name, handler: f.fullPath }; }); } export async function scanModules(nitro: Nitro) { const files = await scanFiles(nitro, "modules"); return files.map((f) => f.fullPath); } async function scanFiles(nitro: Nitro, name: string): Promise { const files = await Promise.all( nitro.options.scanDirs.map((dir) => scanDir(nitro, dir, name)) ).then((r) => r.flat()); return files; } async function scanDir(nitro: Nitro, dir: string, name: string): Promise { const fileNames = await glob(join(name, GLOB_SCAN_PATTERN), { cwd: dir, dot: true, ignore: nitro.options.ignore, absolute: true, }).catch((error) => { if (error?.code === "ENOTDIR") { nitro.logger.warn(`Ignoring \`${join(dir, name)}\`. It must be a directory.`); return []; } throw error; }); return fileNames .map((fullPath) => { return { fullPath, path: relative(join(dir, name), fullPath), }; }) .sort((a, b) => a.path.localeCompare(b.path)); } ================================================ FILE: src/task.ts ================================================ import http from "node:http"; import { existsSync } from "node:fs"; import { readFile } from "node:fs/promises"; import { resolve } from "pathe"; import { withBase, withQuery } from "ufo"; import type { QueryObject } from "ufo"; import type { RequestOptions } from "node:http"; import type { NitroBuildInfo, TaskEvent, TaskRunnerOptions } from "nitro/types"; /** @experimental */ export async function runTask( taskEvent: TaskEvent, opts?: TaskRunnerOptions ): Promise<{ result: unknown }> { const ctx = await _getTasksContext(opts); const result = await ctx.devFetch(`/_nitro/tasks/${taskEvent.name}`, { method: "POST", body: taskEvent, }); return result; } /** @experimental */ export async function listTasks(opts?: TaskRunnerOptions) { const ctx = await _getTasksContext(opts); const res = (await ctx.devFetch("/_nitro/tasks")) as { tasks: Record; }; return res.tasks; } // --- module internal --- const _devHint = `(is dev server running?)`; async function _getTasksContext(opts?: TaskRunnerOptions) { const cwd = resolve(process.cwd(), opts?.cwd || "."); const buildDir = resolve(cwd, opts?.buildDir || "node_modules/.nitro"); const buildInfoPath = resolve(buildDir, "nitro.dev.json"); if (!existsSync(buildInfoPath)) { throw new Error(`Missing info file: \`${buildInfoPath}\` ${_devHint}`); } const buildInfo = JSON.parse(await readFile(buildInfoPath, "utf8")) as NitroBuildInfo; if (!buildInfo.dev?.pid || !buildInfo.dev?.workerAddress) { throw new Error(`Missing dev server info in: \`${buildInfoPath}\` ${_devHint}`); } if (!_pidIsRunning(buildInfo.dev.pid)) { throw new Error(`Dev server is not running (pid: ${buildInfo.dev.pid})`); } const baseURL = `http://${buildInfo.dev.workerAddress.host || "localhost"}:${buildInfo.dev.workerAddress.port || "3000"}`; const socketPath = buildInfo.dev.workerAddress.socketPath; const devFetch = ( path: string, options?: { method?: RequestOptions["method"]; query?: QueryObject; body?: unknown; } ) => { return new Promise((resolve, reject) => { let url = withBase(path, baseURL); if (options?.query) { url = withQuery(url, options.query); } const request = http.request( url, { socketPath, method: options?.method, headers: { Accept: "application/json", "Content-Type": "application/json", }, }, (response) => { if (!response.statusCode || (response.statusCode >= 400 && response.statusCode < 600)) { reject(new Error(response.statusMessage)); return; } let data = ""; response .on("data", (chunk) => (data += chunk)) // Response of tasks is always JSON .on("end", () => resolve(JSON.parse(data))) .on("error", (e) => reject(e)); } ); request.on("error", (e) => reject(e)); if (options?.body) { request.write(JSON.stringify(options.body)); } request.end(); }); }; return { buildInfo, devFetch, }; } function _pidIsRunning(pid: number) { try { process.kill(pid, 0); return true; } catch { return false; } } ================================================ FILE: src/types/_utils.ts ================================================ export type Enumerate = Acc["length"] extends N ? Acc[number] : Enumerate; export type IntRange = Exclude, Enumerate>; export type ExcludeFunctions> = Pick< G, // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type { [P in keyof G]: NonNullable extends Function ? never : P }[keyof G] >; export type KebabCase = T extends `${infer F}${infer R}` ? KebabCase ? "" : "-"}${Lowercase}`> : A; ================================================ FILE: src/types/build.ts ================================================ import type { InputOptions as RollupInputOptions, OutputOptions as RollupOutputOptions, } from "rollup"; import type { InputOptions as RolldownInputOptions, OutputOptions as RolldownOutputOptions, MinifyOptions as RolldownMinifyOptions, TransformOptions as RolldownTransformOptions, } from "rolldown"; export type RollupConfig = RollupInputOptions & { output?: RollupOutputOptions; }; export type RolldownConfig = RolldownInputOptions & { output?: RolldownOutputOptions; }; export interface OXCOptions { minify?: RolldownMinifyOptions; transform?: Omit & { jsx?: Exclude; }; } ================================================ FILE: src/types/config.ts ================================================ import type commonjs from "@rollup/plugin-commonjs"; import type { C12InputConfig, ConfigWatcher, DotenvOptions, ResolvedConfig } from "c12"; import type { WatchConfigOptions } from "c12"; import type { ChokidarOptions } from "chokidar"; import type { CompatibilityDateSpec, CompatibilityDates } from "compatx"; import type { LogLevel } from "consola"; import type { ConnectorName } from "db0"; import type { NestedHooks } from "hookable"; import type { ProxyServerOptions } from "httpxy"; import type { PresetName, PresetNameInput, PresetOptions } from "../presets/index.ts"; import type { TSConfig } from "pkg-types"; import type { Preset as UnenvPreset } from "unenv"; import type { UnimportPluginOptions } from "unimport/unplugin"; import type { BuiltinDriverName } from "unstorage"; import type { UnwasmPluginOptions } from "unwasm/plugin"; import type { RunnerName } from "env-runner"; import type { EventHandlerFormat, NitroDevEventHandler, NitroErrorHandler, NitroEventHandler, } from "./handler.ts"; import type { NitroHooks } from "./hooks.ts"; import type { NitroModuleInput } from "./module.ts"; import type { NitroFrameworkInfo } from "./nitro.ts"; import type { NitroOpenAPIConfig } from "./openapi.ts"; export type { NitroOpenAPIConfig } from "./openapi.ts"; import type { NitroPreset } from "./preset.ts"; import type { OXCOptions, RolldownConfig } from "./build.ts"; import type { RollupConfig } from "./build.ts"; import type { NitroRouteConfig, NitroRouteRules } from "./route-rules.ts"; type RollupCommonJSOptions = NonNullable[0]>; /** * Nitro normalized options (nitro.options) */ export interface NitroOptions extends PresetOptions { // Internal _config: NitroConfig; _c12: ResolvedConfig | ConfigWatcher; _cli?: { command?: string; }; // Compatibility compatibilityDate: CompatibilityDates; // General debug: boolean; preset: PresetName; static: boolean; logLevel: LogLevel; runtimeConfig: NitroRuntimeConfig; // Dirs workspaceDir: string; rootDir: string; serverDir: string | false; scanDirs: string[]; apiDir: string; routesDir: string; buildDir: string; output: { dir: string; serverDir: string; publicDir: string; }; /** @deprecated migrate to `serverDir` */ srcDir: string; // Features storage: StorageMounts; devStorage: StorageMounts; database: DatabaseConnectionConfigs; devDatabase: DatabaseConnectionConfigs; renderer?: { handler?: string; static?: boolean; template?: string }; ssrRoutes: string[]; serveStatic: boolean | "node" | "deno" | "inline"; noPublicDir: boolean; manifest?: { deploymentId?: string; }; features: { /** * Enable runtime hooks for request and response. * * By default this feature will be enabled if there is at least one nitro plugin. */ runtimeHooks?: boolean; /** * Enable WebSocket support */ websocket?: boolean; }; /** * * @see https://github.com/unjs/unwasm */ wasm?: false | UnwasmPluginOptions; openAPI?: NitroOpenAPIConfig; experimental: { openAPI?: boolean; /** * See https://github.com/microsoft/TypeScript/pull/51669 */ typescriptBundlerResolution?: boolean; /** * Enable native async context support for useRequest() */ asyncContext?: boolean; /** * Disable Experimental Sourcemap Minification */ sourcemapMinify?: false; /** * Allow env expansion in runtime config * * @see https://github.com/nitrojs/nitro/pull/2043 */ envExpansion?: boolean; /** * Enable WebSocket support * * @see https://nitro.build/guide/websocket * * @deprecated use `features.websocket` instead. */ websocket?: boolean; /** * Enable experimental Database support * * @see https://nitro.build/guide/database */ database?: boolean; /** * Enable experimental Tasks support * * @see https://nitro.build/guide/tasks */ tasks?: boolean; }; future: { nativeSWR?: boolean; }; serverAssets: ServerAssetDir[]; publicAssets: PublicAssetDir[]; imports: Partial | false; modules?: NitroModuleInput[]; plugins: string[]; tasks: { [name: string]: { handler?: string; description?: string } }; scheduledTasks: { [cron: string]: string | string[] }; virtual: Record string | Promise)>; compressPublicAssets: boolean | CompressOptions; ignore: string[]; // Dev dev: boolean; devServer: { port?: number; hostname?: string; watch?: string[]; runner?: RunnerName; }; watchOptions: ChokidarOptions; devProxy: Record; // Logging logging: { compressedSizes?: boolean; buildSuccess?: boolean; }; // Routing baseURL: string; apiBaseURL: string; serverEntry: false | { handler: string; format?: EventHandlerFormat }; handlers: NitroEventHandler[]; devHandlers: NitroDevEventHandler[]; routeRules: { [path: string]: NitroRouteRules }; routes: Record>; errorHandler: string | string[]; devErrorHandler: NitroErrorHandler; prerender: { /** * Prerender HTML routes within subfolders (`/test` would produce `/test/index.html`) */ autoSubfolderIndex?: boolean; concurrency?: number; interval?: number; crawlLinks?: boolean; failOnError?: boolean; ignore?: Array undefined | null | boolean)>; ignoreUnprefixedPublicAssets?: boolean; routes?: string[]; /** * Amount of retries. Pass Infinity to retry indefinitely. * @default 3 */ retry?: number; /** * Delay between each retry in ms. * @default 500 */ retryDelay?: number; }; // Rollup builder?: "rollup" | "rolldown" | "vite"; rollupConfig?: RollupConfig; rolldownConfig?: RolldownConfig; entry: string; unenv: UnenvPreset[]; alias: Record; minify: boolean; inlineDynamicImports: boolean; sourcemap: boolean; node: boolean; moduleSideEffects: string[]; oxc?: OXCOptions; replace: Record string)>; commonJS?: RollupCommonJSOptions; exportConditions?: string[]; noExternals?: boolean | (string | RegExp)[]; traceDeps?: (string | RegExp)[]; // Advanced typescript: { strict?: boolean; generateRuntimeConfigTypes?: boolean; generateTsConfig?: boolean; tsConfig?: Partial; /** * Path of the generated types directory. * * Default is `node_modules/.nitro/types` */ generatedTypesDir?: string; /** * Path of the generated `tsconfig.json` relative to `typescript.generatedTypesDir` * * Default is `tsconfig.json` (`node_modules/.nitro/types/tsconfig.json`) */ tsconfigPath?: string; }; hooks: NestedHooks; commands: { preview?: string; deploy?: string; }; // Framework framework: NitroFrameworkInfo; // IIS iis?: { mergeConfig?: boolean; overrideConfig?: boolean; }; } /** * Nitro input config (nitro.config) */ export interface NitroConfig extends Partial< Omit< NitroOptions, | "routeRules" | "rollupConfig" | "preset" | "compatibilityDate" | "unenv" | "serverDir" | "_config" | "_c12" | "serverEntry" | "renderer" | "output" > >, C12InputConfig { preset?: PresetNameInput; extends?: string | string[] | NitroPreset; routeRules?: { [path: string]: NitroRouteConfig }; rollupConfig?: Partial; compatibilityDate?: CompatibilityDateSpec; unenv?: UnenvPreset | UnenvPreset[]; serverDir?: boolean | "./" | "./server" | (string & {}); serverEntry?: string | NitroOptions["serverEntry"]; renderer?: false | NitroOptions["renderer"]; output?: Partial; } // ------------------------------------------------------------ // Config Loader // ------------------------------------------------------------ export interface LoadConfigOptions { watch?: boolean; c12?: WatchConfigOptions; compatibilityDate?: CompatibilityDateSpec; dotenv?: boolean | DotenvOptions; } // ------------------------------------------------------------ // Partial types // ------------------------------------------------------------ // Public assets export interface PublicAssetDir { baseURL?: string; fallthrough?: boolean; maxAge: number; dir: string; /** * Pass false to disable ignore patterns when scanning the directory, or * pass an array of glob patterns to ignore (which will override global * nitro.ignore patterns). */ ignore?: false | string[]; } // Public assets compression export interface CompressOptions { gzip?: boolean; brotli?: boolean; zstd?: boolean; } // Server assets export interface ServerAssetDir { baseName: string; pattern?: string; dir: string; ignore?: string[]; } // Storage mounts type CustomDriverName = string & { _custom?: any }; export interface StorageMounts { [path: string]: { driver: BuiltinDriverName | CustomDriverName; [option: string]: any; }; } // Database export type DatabaseConnectionName = "default" | (string & {}); export type DatabaseConnectionConfig = { connector: ConnectorName; options?: { [key: string]: any; }; }; export type DatabaseConnectionConfigs = Record; // Runtime config export interface NitroRuntimeConfigApp { [key: string]: any; } export interface NitroRuntimeConfig { nitro?: { envPrefix?: string; envExpansion?: boolean; routeRules?: { [path: string]: NitroRouteConfig; }; openAPI?: NitroOpenAPIConfig; }; [key: string]: any; } ================================================ FILE: src/types/fetch/_match.ts ================================================ import type { InternalApi } from "./fetch.ts"; type MatchResult< Key extends string, Exact extends boolean = false, Score extends any[] = [], catchAll extends boolean = false, > = { [k in Key]: { key: k; exact: Exact; score: Score; catchAll: catchAll }; }[Key]; type Subtract = Minuend extends [ ...Subtrahend, ...infer Remainder, ] ? Remainder : never; type TupleIfDiff< First extends string, Second extends string, Tuple extends any[] = [], > = First extends `${Second}${infer Diff}` ? (Diff extends "" ? [] : Tuple) : []; type MaxTuple = { current: T; result: MaxTuple; }[[N["length"]] extends [Partial["length"]] ? "current" : "result"]; type CalcMatchScore< Key extends string, Route extends string, Score extends any[] = [], Init extends boolean = false, FirstKeySegMatcher extends string = Init extends true ? ":Invalid:" : "", > = `${Key}/` extends `${infer KeySeg}/${infer KeyRest}` ? KeySeg extends FirstKeySegMatcher // return score if `KeySeg` is empty string (except first pass) ? Subtract<[...Score, ...TupleIfDiff], TupleIfDiff> : `${Route}/` extends `${infer RouteSeg}/${infer RouteRest}` ? `${RouteSeg}?` extends `${infer RouteSegWithoutQuery}?${string}` ? RouteSegWithoutQuery extends KeySeg ? CalcMatchScore // exact match : KeySeg extends `:${string}` ? RouteSegWithoutQuery extends "" ? never : CalcMatchScore // param match : KeySeg extends RouteSegWithoutQuery ? CalcMatchScore // match by ${string} : never : never : never : never; type _MatchedRoutes< Route extends string, MatchedResultUnion extends MatchResult = MatchResult, > = MatchedResultUnion["key"] extends infer MatchedKeys // spread union type ? MatchedKeys extends string ? Route extends MatchedKeys ? MatchResult // exact match : MatchedKeys extends `${infer Root}/**${string}` ? MatchedKeys extends `${string}/**` ? Route extends `${Root}/${string}` ? MatchResult : never // catchAll match : MatchResult> // glob match : MatchResult> // partial match : never : never; export type MatchedRoutes< Route extends string, MatchedKeysResult extends MatchResult = MatchResult, Matches extends MatchResult = _MatchedRoutes, > = Route extends "/" ? keyof InternalApi // root middleware : Extract extends never ? // @ts-ignore | Extract, { score: MaxTuple }>["key"] | Extract["key"] // partial, glob and catchAll matches : Extract["key"]; // exact matches ================================================ FILE: src/types/fetch/_serialize.ts ================================================ /** * @link https://github.com/remix-run/remix/blob/2248669ed59fd716e267ea41df5d665d4781f4a9/packages/remix-server-runtime/serialize.ts */ type JsonPrimitive = string | number | boolean | string | number | boolean | null; // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type type NonJsonPrimitive = undefined | Function | symbol; /* * `any` is the only type that can let you equate `0` with `1` * See https://stackoverflow.com/a/49928360/1490091 */ type IsAny = 0 extends 1 & T ? true : false; type FilterKeys = { [TKey in keyof TObj]: TObj[TKey] extends TFilter ? TKey : never; }[keyof TObj]; // prettier-ignore export type Serialize = IsAny extends true ? any : T extends JsonPrimitive | undefined ? T : T extends Map | Set ? Record : T extends NonJsonPrimitive ? never : T extends { toJSON(): infer U } ? U : T extends [] ? [] : T extends [unknown, ...unknown[]] ? SerializeTuple : T extends ReadonlyArray ? (U extends NonJsonPrimitive ? null : Serialize)[] : T extends object ? SerializeObject : never; /** JSON serialize [tuples](https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types) */ export type SerializeTuple = { [k in keyof T]: T[k] extends NonJsonPrimitive ? null : Serialize; }; /** JSON serialize objects (not including arrays) and classes */ export type SerializeObject = { [k in keyof Omit>]: Serialize; }; /** * @see https://github.com/ianstormtaylor/superstruct/blob/7973400cd04d8ad92bbdc2b6f35acbfb3c934079/src/utils.ts#L323-L325 */ export type Simplify = TType extends any[] | Date ? TType : { [K in keyof TType]: Simplify }; ================================================ FILE: src/types/fetch/fetch.ts ================================================ import type { HTTPMethod } from "h3"; import type { FetchOptions, FetchRequest, FetchResponse } from "ofetch"; import type { MatchedRoutes } from "./_match.ts"; // An interface to extend in a local project export interface InternalApi {} // TODO: upgrade to uppercase for h3 v2 types and web consistency type RouterMethod = Lowercase; export type NitroFetchRequest = | Exclude | Exclude | (string & {}); export type MiddlewareOf< Route extends string, Method extends RouterMethod | "default", > = Method extends keyof InternalApi[MatchedRoutes] ? InternalApi[MatchedRoutes][Method] : never; export type TypedInternalResponse< Route, Default = unknown, Method extends RouterMethod = RouterMethod, > = Default extends string | boolean | number | null | void | object ? // Allow user overrides Default : Route extends string ? MiddlewareOf extends never ? MiddlewareOf extends never ? // Bail if only types are Error or void (for example, from middleware) Default : MiddlewareOf : MiddlewareOf : Default; // Extracts the available http methods based on the route. // Defaults to all methods if there aren't any methods available or if there is a catch-all route. export type AvailableRouterMethod = R extends string ? keyof InternalApi[MatchedRoutes] extends undefined ? RouterMethod : Extract], "default"> extends undefined ? Extract]> : RouterMethod : RouterMethod; // Argumented fetch options to include the correct request methods. // This overrides the default, which is only narrowed to a string. export interface NitroFetchOptions< R extends NitroFetchRequest, M extends AvailableRouterMethod = AvailableRouterMethod, > extends FetchOptions { method?: Uppercase | M; } // Extract the route method from options which might be undefined or without a method parameter. export type ExtractedRouteMethod< R extends NitroFetchRequest, O extends NitroFetchOptions, > = O extends undefined ? "get" : Lowercase> extends RouterMethod ? Lowercase> : "get"; export type Base$Fetch< DefaultT = unknown, DefaultR extends NitroFetchRequest = NitroFetchRequest, > = < T = DefaultT, R extends NitroFetchRequest = DefaultR, O extends NitroFetchOptions = NitroFetchOptions, >( request: R, opts?: O ) => Promise< TypedInternalResponse extends O ? "get" : ExtractedRouteMethod> >; export interface $Fetch< DefaultT = unknown, DefaultR extends NitroFetchRequest = NitroFetchRequest, > extends Base$Fetch { raw< T = DefaultT, R extends NitroFetchRequest = DefaultR, O extends NitroFetchOptions = NitroFetchOptions, >( request: R, opts?: O ): Promise< FetchResponse< TypedInternalResponse< R, T, NitroFetchOptions extends O ? "get" : ExtractedRouteMethod > > >; create( defaults: FetchOptions ): $Fetch; } // eslint-disable-next-line unicorn/require-module-specifiers export type {}; ================================================ FILE: src/types/fetch/index.ts ================================================ export * from "./_match.ts"; export * from "./_serialize.ts"; export * from "./fetch.ts"; ================================================ FILE: src/types/global.ts ================================================ import type { NitroOptions } from "./config.ts"; export interface NitroImportMeta { dev?: boolean; preset?: NitroOptions["preset"]; prerender?: boolean; nitro?: boolean; server?: boolean; client?: boolean; baseURL?: string; runtimeConfig?: Record; _asyncContext?: boolean; _tasks?: boolean; _websocket?: boolean; } declare global { interface ImportMeta extends NitroImportMeta {} } // eslint-disable-next-line unicorn/require-module-specifiers export type {}; ================================================ FILE: src/types/h3.ts ================================================ import type { H3Event as _H3Event } from "h3"; import type { CacheOptions, CapturedErrorContext } from "./runtime/index.ts"; import type { Base$Fetch, NitroFetchRequest } from "./fetch/fetch.ts"; import type { NitroRuntimeConfig } from "./config.ts"; import type { MatchedRouteRules } from "./route-rules.ts"; export type H3EventFetch = (request: NitroFetchRequest, init?: RequestInit) => Promise; export type H3Event$Fetch = Base$Fetch; declare module "srvx" { interface ServerRequestContext { routeRules?: MatchedRouteRules; nitro?: { runtimeConfig?: NitroRuntimeConfig; errors?: { error?: Error; context: CapturedErrorContext }[]; }; cache?: { options?: CacheOptions; }; } } // eslint-disable-next-line unicorn/require-module-specifiers export type {}; ================================================ FILE: src/types/handler.ts ================================================ import type { HTTPError, HTTPMethod, HTTPEvent, HTTPHandler } from "h3"; import type { PresetName } from "../presets/index.ts"; import type { OperationObject, OpenAPI3, Extensable } from "../types/openapi-ts.ts"; type MaybeArray = T | T[]; /** @experimental */ export interface NitroRouteMeta { openAPI?: OperationObject & { $global?: Pick & Extensable; }; } interface NitroHandlerCommon { /** * HTTP pathname pattern to match * * Examples: `/test`, `/api/:id`, `/blog/**` */ route: string; /** * HTTP method to match */ method?: HTTPMethod; /** * Run handler as a middleware before other route handlings */ middleware?: boolean; /** * Extra Meta */ meta?: NitroRouteMeta; } export type EventHandlerFormat = "web" | "node"; export interface NitroEventHandler extends NitroHandlerCommon { /** * Use lazy loading to import handler */ lazy?: boolean; /** * Path to event handler */ handler: string; /** * Event handler type. * * Default is `"web"`. If set to `"node"`, the handler will be converted into a web compatible handler. */ format?: EventHandlerFormat; /* * Environments to include and bundle this handler */ env?: MaybeArray<"dev" | "prod" | "prerender" | PresetName | (string & {})>; } export interface NitroDevEventHandler extends NitroHandlerCommon { /** * Event handler function */ handler: HTTPHandler; } type MaybePromise = T | Promise; export type NitroErrorHandler = ( error: HTTPError, event: HTTPEvent, _: { defaultHandler: ( error: HTTPError, event: HTTPEvent, opts?: { silent?: boolean; json?: boolean } ) => MaybePromise<{ status?: number; statusText?: string; headers?: HeadersInit; body?: string | Record; }>; } ) => MaybePromise; ================================================ FILE: src/types/hooks.ts ================================================ import type { EnvRunnerData } from "env-runner"; import type { NitroConfig } from "./config.ts"; import type { Nitro, NitroTypes } from "./nitro.ts"; import type { PrerenderRoute } from "./prerender.ts"; import type { RollupConfig } from "./build.ts"; type HookResult = void | Promise; export interface NitroHooks { "types:extend": (types: NitroTypes) => HookResult; "build:before": (nitro: Nitro) => HookResult; "rollup:before": (nitro: Nitro, config: RollupConfig) => HookResult; compiled: (nitro: Nitro) => HookResult; "dev:reload": (payload?: { entry?: string; workerData?: EnvRunnerData }) => HookResult; "dev:start": () => HookResult; "dev:error": (cause?: unknown) => HookResult; "rollup:reload": () => HookResult; restart: () => HookResult; close: () => HookResult; // Prerender "prerender:routes": (routes: Set) => HookResult; "prerender:config": (config: NitroConfig) => HookResult; "prerender:init": (prerenderer: Nitro) => HookResult; "prerender:generate": (route: PrerenderRoute, nitro: Nitro) => HookResult; "prerender:route": (route: PrerenderRoute) => HookResult; "prerender:done": (result: { prerenderedRoutes: PrerenderRoute[]; failedRoutes: PrerenderRoute[]; }) => HookResult; } ================================================ FILE: src/types/index.ts ================================================ import "nitro"; import "nitro/app"; import "nitro/cache"; import "nitro/context"; import "nitro/database"; import "nitro/h3"; import "nitro/runtime-config"; import "nitro/storage"; import "nitro/task"; export * from "./fetch/index.ts"; export * from "./runtime/index.ts"; export * from "./config.ts"; export * from "./runner.ts"; export * from "./global.ts"; export * from "./h3.ts"; export * from "./handler.ts"; export * from "./hooks.ts"; export * from "./module.ts"; export * from "./nitro.ts"; export * from "./prerender.ts"; export * from "./preset.ts"; export * from "./build.ts"; export * from "./route-rules.ts"; export * from "./srvx.ts"; ================================================ FILE: src/types/module.ts ================================================ import type { Nitro } from "./nitro.ts"; export type NitroModuleInput = string | NitroModule | NitroModule["setup"] | { nitro: NitroModule }; export interface NitroModule { name?: string; setup: (this: void, nitro: Nitro) => void | Promise; } ================================================ FILE: src/types/nitro.ts ================================================ import type { ConsolaInstance } from "consola"; import type { HTTPMethod } from "h3"; import type { Hookable } from "hookable"; import type { PresetName, PresetOptions } from "../presets/index.ts"; import type { Unimport } from "unimport"; import type { NitroConfig, NitroOptions } from "./config.ts"; import type { NitroEventHandler } from "./handler.ts"; import type { NitroHooks } from "./hooks.ts"; import type { PrerenderRoute } from "./prerender.ts"; import type { TSConfig } from "pkg-types"; import type { Router } from "../routing.ts"; import type { NitroRouteRules } from "./route-rules.ts"; import type { WorkerAddress } from "./runner.ts"; type MaybeArray = T | T[]; export interface Nitro { options: NitroOptions; scannedHandlers: NitroEventHandler[]; vfs: Map string | Promise }>; hooks: Hookable; unimport?: Unimport; logger: ConsolaInstance; fetch: (input: Request) => Response | Promise; close: () => Promise; updateConfig: (config: NitroDynamicConfig) => void | Promise; routing: Readonly<{ sync: () => void; routeRules: Router; routes: Router>; globalMiddleware: (NitroEventHandler & { _importHash: string })[]; routedMiddleware: Router; }>; /* @internal */ _prerenderedRoutes?: PrerenderRoute[]; _prerenderMeta?: Record; } export type NitroDynamicConfig = Pick; export type NitroTypes = { routes: Record>>; tsConfig?: TSConfig; }; export interface NitroFrameworkInfo { name?: "nitro" | (string & {}); version?: string; } /** Build info written to `.output/nitro.json` or `.nitro/dev/nitro.json` */ export interface NitroBuildInfo { date: string; preset: PresetName; framework: NitroFrameworkInfo; versions: { nitro: string; [key: string]: string; }; commands?: { preview?: string; deploy?: string; }; serverEntry?: string; publicDir?: string; dev?: { pid: number; workerAddress?: WorkerAddress; }; config?: Partial; } ================================================ FILE: src/types/openapi-ts.ts ================================================ /** Source: (inlined because of install size concernes) https://github.com/openapi-ts/openapi-typescript/blob/fc3f7/packages/openapi-typescript/src/types.ts MIT License Copyright (c) 2020 Drew Powers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ // Many types allow for true “any” for inheritance to work export interface Extensable { [key: `x-${string}`]: any; } // Note: these OpenAPI types are meant only for internal use, not external // consumption. Some formatting may be better in other libraries meant for // consumption. Some typing may be “loose” or “incorrect” in order to guarantee // that all logical paths are handled. In other words, these are built more // for ways schemas _can_ be written, not necessarily how they _should_ be. /** * [4.8] Schema * @see https://spec.openapis.org/oas/v3.1.0#schema */ export interface OpenAPI3 extends Extensable { /** REQUIRED. This string MUST be the version number of the OpenAPI Specification that the OpenAPI document uses. The openapi field SHOULD be used by tooling to interpret the OpenAPI document. This is not related to the API info.version string. */ openapi: string; /** REQUIRED. Provides metadata about the API. The metadata MAY be used by tooling as required. */ info: InfoObject; // required /** The default value for the $schema keyword within Schema Objects contained within this OAS document. This MUST be in the form of a URI. */ jsonSchemaDialect?: string; /** An array of Server Objects, which provide connectivity information to a target server. If the servers property is not provided, or is an empty array, the default value would be a Server Object with a url value of /. */ servers?: ServerObject[]; /** The available paths and operations for the API. */ paths?: PathsObject; /** The incoming webhooks that MAY be received as part of this API and that the API consumer MAY choose to implement. Closely related to the callbacks feature, this section describes requests initiated other than by an API call, for example by an out of band registration. The key name is a unique string to refer to each webhook, while the (optionally referenced) Path Item Object describes a request that may be initiated by the API provider and the expected responses. An example is available. */ webhooks?: { [id: string]: PathItemObject | ReferenceObject }; /** An element to hold various schemas for the document. */ components?: ComponentsObject; /** A declaration of which security mechanisms can be used across the API. The list of values includes alternative security requirement objects that can be used. Only one of the security requirement objects need to be satisfied to authorize a request. Individual operations can override this definition. To make security optional, an empty security requirement ({}) can be included in the array. */ security?: SecurityRequirementObject[]; /** A list of tags used by the document with additional metadata. The order of the tags can be used to reflect on their order by the parsing tools. Not all tags that are used by the Operation Object must be declared. The tags that are not declared MAY be organized randomly or based on the tools’ logic. Each tag name in the list MUST be unique. */ tags?: TagObject[]; /** Additional external documentation. */ externalDocs?: ExternalDocumentationObject; $defs?: $defs; } /** * [4.8.2] Info Object * The object provides metadata about the API. */ export interface InfoObject extends Extensable { /** REQUIRED. The title of the API. */ title: string; /** A short summary of the API. */ summary?: string; /** A description of the API. CommonMark syntax MAY be used for rich text representation. */ description?: string; /** A URL to the Terms of Service for the API. This MUST be in the form of a URL. */ termsOfService?: string; /** The contact information for the exposed API. */ contact?: ContactObject; /** The license information for the exposed API. */ license?: LicenseObject; /** REQUIRED. The version of the OpenAPI document (which is distinct from the OpenAPI Specification version or the API implementation version). */ version: string; } /** * [4.8.3] Contact Object * Contact information for the exposed API. */ export interface ContactObject extends Extensable { /** The identifying name of the contact person/organization. */ name?: string; /** The URL pointing to the contact information. This MUST be in the form of a URL. */ url?: string; /** The email address of the contact person/organization. This MUST be in the form of an email address. */ email?: string; } /** * [4.8.4] License object * License information for the exposed API. */ export interface LicenseObject extends Extensable { /** REQUIRED. The license name used for the API. */ name: string; /** An SPDX license expression for the API. The identifier field is mutually exclusive of the url field. */ identifier: string; /** A URL to the license used for the API. This MUST be in the form of a URL. The url field is mutually exclusive of the identifier field. */ url: string; } /** * [4.8.5] Server Object * An object representing a Server. */ export interface ServerObject extends Extensable { /** REQUIRED. A URL to the target host. This URL supports Server Variables and MAY be relative, to indicate that the host location is relative to the location where the OpenAPI document is being served. Variable substitutions will be made when a variable is named in {brackets}. */ url: string; /** An optional string describing the host designated by the URL. CommonMark syntax MAY be used for rich text representation. */ description: string; /** A map between a variable name and its value. The value is used for substitution in the server’s URL template. */ variables: { [name: string]: ServerVariableObject }; } /** * [4.8.6] Server Variable Object * An object representing a Server Variable for server URL template substitution. */ export interface ServerVariableObject extends Extensable { /** An enumeration of string values to be used if the substitution options are from a limited set. The array MUST NOT be empty. */ enum?: string[]; /** REQUIRED. The default value to use for substitution, which SHALL be sent if an alternate value is not supplied. Note this behavior is different than the Schema Object’s treatment of default values, because in those cases parameter values are optional. If the enum is defined, the value MUST exist in the enum’s values. */ default: string; /** An optional description for the server variable. CommonMark syntax MAY be used for rich text representation. */ description?: string; } /** * [4.8.7] Components Object * Holds a set of reusable objects for different aspects of the OAS. */ export interface ComponentsObject extends Extensable { /** An object to hold reusable Schema Objects.*/ schemas?: Record; /** An object to hold reusable Response Objects. */ responses?: Record; /** An object to hold reusable Parameter Objects. */ parameters?: Record; /** An object to hold reusable Example Objects. */ examples?: Record; /** An object to hold reusable Request Body Objects. */ requestBodies?: Record; /** An object to hold reusable Header Objects. */ headers?: Record; /** An object to hold reusable Security Scheme Objects. */ securitySchemes?: Record; /** An object to hold reusable Link Objects. */ links?: Record; /** An object to hold reusable Callback Objects. */ callbacks?: Record; /** An object to hold reusable Path Item Objects. */ pathItems?: Record; } /** * [4.8.8] Paths Object * Holds the relative paths to the individual endpoints and their operations. The path is appended to the URL from the Server Object in order to construct the full URL. The Paths MAY be empty, due to Access Control List (ACL) constraints. */ export interface PathsObject { [pathname: string]: PathItemObject | ReferenceObject; // note: paths object does support $refs; the schema just defines it in a weird way } /** * [x.x.x] Webhooks Object * Holds the webhooks definitions, indexed by their names. A webhook is defined by a Path Item Object; the only difference is that the request is initiated by the API provider. */ export interface WebhooksObject { [name: string]: PathItemObject; } /** * [4.8.9] Path Item Object * Describes the operations available on a single path. A Path Item MAY be empty, due to ACL constraints. The path itself is still exposed to the documentation viewer but they will not know which operations and parameters are available. */ export interface PathItemObject extends Extensable { /** A definition of a GET operation on this path. */ get?: OperationObject | ReferenceObject; /** A definition of a PUT operation on this path. */ put?: OperationObject | ReferenceObject; /** A definition of a POST operation on this path. */ post?: OperationObject | ReferenceObject; /** A definition of a DELETE operation on this path. */ delete?: OperationObject | ReferenceObject; /** A definition of a OPTIONS operation on this path. */ options?: OperationObject | ReferenceObject; /** A definition of a HEAD operation on this path. */ head?: OperationObject | ReferenceObject; /** A definition of a PATCH operation on this path. */ patch?: OperationObject | ReferenceObject; /** A definition of a TRACE operation on this path. */ trace?: OperationObject | ReferenceObject; /** An alternative server array to service all operations in this path. */ servers?: ServerObject[]; /** A list of parameters that are applicable for all the operations described under this path. These parameters can be overridden at the operation level, but cannot be removed there. The list MUST NOT include duplicated parameters. A unique parameter is defined by a combination of a name and location. The list can use the Reference Object to link to parameters that are defined at the OpenAPI Object’s components/parameters. */ parameters?: (ParameterObject | ReferenceObject)[]; } /** * [4.8.10] Operation Object * Describes a single API operation on a path. */ export interface OperationObject extends Extensable { /** A list of tags for API documentation control. Tags can be used for logical grouping of operations by resources or any other qualifier. */ tags?: string[]; /** A short summary of what the operation does. */ summary?: string; /** A verbose explanation of the operation behavior. CommonMark syntax MAY be used for rich text representation. */ description?: string; /** Additional external documentation for this operation. */ externalDocs?: ExternalDocumentationObject; /** Unique string used to identify the operation. The id MUST be unique among all operations described in the API. The operationId value is case-sensitive. Tools and libraries MAY use the operationId to uniquely identify an operation, therefore, it is RECOMMENDED to follow common programming naming conventions. */ operationId?: string; /** A list of parameters that are applicable for this operation. If a parameter is already defined at the Path Item, the new definition will override it but can never remove it. The list MUST NOT include duplicated parameters. A unique parameter is defined by a combination of a name and location. The list can use the Reference Object to link to parameters that are defined at the OpenAPI Object’s components/parameters. */ parameters?: (ParameterObject | ReferenceObject)[]; /** The request body applicable for this operation. The requestBody is fully supported in HTTP methods where the HTTP 1.1 specification [RFC7231] has explicitly defined semantics for request bodies. In other cases where the HTTP spec is vague (such as GET, HEAD and DELETE), requestBody is permitted but does not have well-defined semantics and SHOULD be avoided if possible. */ requestBody?: RequestBodyObject | ReferenceObject; /** The list of possible responses as they are returned from executing this operation. */ responses?: ResponsesObject; /** A map of possible out-of band callbacks related to the parent operation. The key is a unique identifier for the Callback Object. Each value in the map is a Callback Object that describes a request that may be initiated by the API provider and the expected responses. */ callbacks?: Record; /** Declares this operation to be deprecated. Consumers SHOULD refrain from usage of the declared operation. Default value is false. */ deprecated?: boolean; /** A declaration of which security mechanisms can be used for this operation. The list of values includes alternative security requirement objects that can be used. Only one of the security requirement objects need to be satisfied to authorize a request. To make security optional, an empty security requirement ({}) can be included in the array. This definition overrides any declared top-level security. To remove a top-level security declaration, an empty array can be used. */ security?: SecurityRequirementObject[]; /** An alternative server array to service this operation. If an alternative server object is specified at the Path Item Object or Root level, it will be overridden by this value. */ servers?: ServerObject[]; } /** * [4.8.11] External Documentation Object * Allows referencing an external resource for extended documentation. */ export interface ExternalDocumentationObject extends Extensable { /** A description of the target documentation. CommonMark syntax MAY be used for rich text representation. */ description?: string; /** REQUIRED. The URL for the target documentation. This MUST be in the form of a URL. */ url: string; } /** * [4.8.12] Parameter Object * Describes a single operation parameter. * A unique parameter is defined by a combination of a name and location. */ export interface ParameterObject extends Extensable { /** * REQUIRED. The name of the parameter. Parameter names are case sensitive. * * - If `in` is `"path"`, the `name` field MUST correspond to a template expression occurring within the path field in the Paths Object. See Path Templating for further information. * - If `in` is `"header"` and the `name` field is `"Accept"`, `"Content-Type"` or `"Authorization"`, the parameter definition SHALL be ignored. * - For all other cases, the `name` corresponds to the parameter name used by the `in` property. */ name: string; /** REQUIRED. The location of the parameter. Possible values are "query", "header", "path" or "cookie".*/ in: "query" | "header" | "path" | "cookie"; /** A brief description of the parameter. This could contain examples of use. CommonMark syntax MAY be used for rich text representation. */ description?: string; /** Determines whether this parameter is mandatory. If the parameter location is "path", this property is REQUIRED and its value MUST be true. Otherwise, the property MAY be included and its default value is false. */ required?: boolean; /** Specifies that a parameter is deprecated and SHOULD be transitioned out of usage. Default value is false. */ deprecated?: boolean; /** Sets the ability to pass empty-valued parameters. This is valid only for query parameters and allows sending a parameter with an empty value. Default value is false. If style is used, and if behavior is n/a (cannot be serialized), the value of allowEmptyValue SHALL be ignored. Use of this property is NOT RECOMMENDED, as it is likely to be removed in a later revision. */ allowEmptyValue?: boolean; /** Describes how the parameter value will be serialized depending on the type of the parameter value. Default values (based on value of in): for query - form; for path - simple; for header - simple; for cookie - form. */ style?: string; /** When this is true, parameter values of type `array` or `object` generate separate parameters for each value of the array or key-value pair of the map. For other types of parameters this property has no effect. When `style` is `form`, the default value is `true`. For all other styles, the default value is `false`. */ explode?: boolean; /** Determines whether the parameter value SHOULD allow reserved characters, as defined by [RFC3986] `:/?#[]@!$&'()*+,;=` to be included without percent-encoding. This property only applies to parameters with an `in` value of `query`. The default value is `false`. */ allowReserved?: boolean; /** The schema defining the type used for the parameter. */ schema?: SchemaObject; /** Example of the parameter’s potential value. */ example?: any; /** Examples of the parameter’s potential value. */ examples?: { [name: string]: ExampleObject | ReferenceObject }; /** A map containing the representations for the parameter. */ content?: { [contentType: string]: MediaTypeObject | ReferenceObject }; } /** * [4.8.13] Request Body Object * Describes a single request body. */ export interface RequestBodyObject extends Extensable { /** A brief description of the request body. This could contain examples of use. CommonMark syntax MAY be used for rich text representation. */ description?: string; /** REQUIRED. The content of the request body. The key is a media type or media type range and the value describes it. For requests that match multiple keys, only the most specific key is applicable. e.g. text/plain overrides text */ content: { [contentType: string]: MediaTypeObject | ReferenceObject }; /** Determines if the request body is required in the request. Defaults to false. */ required?: boolean; } /** * [4.8.14] Media Type Object */ export interface MediaTypeObject extends Extensable { /** The schema defining the content of the request, response, or parameter. */ schema?: SchemaObject | ReferenceObject; /** Example of the media type. The example object SHOULD be in the correct format as specified by the media type. The example field is mutually exclusive of the examples field. Furthermore, if referencing a schema which contains an example, the example value SHALL override the example provided by the schema. */ example?: any; /** Examples of the media type. Each example object SHOULD match the media type and specified schema if present. The examples field is mutually exclusive of the example field. Furthermore, if referencing a schema which contains an example, the examples value SHALL override the example provided by the schema. */ examples?: { [name: string]: ExampleObject | ReferenceObject }; /** A map between a property name and its encoding information. The key, being the property name, MUST exist in the schema as a property. The encoding object SHALL only apply to requestBody objects when the media type is multipart or application/x-www-form-urlencoded. */ encoding?: { [propertyName: string]: EncodingObject }; } /** * [4.8.15] Encoding Object * A single encoding definition applied to a single schema property. */ export interface EncodingObject extends Extensable { /** The Content-Type for encoding a specific property. Default value depends on the property type: for object - application/json; for array – the default is defined based on the inner type; for all other cases the default is application/octet-stream. The value can be a specific media type (e.g. application/json), a wildcard media type (e.g. image/*), or a comma-separated list of the two types. */ contentType?: string; /** A map allowing additional information to be provided as headers, for example Content-Disposition. Content-Type is described separately and SHALL be ignored in this section. This property SHALL be ignored if the request body media type is not a multipart. */ headers?: { [name: string]: HeaderObject | ReferenceObject }; /** Describes how a specific property value will be serialized depending on its type. See Parameter Object for details on the style property. The behavior follows the same values as query parameters, including default values. This property SHALL be ignored if the request body media type is not application/x-www-form-urlencoded or multipart/form-data. If a value is explicitly defined, then the value of contentType (implicit or explicit) SHALL be ignored. */ style?: string; /** When this is true, property values of type array or object generate separate parameters for each value of the array, or key-value-pair of the map. For other types of properties this property has no effect. When style is form, the default value is true. For all other styles, the default value is false. This property SHALL be ignored if the request body media type is not application/x-www-form-urlencoded or multipart/form-data. If a value is explicitly defined, then the value of contentType (implicit or explicit) SHALL be ignored. */ explode?: string; /** Determines whether the parameter value SHOULD allow reserved characters, as defined by [RFC3986] :/?#[]@!$&'()*+,;= to be included without percent-encoding. The default value is false. This property SHALL be ignored if the request body media type is not application/x-www-form-urlencoded or multipart/form-data. If a value is explicitly defined, then the value of contentType (implicit or explicit) SHALL be ignored. */ allowReserved?: string; } /** * [4.8.16] Responses Object * A container for the expected responses of an operation. The container maps a HTTP response code to the expected response. */ export type ResponsesObject = { [responseCode: string]: ResponseObject | ReferenceObject; } & { /** The documentation of responses other than the ones declared for specific HTTP response codes. Use this field to cover undeclared responses. */ default?: ResponseObject | ReferenceObject; }; /** * [4.8.17] Response Object * Describes a single response from an API Operation, including design-time, static links to operations based on the response. */ export interface ResponseObject extends Extensable { /** REQUIRED. A description of the response. CommonMark syntax MAY be used for rich text representation. */ description: string; /** Maps a header name to its definition. [RFC7230] states header names are case insensitive. If a response header is defined with the name "Content-Type", it SHALL be ignored. */ headers?: { [name: string]: HeaderObject | ReferenceObject }; /** A map containing descriptions of potential response payloads. The key is a media type or media type range and the value describes it. For responses that match multiple keys, only the most specific key is applicable. e.g. text/plain overrides text */ content?: { [contentType: string]: MediaTypeObject }; /** A map of operations links that can be followed from the response. The key of the map is a short name for the link, following the naming constraints of the names for Component Objects. */ links?: { [name: string]: LinkObject | ReferenceObject }; } /** * [4.8.18] Callback Object * A map of possible out-of band callbacks related to the parent operation. Each value in the map is a Path Item Object that describes a set of requests that may be initiated by the API provider and the expected responses. The key value used to identify the path item object is an expression, evaluated at runtime, that identifies a URL to use for the callback operation. */ export type CallbackObject = Record; /** * [4.8.19[ Example Object */ export interface ExampleObject extends Extensable { /** Short description for the example. */ summary?: string; /** Long description for the example. CommonMark syntax MAY be used for rich text representation. */ description?: string; /** Embedded literal example. The value field and externalValue field are mutually exclusive. To represent examples of media types that cannot naturally represented in JSON or YAML, use a string value to contain the example, escaping where necessary. */ value?: any; /** A URI that points to the literal example. This provides the capability to reference examples that cannot easily be included in JSON or YAML documents. The value field and externalValue field are mutually exclusive. See the rules for resolving Relative References. */ externalValue?: string; } /** * [4.8.20] Link Object * The Link object represents a possible design-time link for a response. The presence of a link does not guarantee the caller’s ability to successfully invoke it, rather it provides a known relationship and traversal mechanism between responses and other operations. */ export interface LinkObject extends Extensable { /** A relative or absolute URI reference to an OAS operation. This field is mutually exclusive of the operationId field, and MUST point to an Operation Object. Relative operationRef values MAY be used to locate an existing Operation Object in the OpenAPI definition. See the rules for resolving Relative References. */ operationRef?: string; /** The name of an existing, resolvable OAS operation, as defined with a unique operationId. This field is mutually exclusive of the operationRef field. */ operationId?: string; /** A map representing parameters to pass to an operation as specified with operationId or identified via operationRef. The key is the parameter name to be used, whereas the value can be a constant or an expression to be evaluated and passed to the linked operation. The parameter name can be qualified using the parameter location [{in}.]{name} for operations that use the same parameter name in different locations (e.g. path.id). */ parameters?: { [name: string]: `$${string}` }; /** A literal value or {expression} to use as a request body when calling the target operation. */ requestBody?: `$${string}`; /** A description of the link. CommonMark syntax MAY be used for rich text representation. */ description?: string; /** A server object to be used by the target operation. */ server?: ServerObject; } /** * [4.8.21] Header Object * The Header Object follows the structure of the Parameter Object with the following changes: * * 1. `name` MUST NOT be specified, it is given in the corresponding `headers` map. * 2. `in` MUST NOT be specified, it is implicitly in `header`. * 3. All traits that are affected by the location MUST be applicable to a location of `heade`r (for example, `style`). */ export type HeaderObject = Omit; /** * [4.8.22] Tag Object * Adds metadata to a single tag that is used by the Operation Object. It is not mandatory to have a Tag Object per tag defined in the Operation Object instances. */ export interface TagObject extends Extensable { /** REQUIRED. The name of the tag. */ name: string; /** A description for the tag. CommonMark syntax MAY be used for rich text representation. */ description?: string; /** Additional external documentation for this tag. */ externalDocs?: ExternalDocumentationObject; } /** * [4.8.23] Reference Object * A simple object to allow referencing other components in the OpenAPI document, internally and externally. The $ref string value contains a URI [RFC3986], which identifies the location of the value being referenced. See the rules for resolving Relative References. */ export interface ReferenceObject extends Extensable { /** REQUIRED. The reference identifier. This MUST be in the form of a URI. */ $ref: string; /** A short summary which by default SHOULD override that of the referenced component. If the referenced object-type does not allow a summary field, then this field has no effect. */ summary?: string; /** A description which by default SHOULD override that of the referenced component. CommonMark syntax MAY be used for rich text representation. If the referenced object-type does not allow a description field, then this field has no effect. */ description?: string; } /** * [4.8.24] Schema Object * The Schema Object allows the definition of input and output data types. These types can be objects, but also primitives and arrays. This object is a superset of the JSON Schema Specification Draft 2020-12. */ export type SchemaObject = { /** The Schema Object allows the definition of input and output data types. These types can be objects, but also primitives and arrays. This object is a superset of the JSON Schema Specification Draft 2020-12. */ discriminator?: DiscriminatorObject; /** MAY be used only on properties schemas. It has no effect on root schemas. Adds additional metadata to describe the XML representation of this property. */ xml?: XMLObject; /** Additional external documentation for this schema. */ externalDocs?: ExternalDocumentationObject; /** @deprecated */ example?: any; title?: string; description?: string; $comment?: string; deprecated?: boolean; readOnly?: boolean; writeOnly?: boolean; enum?: unknown[]; /** Use of this keyword is functionally equivalent to an "enum" (Section 6.1.2) with a single value. */ const?: unknown; default?: unknown; format?: string; /** @deprecated in 3.1 (still valid for 3.0) */ nullable?: boolean; oneOf?: (SchemaObject | ReferenceObject)[]; allOf?: (SchemaObject | ReferenceObject)[]; anyOf?: (SchemaObject | ReferenceObject)[]; required?: string[]; [key: `x-${string}`]: any; } & ( | StringSubtype | NumberSubtype | IntegerSubtype | ArraySubtype | BooleanSubtype | NullSubtype | ObjectSubtype | { type: ("string" | "number" | "integer" | "array" | "boolean" | "null" | "object")[]; } ); export interface StringSubtype { type: "string" | ["string", "null"]; enum?: (string | ReferenceObject)[]; } export interface NumberSubtype { type: "number" | ["number", "null"]; minimum?: number; maximum?: number; enum?: (number | ReferenceObject)[]; } export interface IntegerSubtype { type: "integer" | ["integer", "null"]; minimum?: number; maximum?: number; enum?: (number | ReferenceObject)[]; } export interface ArraySubtype { type: "array" | ["array", "null"]; prefixItems?: (SchemaObject | ReferenceObject)[]; items?: SchemaObject | ReferenceObject | (SchemaObject | ReferenceObject)[]; minItems?: number; maxItems?: number; enum?: (SchemaObject | ReferenceObject)[]; } export interface BooleanSubtype { type: "boolean" | ["boolean", "null"]; enum?: (boolean | ReferenceObject)[]; } export interface NullSubtype { type: "null"; } export interface ObjectSubtype { type: "object" | ["object", "null"]; properties?: { [name: string]: SchemaObject | ReferenceObject }; additionalProperties?: boolean | Record | SchemaObject | ReferenceObject; required?: string[]; allOf?: (SchemaObject | ReferenceObject)[]; anyOf?: (SchemaObject | ReferenceObject)[]; enum?: (SchemaObject | ReferenceObject)[]; $defs?: $defs; } /** * [4.8.25] Discriminator Object * When request bodies or response payloads may be one of a number of different schemas, a discriminator object can be used to aid in serialization, deserialization, and validation. The discriminator is a specific object in a schema which is used to inform the consumer of the document of an alternative schema based on the value associated with it. */ export interface DiscriminatorObject { /** REQUIRED. The name of the property in the payload that will hold the discriminator value. */ propertyName: string; /** An object to hold mappings between payload values and schema names or references. */ mapping?: Record; /** If this exists, then a discriminator type should be added to objects matching this path */ oneOf?: string[]; } /** * [4.8.26] XML Object * A metadata object that allows for more fine-tuned XML model definitions. When using arrays, XML element names are not inferred (for singular/plural forms) and the `name` property SHOULD be used to add that information. See examples for expected behavior. */ export interface XMLObject extends Extensable { /** Replaces the name of the element/attribute used for the described schema property. When defined within `items`, it will affect the name of the individual XML elements within the list. When defined alongside `type` being `array` (outside the `items`), it will affect the wrapping element and only if `wrapped` is `true`. If `wrapped` is `false`, it will be ignored. */ name?: string; /** The URI of the namespace definition. This MUST be in the form of an absolute URI. */ namespace?: string; /** The prefix to be used for the name. */ prefix?: string; /** Declares whether the property definition translates to an attribute instead of an element. Default value is `false`. */ attribute?: boolean; /** MAY be used only for an array definition. Signifies whether the array is wrapped (for example, ``) or unwrapped (``). Default value is `false`. The definition takes effect only when defined alongside `type` being `array` (outside the `items`). */ wrapped?: boolean; } /** * [4.8.27] Security Scheme Object * Defines a security scheme that can be used by the operations. */ export type SecuritySchemeObject = { /** A description for security scheme. CommonMark syntax MAY be used for rich text representation. */ description?: string; [key: `x-${string}`]: any; } & ( | { /** REQUIRED. The type of the security scheme. */ type: "apiKey"; /** REQUIRED. The name of the header, query or cookie parameter to be used. */ name: string; /** REQUIRED. The location of the API key. */ in: "query" | "header" | "cookie"; } | { /** REQUIRED. The type of the security scheme. */ type: "http"; /** REQUIRED. The name of the HTTP Authorization scheme to be used in the Authorization header as defined in [RFC7235]. The values used SHOULD be registered in the IANA Authentication Scheme registry. */ scheme: string; /** A hint to the client to identify how the bearer token is formatted. Bearer tokens are usually generated by an authorization server, so this information is primarily for documentation purposes. */ bearer?: string; } | { /** REQUIRED. The type of the security scheme. */ type: "mutualTLS"; } | { /** REQUIRED. Tye type of the security scheme. */ type: "oauth2"; /** REQUIRED. An object containing configuration information for the flow types supported. */ flows: OAuthFlowsObject; } | { /** REQUIRED. Tye type of the security scheme. */ type: "openIdConnect"; /** REQUIRED. OpenId Connect URL to discover OAuth2 configuration values. This MUST be in the form of a URL. The OpenID Connect standard requires the use of TLS. */ openIdConnectUrl: string; } ); /** * [4.8.26] OAuth Flows Object * Allows configuration of the supported OAuth Flows. */ export interface OAuthFlowsObject extends Extensable { /** Configuration for the OAuth Implicit flow */ implicit?: OAuthFlowObject; /** Configuration for the OAuth Resource Owner Password flow */ password?: OAuthFlowObject; /** Configuration for the OAuth Client Credentials flow. Previously called `application` in OpenAPI 2.0. */ clientCredentials?: OAuthFlowObject; /** Configuration for the OAuth Authorization Code flow. Previously called `accessCode` in OpenAPI 2.0. */ authorizationCode?: OAuthFlowObject; } /** * [4.8.29] OAuth Flow Object * Configuration details for a supported OAuth Flow */ export interface OAuthFlowObject extends Extensable { /** REQUIRED. The authorization URL to be used for this flow. This MUST be in the form of a URL. The OAuth2 standard requires the use of TLS. */ authorizationUrl: string; /** REQUIRED. The token URL to be used for this flow. This MUST be in the form of a URL. The OAuth2 standard requires the use of TLS. */ tokenUrl: string; /** The URL to be used for obtaining refresh tokens. This MUST be in the form of a URL. The OAuth2 standard requires the use of TLS. */ refreshUrl: string; /** REQUIRED. The available scopes for the OAuth2 security scheme. A map between the scope name and a short description for it. The map MAY be empty. */ scopes: { [name: string]: string }; } /** * [4.8.30] Security Requirements Object * Lists the required security schemes to execute this operation. The name used for each property MUST correspond to a security scheme declared in the Security Schemes under the Components Object. */ export type SecurityRequirementObject = { [P in keyof ComponentsObject["securitySchemes"]]?: string[]; }; export type $defs = Record; ================================================ FILE: src/types/openapi.ts ================================================ // import type { ApiReferenceConfiguration as ScalarConfig } from "@scalar/api-reference"; /** * Nitro OpenAPI configuration */ export interface NitroOpenAPIConfig { /** * OpenAPI meta information */ meta?: { title?: string; description?: string; version?: string; }; /** * OpenAPI json route * * Default is `/_openapi.json` */ route?: string; /** * Enable OpenAPI generation for production builds */ production?: false | "runtime" | "prerender"; /** * UI configurations */ ui?: { /** * Scalar UI configuration */ scalar?: | false | (Partial & { /** * Scalar UI route * * Default is `/_scalar` */ route?: string; }); /** * Swagger UI configuration */ swagger?: | false | { /** * Swagger UI route * * Default is `/_swagger` */ route?: string; }; }; } ================================================ FILE: src/types/prerender.ts ================================================ import type { HTTPError } from "h3"; export interface PrerenderRoute { route: string; contents?: string; data?: ArrayBuffer; fileName?: string; error?: Partial; generateTimeMS?: number; skip?: boolean; contentType?: string; } /** @deprecated Internal type will be removed in future versions */ export type PrerenderGenerateRoute = PrerenderRoute; ================================================ FILE: src/types/preset.ts ================================================ import type { DateString } from "compatx"; import type { ProviderName } from "std-env"; import type { NitroConfig } from "./config.ts"; export type NitroPreset = NitroConfig | (() => NitroConfig); export interface NitroPresetMeta { name: string; stdName?: ProviderName; aliases?: string[]; static?: boolean; dev?: boolean; compatibilityDate?: DateString; } ================================================ FILE: src/types/route-rules.ts ================================================ import type { Middleware, ProxyOptions, BasicAuthOptions } from "h3"; import type { ExcludeFunctions, IntRange } from "./_utils.ts"; import type { CachedEventHandlerOptions } from "./runtime/index.ts"; export type HTTPstatus = IntRange<100, 600>; export interface NitroRouteConfig { cache?: ExcludeFunctions | false; headers?: Record; redirect?: string | { to: string; status?: HTTPstatus }; prerender?: boolean; proxy?: string | ({ to: string } & ProxyOptions); isr?: number /* expiration */ | boolean | VercelISRConfig; basicAuth?: Pick | false; // Shortcuts cors?: boolean; swr?: boolean | number; static?: boolean | number; } export interface NitroRouteRules extends Omit< NitroRouteConfig, "redirect" | "cors" | "swr" | "static" > { redirect?: { to: string; status: HTTPstatus }; proxy?: { to: string } & ProxyOptions; [key: string]: any; } export type MatchedRouteRule = { name: K; options: Exclude; route: string; params?: Record; handler?: (opts: unknown) => Middleware; }; export type MatchedRouteRules = { [K in keyof NitroRouteRules]: MatchedRouteRule; }; // https://vercel.com/docs/build-output-api/primitives#prerender-configuration-file export interface VercelISRConfig { /** * (vercel) * Expiration time (in seconds) before the cached asset will be re-generated by invoking the Serverless Function. * Setting the value to `false` (or `isr: true` route rule) means it will never expire. */ expiration?: number | false; /** * (vercel) * Group number of the asset. * Prerender assets with the same group number will all be re-validated at the same time. */ group?: number; /** * (vercel) * List of query string parameter names that will be cached independently. * - If an empty array, query values are not considered for caching. * - If undefined each unique query value is cached independently * - For wildcard `/**` route rules, `url` is always added. */ allowQuery?: string[]; /** * (vercel) * When `true`, the query string will be present on the `request` argument passed to the invoked function. The `allowQuery` filter still applies. */ passQuery?: boolean; /** * (vercel) * * When `true`, expose the response body regardless of status code including error status codes. (default `false`) */ exposeErrBody?: boolean; } ================================================ FILE: src/types/runner.ts ================================================ export type { FetchHandler, RunnerMessageListener, UpgradeHandler, RunnerRPCHooks, WorkerAddress, WorkerHooks, EnvRunner, } from "env-runner"; ================================================ FILE: src/types/runtime/asset.ts ================================================ export interface PublicAsset { type: string; etag: string; mtime: string; path: string; size: number; encoding?: string; data?: string; } export interface AssetMeta { type?: string; etag?: string; mtime?: string; } ================================================ FILE: src/types/runtime/cache.ts ================================================ import type { HTTPEvent } from "h3"; export type { CacheEntry, CacheOptions, ResponseCacheEntry } from "ocache"; export interface CachedEventHandlerOptions extends Omit< import("ocache").CachedEventHandlerOptions, "toResponse" | "createResponse" | "handleCacheHeaders" > {} ================================================ FILE: src/types/runtime/index.ts ================================================ export * from "./asset.ts"; export * from "./cache.ts"; export * from "./nitro.ts"; export * from "./task.ts"; ================================================ FILE: src/types/runtime/nitro.ts ================================================ import type { H3Core, HTTPEvent } from "h3"; import type { HookableCore } from "hookable"; import type { ServerRequest } from "srvx"; export interface NitroApp { fetch: (req: Request) => Response | Promise; h3?: H3Core; hooks?: HookableCore; captureError?: CaptureError; } export interface NitroAppPlugin { ( nitro: NitroApp & { hooks: NonNullable; } ): void; } export interface NitroAsyncContext { request: ServerRequest; } export interface RenderResponse { body: any; status: number; statusText: string; headers: Record; } export type RenderHandler = ( event: HTTPEvent ) => Partial | Promise>; export interface RenderContext { event: HTTPEvent; render: RenderHandler; response?: Partial; } export interface CapturedErrorContext { event?: HTTPEvent; tags?: string[]; } export type CaptureError = (error: Error, context: CapturedErrorContext) => void; export interface NitroRuntimeHooks { close: () => void; error: CaptureError; request: (event: HTTPEvent) => void | Promise; response: (res: Response, event: HTTPEvent) => void | Promise; } ================================================ FILE: src/types/runtime/task.ts ================================================ type MaybePromise = T | Promise; /** @experimental */ export interface TaskContext {} /** @experimental */ export interface TaskPayload { [key: string]: unknown; } /** @experimental */ export interface TaskMeta { name?: string; description?: string; } /** @experimental */ export interface TaskEvent { name: string; payload: TaskPayload; context: TaskContext; } /** @experimental */ export interface TaskResult { result?: RT; } /** @experimental */ export interface Task { meta?: TaskMeta; run(event: TaskEvent): MaybePromise<{ result?: RT }>; } /** @experimental */ export interface TaskRunnerOptions { cwd?: string; buildDir?: string; } ================================================ FILE: src/types/srvx.ts ================================================ export type { ServerRequest } from "srvx"; ================================================ FILE: src/utils/compress.ts ================================================ import { existsSync } from "node:fs"; import fsp from "node:fs/promises"; import zlib from "node:zlib"; import { glob } from "tinyglobby"; import mime from "mime"; import type { Nitro } from "nitro/types"; import { resolve } from "pathe"; const EncodingMap = { gzip: ".gz", br: ".br", zstd: ".zst" } as const; export async function compressPublicAssets(nitro: Nitro) { const publicFiles = await glob("**", { cwd: nitro.options.output.publicDir, absolute: false, dot: true, ignore: ["**/*.gz", "**/*.br", "**/*.zst"], }); await Promise.all( publicFiles.map(async (fileName) => { const compressPublicAssets = nitro.options.compressPublicAssets; if (compressPublicAssets === false) { return; } const { gzip = false, brotli = false, zstd = false, } = compressPublicAssets === true ? { gzip: true, brotli: true, zstd: true } : compressPublicAssets; const zstdSupported = zlib.zstdCompress !== undefined; const filePath = resolve(nitro.options.output.publicDir, fileName); if ( (gzip && existsSync(filePath + EncodingMap.gzip)) || (brotli && existsSync(filePath + EncodingMap.br)) || (zstd && zstdSupported && existsSync(filePath + EncodingMap.zstd)) ) { return; } const mimeType = mime.getType(fileName) || "text/plain"; const fileContents = await fsp.readFile(filePath); if ( fileContents.length < 1024 || fileName.endsWith(".map") || !isCompressibleMime(mimeType) ) { return; } const encodings = [ gzip && ("gzip" as const), brotli && ("br" as const), zstd && zstdSupported && ("zstd" as const), ].filter((v): v is keyof typeof EncodingMap => v !== false); await Promise.all( encodings.map(async (encoding) => { const suffix = EncodingMap[encoding]; const compressedPath = filePath + suffix; if (existsSync(compressedPath)) { return; } const brotliOptions = { [zlib.constants.BROTLI_PARAM_MODE]: isTextMime(mimeType) ? zlib.constants.BROTLI_MODE_TEXT : zlib.constants.BROTLI_MODE_GENERIC, [zlib.constants.BROTLI_PARAM_QUALITY]: zlib.constants.BROTLI_DEFAULT_QUALITY, [zlib.constants.BROTLI_PARAM_SIZE_HINT]: fileContents.length, }; const compressedBuff: Buffer = await new Promise((resolve, reject) => { const cb = (error: Error | null, result: Buffer) => error ? reject(error) : resolve(result); if (encoding === "gzip") { zlib.gzip(fileContents, cb); } else if (encoding === "br") { zlib.brotliCompress(fileContents, brotliOptions, cb); } else if (zstdSupported) { zlib.zstdCompress(fileContents, cb); } }); await fsp.writeFile(compressedPath, compressedBuff); }) ); }) ); } function isTextMime(mimeType: string) { return /text|javascript|json|xml/.test(mimeType); } // Reference list of compressible MIME types from AWS // https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/ServingCompressedFiles.html#compressed-content-cloudfront-file-types const COMPRESSIBLE_MIMES_RE = new Set([ "application/dash+xml", "application/eot", "application/font", "application/font-sfnt", "application/javascript", "application/json", "application/opentype", "application/otf", "application/pdf", "application/pkcs7-mime", "application/protobuf", "application/rss+xml", "application/truetype", "application/ttf", "application/vnd.apple.mpegurl", "application/vnd.mapbox-vector-tile", "application/vnd.ms-fontobject", "application/wasm", "application/xhtml+xml", "application/xml", "application/x-font-opentype", "application/x-font-truetype", "application/x-font-ttf", "application/x-httpd-cgi", "application/x-javascript", "application/x-mpegurl", "application/x-opentype", "application/x-otf", "application/x-perl", "application/x-ttf", "font/eot", "font/opentype", "font/otf", "font/ttf", "image/svg+xml", "text/css", "text/csv", "text/html", "text/javascript", "text/js", "text/plain", "text/richtext", "text/tab-separated-values", "text/xml", "text/x-component", "text/x-java-source", "text/x-script", "vnd.apple.mpegurl", ]); function isCompressibleMime(mimeType: string) { return COMPRESSIBLE_MIMES_RE.has(mimeType); } ================================================ FILE: src/utils/dep.ts ================================================ import { consola } from "consola"; import { resolveModulePath } from "exsolve"; import { isCI, isTest } from "std-env"; export async function importDep( opts: { id: string; dir: string; reason: string; }, _retry?: boolean ): Promise { const resolved = resolveModulePath(opts.id, { from: [opts.dir, import.meta.url], cache: _retry ? false : true, try: true, }); if (resolved) { return (await import(resolved)) as Promise; } let shouldInstall: boolean | undefined; if (_retry || isTest) { shouldInstall = false; // Do not install dependencies in test mode } else if (isCI) { consola.info( `\`${opts.id}\` is required for ${opts.reason}. Installing automatically in CI environment...` ); shouldInstall = true; // Auto install in CI environments } else { shouldInstall = await consola.prompt( `\`${opts.id}\` is required for ${opts.reason}, but it is not installed. Would you like to install it?`, { type: "confirm", default: true, cancel: "undefined" } ); } if (!shouldInstall) { throw new Error( `\`${opts.id}\` is not installed. Please add it to your dependencies for ${opts.reason}.` ); } const start = Date.now(); consola.start(`Installing \`${opts.id}\` in \`${opts.dir}\`...`); const { addDevDependency } = await import("nypm"); await addDevDependency(opts.id, { cwd: opts.dir }); consola.success(`Installed \`${opts.id}\` in ${opts.dir} (${Date.now() - start}ms).`); return importDep(opts, true); } ================================================ FILE: src/utils/fs-tree.ts ================================================ import { promises as fsp } from "node:fs"; import { colors } from "consola/utils"; import { glob } from "tinyglobby"; import { gzipSize } from "gzip-size"; import { dirname, relative, resolve } from "pathe"; import prettyBytes from "pretty-bytes"; import { isTest } from "std-env"; import { runParallel } from "./parallel.ts"; export async function generateFSTree(dir: string, options: { compressedSizes?: boolean } = {}) { if (isTest) { return; } const files = await glob("**/*.*", { cwd: dir, ignore: ["*.map"] }); const items: { file: string; path: string; size: number; gzip: number }[] = []; await runParallel( new Set(files), async (file) => { const path = resolve(dir, file); const src = await fsp.readFile(path); const size = src.byteLength; const gzip = options.compressedSizes ? await gzipSize(src) : 0; items.push({ file, path, size, gzip }); }, { concurrency: 10 } ); items.sort((a, b) => a.path.localeCompare(b.path)); let totalSize = 0; let totalGzip = 0; let totalNodeModulesSize = 0; let totalNodeModulesGzip = 0; let treeText = ""; for (const [index, item] of items.entries()) { let dir = dirname(item.file); if (dir === ".") { dir = ""; } const rpath = relative(process.cwd(), item.path); const treeChar = index === items.length - 1 ? "└─" : "├─"; const isNodeModules = item.file.includes("node_modules"); if (isNodeModules) { totalNodeModulesSize += item.size; totalNodeModulesGzip += item.gzip; continue; } treeText += colors.gray(` ${treeChar} ${rpath} (${prettyBytes(item.size)})`); if (options.compressedSizes) { treeText += colors.gray(` (${prettyBytes(item.gzip)} gzip)`); } treeText += "\n"; totalSize += item.size; totalGzip += item.gzip; } treeText += `${colors.cyan("Σ Total size:")} ${prettyBytes(totalSize + totalNodeModulesSize)}`; if (options.compressedSizes) { treeText += ` (${prettyBytes(totalGzip + totalNodeModulesGzip)} gzip)`; } treeText += "\n"; return treeText; } ================================================ FILE: src/utils/fs.ts ================================================ import type { Nitro } from "nitro/types"; import { stat, mkdir, writeFile as fspWriteFile } from "node:fs/promises"; import { dirname } from "pathe"; import consola from "consola"; import { colors } from "consola/utils"; import { getProperty } from "dot-prop"; import { relative, resolve } from "pathe"; export function prettyPath(p: string, highlight = true) { p = relative(process.cwd(), p); return highlight ? colors.cyan(p) : p; } export function resolveNitroPath( path: string, nitroOptions: Nitro["options"], base?: string ): string { if (typeof path !== "string") { throw new TypeError("Invalid path: " + path); } // TODO: Skip if no template used path = _compilePathTemplate(path)(nitroOptions); for (const base in nitroOptions.alias) { if (path.startsWith(base)) { path = nitroOptions.alias[base] + path.slice(base.length); } } // eslint-disable-next-line no-control-regex if (/^[#\u0000]/.test(path)) { return path; } return resolve(base || nitroOptions.rootDir, path); } function _compilePathTemplate(contents: string) { return (params: Record) => contents.replace(/{{ ?([\w.]+) ?}}/g, (_, match) => { const val = getProperty, string>(params, match); if (!val) { consola.warn(`cannot resolve template param '${match}' in ${contents.slice(0, 20)}`); } return val || `${match}`; }); } export async function writeFile(file: string, contents: Buffer | string, log = false) { await mkdir(dirname(file), { recursive: true }); await fspWriteFile(file, contents, typeof contents === "string" ? "utf8" : undefined); if (log) { consola.info("Generated", prettyPath(file)); } } export async function isDirectory(path: string) { try { return (await stat(path)).isDirectory(); } catch { return false; } } ================================================ FILE: src/utils/parallel.ts ================================================ export async function runParallel( inputs: Set, cb: (input: T) => unknown | Promise, opts: { concurrency: number; interval?: number } ): Promise<{ errors: unknown[] }> { const errors: unknown[] = []; const tasks = new Set>(); function queueNext(): undefined | Promise { const route = inputs.values().next().value; if (!route) { return; } inputs.delete(route); const task = ( opts.interval ? new Promise((resolve) => setTimeout(resolve, opts.interval)) : Promise.resolve() ) .then(() => cb(route)) .catch((error) => { console.error(error); errors.push(error); }); tasks.add(task); return task.then(() => { tasks.delete(task); if (inputs.size > 0) { return refillQueue(); } }); } function refillQueue(): Promise { const workers = Math.min(opts.concurrency - tasks.size, inputs.size); return Promise.all(Array.from({ length: workers }, () => queueNext())); } await refillQueue(); return { errors }; } ================================================ FILE: src/utils/regex.ts ================================================ import { isWindows } from "std-env"; export function escapeRegExp(string: string): string { return string.replace(/[-\\^$*+?.()|[\]{}]/g, String.raw`\$&`); } export function pathRegExp(string: string): string { if (isWindows) { string = string.replace(/\\/g, "/"); } let escaped = escapeRegExp(string); if (isWindows) { escaped = escaped.replace(/\//g, String.raw`[/\\]`); } return escaped; } export function toPathRegExp(input: string | RegExp): RegExp { if (input instanceof RegExp) { return input; } if (typeof input === "string") { return new RegExp(pathRegExp(input)); } throw new TypeError("Expected a string or RegExp", { cause: input }); } ================================================ FILE: src/vite.ts ================================================ export { nitro } from "./build/vite/plugin.ts"; export type { NitroPluginConfig, ServiceConfig } from "./build/vite/types.ts"; ================================================ FILE: test/examples.test.ts ================================================ import { join } from "node:path"; import { readdir } from "node:fs/promises"; import { fileURLToPath, pathToFileURL } from "node:url"; import { toRequest } from "h3"; import { describe, test, expect, beforeAll, afterAll } from "vitest"; import type { ViteDevServer } from "vite"; const examplesDir = fileURLToPath(new URL("../examples", import.meta.url)); const { createServer, createBuilder, rolldownVersion } = (await import( process.env.NITRO_VITE_PKG || "vite" )) as typeof import("vite"); const isRolldown = !!rolldownVersion; const skip = new Set([ "websocket", ...(isRolldown ? [ // TODO: Cannot read properties of null (reading 'use') "vite-rsc", ] : [ "vite-rsc", // No tsConfigPaths support in rollup "import-alias", ]), ]); const skipDev = new Set(["auto-imports", "cached-handler"]); const skipProd = new Set(isRolldown ? [] : []); for (const example of await readdir(examplesDir)) { if (example.startsWith("_")) continue; setupTest(example); } function setupTest(name: string) { const rootDir = join(examplesDir, name); describe.skipIf(skip.has(name))(name, () => { type TestContext = { fetch: typeof globalThis.fetch; }; function registerTests(ctx: TestContext, mode: string) { test(`${name} (${mode})`, async () => { const res = await ctx.fetch("/"); const expectedStatus = name === "custom-error-handler" ? 500 : 200; expect(res.status, res.statusText).toBe(expectedStatus); }); } describe.skipIf(skipDev.has(name))(`${name} (dev)`, () => { let server: ViteDevServer; const context: TestContext = {} as any; beforeAll(async () => { process.chdir(rootDir); server = await createServer({ root: rootDir }); await server.listen("0" as unknown as number); const addr = server.httpServer?.address() as { port: number; address: string; family: string; }; const baseURL = `http://${addr.family === "IPv6" ? `[${addr.address}]` : addr.address}:${addr.port}`; context.fetch = (url, opts) => fetch(baseURL + url, opts); }, 30_000); afterAll(async () => { await server?.close(); }); registerTests(context, "dev"); }); describe.skipIf(skipProd.has(name))(`${name} (prod)`, () => { const context: TestContext = {} as any; beforeAll(async () => { process.chdir(rootDir); process.env.NITRO_PRESET = "standard"; const builder = await createBuilder({ logLevel: "warn" }); await builder.buildApp(); delete globalThis.__nitro__; const { default: entryMod } = await import( pathToFileURL(join(rootDir, ".output/server/index.mjs")).href ); delete (globalThis as any).document; // Set by nano-jsx! expect(entryMod?.fetch).toBeInstanceOf(Function); context.fetch = (input, init) => entryMod.fetch(toRequest(input, init)); }, 30_000); registerTests(context, "prod"); }); }); } ================================================ FILE: test/fixture/.env ================================================ APP_DOMAIN=test.com NITRO_DYNAMIC=from-env ================================================ FILE: test/fixture/.gitignore ================================================ !.env !node_modules/@fixture vercel.json ================================================ FILE: test/fixture/error.ts ================================================ import { defineErrorHandler } from "nitro"; export default defineErrorHandler(async (error, event, { defaultHandler }) => { if (event.req.url.includes("?json")) { const res = await defaultHandler(error, event, { json: true }); return Response.json({ json: res.body }); } }); ================================================ FILE: test/fixture/exports.cloudflare.ts ================================================ export function myScheduled() { console.log("scheduled!"); } ================================================ FILE: test/fixture/nitro.config.ts ================================================ import { defineConfig } from "nitro"; import { dirname, resolve } from "node:path"; import { existsSync } from "node:fs"; export default defineConfig({ compressPublicAssets: true, compatibilityDate: "latest", serverDir: "server", builder: (process.env.NITRO_BUILDER as any) || "rolldown", // @ts-expect-error __vitePkg__: process.env.NITRO_VITE_PKG, framework: { name: "nitro", version: "3.x" }, imports: { presets: [ { // TODO: move this to built-in preset from: "scule", imports: ["camelCase", "pascalCase", "kebabCase"], }, ], }, sourcemap: true, rollupConfig: { output: { sourcemapPathTransform: (relativeSourcePath, sourcemapPath) => { const sourcemapDir = dirname(sourcemapPath); const sourcePath = resolve(sourcemapDir, relativeSourcePath); return existsSync(sourcePath) ? sourcePath : relativeSourcePath; }, }, }, virtual: { "#virtual-route": () => `export default () => new Response("Hello from virtual entry!")`, }, handlers: [ { route: "/api/test/*/foo", handler: "./server/routes/api/hello.ts", method: "GET", }, { route: "/api/hello2", handler: "./server/routes/api/hello.ts", middleware: true, }, { route: "/virtual", handler: "#virtual-route", }, ], devProxy: { "/proxy/example": { target: "https://icanhazip.com", changeOrigin: true, ignorePath: true, }, }, traceDeps: ["@fixture"], serverAssets: [ { baseName: "files", dir: "server/files", }, ], ignore: ["routes/api/**/_*", "middleware/_ignored.ts", "routes/_*.ts", "**/_*.txt"], runtimeConfig: { dynamic: "initial", url: "https://{{APP_DOMAIN}}", }, publicAssets: [ { baseURL: "build", dir: "public/build", maxAge: 3600, }, ], tasks: { "db:migrate": { description: "Migrate database" }, "db:seed": { description: "Seed database" }, }, errorHandler: "error.ts", routeRules: { "/api/param/prerender4": { prerender: true }, "/api/param/prerender2": { prerender: false }, "/rules/headers": { headers: { "cache-control": "s-maxage=60" } }, "/rules/cors": { cors: true, headers: { "access-control-allow-methods": "GET" }, }, "/rules/dynamic": { cache: false, isr: false }, "/rules/redirect": { redirect: "/base" }, "/rules/isr/**": { isr: { allowQuery: ["q"] } }, "/rules/isr-ttl/**": { isr: 60 }, "/rules/swr/**": { swr: true }, "/rules/swr-ttl/**": { swr: 60 }, "/rules/redirect/obj": { redirect: { to: "https://nitro.build/", status: 308 }, }, "/rules/redirect/wildcard/**": { redirect: "https://nitro.build/**" }, "/rules/nested/**": { redirect: "/base", headers: { "x-test": "test" } }, "/rules/nested/override": { redirect: { to: "/other" } }, "/rules/_/noncached/cached": { swr: true }, "/rules/_/noncached/**": { swr: false, cache: false, isr: false }, "/rules/_/cached/noncached": { cache: false, swr: false, isr: false }, "/rules/_/cached/**": { swr: true }, "/api/proxy/**": { proxy: "/api/echo" }, "/cdn/**": { proxy: "https://cdn.jsdelivr.net/**" }, "/rules/basic-auth/**": { basicAuth: { username: "admin", password: "secret", realm: "Secure Area" }, }, "/rules/basic-auth/no-auth/**": { basicAuth: false }, "**": { headers: { "x-test": "test" } }, }, prerender: { crawlLinks: true, ignore: [ // '/api/param/' ], routes: ["/prerender", "/prerender-custom.html", "/404"], }, experimental: { openAPI: true, asyncContext: true, envExpansion: true, database: true, tasks: true, }, scheduledTasks: { "* * * * *": "test", }, cloudflare: { pages: { routes: { include: ["/*"], exclude: ["/blog/static/*", "/cf-pages-exclude/*"], }, }, wrangler: { compatibility_date: "2024-01-01", }, }, typescript: { generateRuntimeConfigTypes: true, generateTsConfig: true, }, openAPI: { production: "prerender", meta: { title: "Nitro Test Fixture", description: "Nitro Test Fixture API", version: "2.0", }, }, }); ================================================ FILE: test/fixture/package.json ================================================ { "name": "nitro-test-fixture", "version": "1.0.0", "type": "module", "scripts": { "build": "nitro build", "preview": "nitro preview", "dev": "nitro dev" }, "devDependencies": { "mono-jsx": "^0.8.2", "nitro": "latest" } } ================================================ FILE: test/fixture/public/_ignored.txt ================================================ This file should be ignored! ================================================ FILE: test/fixture/public/_unignored.txt ================================================ This file should not be ignored! ================================================ FILE: test/fixture/public/build/test.txt ================================================ Works! ================================================ FILE: test/fixture/public/cf-pages-exclude/not-in-routes-json.txt ================================================ This file shouldn't be under "exclude" in the _routes.json outputted by Cloudflare Pages builds. It's covered by the "/cf-pages-exclude/*" wildcard. ================================================ FILE: test/fixture/public/foo.css ================================================ ================================================ FILE: test/fixture/public/foo.js ================================================ const hello = "world"; ================================================ FILE: test/fixture/server/assets/test.json ================================================ { "foo": "bar" } ================================================ FILE: test/fixture/server/assets/test.md ================================================ # Hello world Use `process.env.NODE_ENV` to ... ================================================ FILE: test/fixture/server/files/index.html ================================================

    nitro is amazing!

    ================================================ FILE: test/fixture/server/files/sql.sql ================================================ -- ================================================ FILE: test/fixture/server/files/sqlts.sql.ts ================================================ export default "--\n"; ================================================ FILE: test/fixture/server/files/test.txt ================================================ this is an asset from a text file from nitro ================================================ FILE: test/fixture/server/middleware/_ignored.ts ================================================ import { HTTPError } from "nitro/h3"; export default () => { throw new HTTPError("This file should be ignored!"); }; ================================================ FILE: test/fixture/server/plugins/errors.ts ================================================ import { definePlugin } from "nitro"; export const allErrors: { error: Error; context: any }[] = []; export default definePlugin((app) => { app.hooks.hook("error", (error, context) => { allErrors.push({ error, context }); }); }); ================================================ FILE: test/fixture/server/plugins/vary.ts ================================================ import { definePlugin } from "nitro"; export default definePlugin((app) => { app.hooks.hook("response", (res, event) => { const { pathname } = new URL(event.req.url); if (pathname.endsWith(".css") || pathname.endsWith(".js")) { res.headers.append("Vary", "Origin"); } }); }); ================================================ FILE: test/fixture/server/routes/(route-group)/route-group.ts ================================================ export default () => { return "Hi from inside group"; }; ================================================ FILE: test/fixture/server/routes/500.ts ================================================ import { HTTPError } from "nitro/h3"; export default () => { throw new HTTPError({ status: 500, statusText: "Test Error" }); }; ================================================ FILE: test/fixture/server/routes/api/_ignored.ts ================================================ import { HTTPError } from "nitro/h3"; export default () => { throw new HTTPError("This file should be ignored!"); }; ================================================ FILE: test/fixture/server/routes/api/cached.ts ================================================ import { defineCachedHandler } from "nitro/cache"; export default defineCachedHandler( (event) => { return { timestamp: Date.now(), eventContextCache: event.context.cache, }; }, { swr: true, maxAge: 60 } ); ================================================ FILE: test/fixture/server/routes/api/db.ts ================================================ import { useDatabase } from "nitro/database"; export default async () => { const db = useDatabase(); // Create users table await db.sql`DROP TABLE IF EXISTS users`; await db.sql`CREATE TABLE IF NOT EXISTS users ("id" TEXT PRIMARY KEY, "firstName" TEXT, "lastName" TEXT, "email" TEXT)`; // Add a new user const userId = "1001"; await db.sql`INSERT INTO users VALUES (${userId}, 'John', 'Doe', '')`; // Query for users const { rows } = await db.sql`SELECT * FROM users WHERE id = ${userId}`; return { rows, }; }; ================================================ FILE: test/fixture/server/routes/api/echo.ts ================================================ import { defineHandler } from "nitro/h3"; export default defineHandler((event) => { return { url: event.path, method: event.method, headers: Object.fromEntries(event.headers.entries()), }; }); ================================================ FILE: test/fixture/server/routes/api/headers.ts ================================================ import { defineHandler, setCookie } from "nitro/h3"; export default defineHandler((event) => { event.res.headers.set("x-foo", "bar"); event.res.headers.append("x-array", "foo"); event.res.headers.append("x-array", "bar"); // setHeader(event, "Set-Cookie", "foo=bar, bar=baz"); event.res.headers.append("Set-Cookie", "foo=bar"); event.res.headers.append("Set-Cookie", "bar=baz"); setCookie(event, "test", "value"); setCookie(event, "test2", "value"); return "headers sent"; }); ================================================ FILE: test/fixture/server/routes/api/hello.ts ================================================ export default () => ({ message: "Hello API" }); ================================================ FILE: test/fixture/server/routes/api/hey/index.get.ts ================================================ import { defineHandler } from "nitro/h3"; export default defineHandler((event) => { event.res.headers.set("Content-Type", "text/html"); return "Hey API"; }); ================================================ FILE: test/fixture/server/routes/api/kebab.ts ================================================ import { kebabCase } from "scule"; export default () => kebabCase("HelloWorld"); ================================================ FILE: test/fixture/server/routes/api/meta/test.ts ================================================ import { defineRouteMeta } from "nitro"; defineRouteMeta({ openAPI: { tags: ["test"], description: "Test route description", parameters: [ { in: "query", name: "test", required: true }, { in: "query", name: "val", schema: { type: "integer", enum: [0, 1] }, }, ], responses: { 200: { description: "result", content: { "application/json": { schema: { $ref: "#/components/schemas/Test" } }, }, }, }, $global: { components: { schemas: { Test: { type: "object", properties: { status: { type: "string", enum: ["OK", "ERROR"], }, }, }, }, }, }, }, }); export default () => { return { status: "OK", }; }; ================================================ FILE: test/fixture/server/routes/api/methods/foo.get.get.ts ================================================ export default () => "foo.get"; ================================================ FILE: test/fixture/server/routes/api/methods/get.ts ================================================ export default () => "get"; ================================================ FILE: test/fixture/server/routes/api/param/[test-id].ts ================================================ import { defineHandler } from "nitro/h3"; export default defineHandler((event) => { event.res.headers.set("Content-Type", "text/plain; custom"); return event.context.params!["test-id"]; }); ================================================ FILE: test/fixture/server/routes/api/storage/item.get.ts ================================================ import { defineHandler } from "nitro/h3"; import { useStorage } from "nitro/storage"; export default defineHandler(async (event) => { const base = event.url.searchParams.get("base") || ""; const key = event.url.searchParams.get("key") || ""; const storage = useStorage(`test:${base}`); if (!key || key.endsWith(":")) { return await storage.getKeys(); } const value = await storage.getItem(key); return value; }); ================================================ FILE: test/fixture/server/routes/api/storage/item.put.ts ================================================ import { defineHandler } from "nitro/h3"; import { useStorage } from "nitro/storage"; export default defineHandler(async (event) => { const base = event.url.searchParams.get("base") || ""; const key = event.url.searchParams.get("key") || ""; const storage = useStorage(`test:${base}`); const value = await event.req.text(); await storage.setItem(key, value); return value; }); ================================================ FILE: test/fixture/server/routes/api/upload.post.ts ================================================ export default () => { return "uploaded!"; }; ================================================ FILE: test/fixture/server/routes/api/wildcard/[...param].ts ================================================ import { defineHandler } from "nitro/h3"; export default defineHandler((event) => { return event.context.params!.param as string; }); ================================================ FILE: test/fixture/server/routes/assets/[id].ts ================================================ import { defineHandler, HTTPError } from "nitro/h3"; import { useStorage } from "nitro/storage"; export default defineHandler(async (event) => { const serverAssets = useStorage("assets/server"); const id = event.context.params!.id; if (!(await serverAssets.hasItem(id))) { throw new HTTPError({ message: `Asset ${id} not found`, status: 404 }); } const meta = (await serverAssets.getMeta(event.context.params!.id)) as unknown as { type: string; etag: string; mtime: string; }; if (meta.type) { event.res.headers.set("content-type", meta.type); } if (meta.etag) { event.res.headers.set("etag", meta.etag); } if (meta.mtime) { event.res.headers.set("last-modified", meta.mtime); } return serverAssets.getItemRaw(event.context.params!.id); }); ================================================ FILE: test/fixture/server/routes/assets/all.ts ================================================ import { useStorage } from "nitro/storage"; export default async () => { const serverAssets = useStorage("assets/server"); const keys = await serverAssets.getKeys(); const items = await Promise.all( keys.map(async (key) => { return { key, meta: await serverAssets.getMeta(key), data: await serverAssets.getItem(key).then((r) => // prettier-ignore typeof r === "string" ? r.slice(0, 32) : (isPureObject(r) ? r : ``) ), }; }) ); return items; }; function isPureObject(value: unknown): boolean { return value !== null && typeof value === "object" && value.constructor === Object; } ================================================ FILE: test/fixture/server/routes/assets/md.ts ================================================ export default async () => { const md = await import("raw:../../assets/test.md" as string).then((r) => r.default); return md; }; ================================================ FILE: test/fixture/server/routes/config.ts ================================================ import { useRuntimeConfig } from "nitro/runtime-config"; const sharedRuntimeConfig = useRuntimeConfig(); export default () => { const runtimeConfig = useRuntimeConfig(); return { runtimeConfig, sharedRuntimeConfig, }; }; ================================================ FILE: test/fixture/server/routes/context.ts ================================================ import { defineEventHandler } from "nitro/h3"; import { useRequest } from "nitro/context"; export default defineEventHandler(async () => { await Promise.resolve(setTimeout(() => {}, 10)); return await useTest(); }); function useTest() { const url = new URL(useRequest().url); return { context: { path: url.pathname + url.search, }, }; } ================================================ FILE: test/fixture/server/routes/env/index.dev.ts ================================================ export default () => "dev env"; ================================================ FILE: test/fixture/server/routes/env/index.get.prod.ts ================================================ export default () => "prod env"; ================================================ FILE: test/fixture/server/routes/errors/captured.ts ================================================ import { allErrors } from "../../plugins/errors.ts"; export default () => { return { allErrors: allErrors.map((entry) => ({ message: entry.error.message, })), }; }; ================================================ FILE: test/fixture/server/routes/errors/stack.ts ================================================ export default async () => { return { stack: new Error("testing error").stack!.replace(/\\/g, "/"), }; }; ================================================ FILE: test/fixture/server/routes/errors/throw.ts ================================================ import { H3Event, HTTPError } from "nitro/h3"; export default ({ url }: H3Event) => { const unhandled = url.searchParams.has("unhandled"); const shouldThrow = url.searchParams.get("action") === "throw"; const error = unhandled ? new Error("Unhandled error") : new HTTPError({ status: 503, statusText: "Custom Status Text", message: "Handled error", headers: { "x-custom-error": "custom-value" }, data: { custom: "data" }, body: { custom: "body" }, }); if (shouldThrow) { throw error; } return error; }; ================================================ FILE: test/fixture/server/routes/fetch.ts ================================================ import { serverFetch as runtimeServerFetch, fetch as runtimeFetch, useNitroApp } from "nitro/app"; import { serverFetch as nitroServerFetch, fetch as nitroFetch } from "nitro"; export default async () => { const nitroApp = useNitroApp(); return { "nitroApp.fetch": await Promise.resolve( nitroApp.fetch(new Request(new URL("/api/hello", "http://localhost"))) ).then((res) => res.json()), "nitro/runtime.serverFetch": await runtimeServerFetch("/api/hello").then((res) => res.json()), "nitro/runtime.fetch": await runtimeFetch("/api/hello").then((res) => res.json()), "nitro/serverFetch": await nitroServerFetch("/api/hello").then((res) => res.json()), "nitro/fetch": await nitroFetch("/api/hello").then((res) => res.json()), }; }; ================================================ FILE: test/fixture/server/routes/file.ts ================================================ import { defineHandler, getQuery } from "nitro/h3"; import { useStorage } from "nitro/storage"; export default defineHandler(async (event) => { const query = getQuery(event); const filename = query?.filename || "index.html"; const serverAsset = await useStorage().getItem(`assets/files/${filename}`); return serverAsset; }); ================================================ FILE: test/fixture/server/routes/icon.png.ts ================================================ import { defineHandler } from "nitro/h3"; export default defineHandler((event) => { event.res.headers.set("Content-Type", "image/png"); return Buffer.from(_base64ToArray(_getLogoBase64())); }); function _getLogoBase64() { return "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAT9JREFUOE+t081KAlEUwPH/nRkn0pmgZWQgFLTpBYqQVkFUmgTRNnqKkFrZop7Blm4KQq02rVqYr1AILaKPZYIzGY46ExdRNMWP6u4ul/O759x7jggli0fgxQGd0ZYD4liEkh+VXwQ3r3Ik4DV3igBVQNXtnYomQB65rQjoALbndE7DAfZvbe5eq11KJmKSeqxwXnBaZ13ASThAyfHYTJd4sztTGQrYWxjjueQybSjsXFs4bcbQwO6NTSZqkn+vcZgvt9IdGthIW8xPqlxGTOK5MumnRs0jATIgNquTWPYTy1oUivXRAYkklvwsTmlEsxapNWPwL8hHlCU0l67CxbrJi+0yYyqkHgZ8409AQkFD4WprAsMniN9/9u+DXoBEVoI+zlYNDnJ9AE0Bn4Cveu9WHtcabV5r643/GKa/jfM3OT68lZwxK8oAAAAASUVORK5CYII="; } function _base64ToArray(base64: string) { const str = atob(base64); const bytes = new Uint8Array(str.length); for (let i = 0; i < str.length; i++) { bytes[i] = str.charCodeAt(i); } return bytes; } ================================================ FILE: test/fixture/server/routes/imports.ts ================================================ import { testBarUtil } from "../utils/foo/bar/test.ts"; import { testFooUtil } from "../utils/foo/test.ts"; import { testUtil } from "../utils/test.ts"; export default () => { return { testUtil: testUtil(), testNestedUtil: testFooUtil() + testBarUtil(), }; }; ================================================ FILE: test/fixture/server/routes/json-string.ts ================================================ export default () => { return '{"foo":"bar"}'; }; ================================================ FILE: test/fixture/server/routes/jsx.tsx ================================================ export default () => (

    Hello JSX!

    ); ================================================ FILE: test/fixture/server/routes/modules.ts ================================================ // @ts-ignore import depA from "@fixture/nitro-dep-a"; // @ts-ignore import depB from "@fixture/nitro-dep-b"; // @ts-ignore import depLib from "@fixture/nitro-lib"; // @ts-ignore import subpathLib from "@fixture/nitro-lib/subpath"; // @ts-ignore import extraUtils from "@fixture/nitro-utils/extra"; export default () => { return { depA, // expected to all be 1.0.0 depB, // expected to all be 2.0.1 depLib, // expected to all be 2.0.0 subpathLib, // expected to 2.0.0 extraUtils, }; }; ================================================ FILE: test/fixture/server/routes/node-compat.ts ================================================ import nodeAsyncHooks from "node:async_hooks"; import nodeCrypto from "node:crypto"; import nodeTLS from "node:tls"; const nodeCompatTests = { globals: { // eslint-disable-next-line unicorn/prefer-global-this global: () => globalThis.global === global, // eslint-disable-next-line unicorn/prefer-global-this Buffer: () => Buffer && globalThis.Buffer && global.Buffer, // eslint-disable-next-line unicorn/prefer-global-this process: () => process && globalThis.process && global.process, BroadcastChannel: () => !!new BroadcastChannel("test"), }, crypto: { createHash: () => { return nodeCrypto.createHash("sha256").update("hello").digest("hex").startsWith("2cf24"); }, }, async_hooks: { AsyncLocalStorage: async () => { const ctx = new nodeAsyncHooks.AsyncLocalStorage(); const rand = Math.random(); return ctx.run(rand, async () => { await new Promise((r) => r()); if (ctx.getStore() !== rand) { return false; } return true; }); }, }, tls: { connect: async () => { // TODO: Use a local TLS server for testing if ("Bun" in globalThis || "Deno" in globalThis) { return true; } const socket = nodeTLS.connect(443, "1.1.1.1"); await new Promise((r) => socket.on("connect", r)); socket.end(); return true; }, }, }; export default async () => { const results: Record = {}; for (const [group, groupTests] of Object.entries(nodeCompatTests)) { for (const [name, test] of Object.entries(groupTests)) { results[`${group}:${name}`] = await testFn(test); } } return new Response(JSON.stringify(results, null, 2), { headers: { "Content-Type": "application/json", }, }); }; async function testFn(fn: () => any) { try { return !!(await fn()); } catch { return false; } } ================================================ FILE: test/fixture/server/routes/prerender-custom.html.ts ================================================ export default () => { const links = ["/api/hello", "/api/param/foo.json", "/api/param/foo.css"]; return ` Prerendered routes test

    Prerendered routes test #2:

      ${links.map((link) => `
    • ${link}
    • `).join("\n")}
    `; }; ================================================ FILE: test/fixture/server/routes/prerender.ts ================================================ import { defineHandler } from "nitro/h3"; export default defineHandler((event) => { const links = [ "/404", "https://about.google/products/", "/api/hello?bar=baz", "/api/hello?bar=baz&bing=bap", "/api/hello?bar=baz&foo=qux", "/prerender#foo", "../api/hey", "/json-string", event.url.href.includes("?") ? "/api/param/hidden" : "/prerender?withQuery", ]; event.res.headers.append("x-nitro-prerender", "/api/param/prerender1, /api/param/prerender2"); event.res.headers.append("x-nitro-prerender", "/api/param/prerender3"); event.res.headers.set("content-type", "text/html"); return /* html */ ` Prerendered routes test

    Prerendered routes test:

      ${links.map((link) => `
    • ${link}
    • `).join("\n")}
    x-href attr <a href="/500</a> #a #b `; }); ================================================ FILE: test/fixture/server/routes/raw.ts ================================================ // @ts-ignore import sql from "raw:../files/sql.sql"; // https://github.com/nitrojs/nitro/issues/2836 // @ts-ignore import sqlts from "../files/sqlts.sql"; export default async () => { return { sql: sql.trim(), sqlts: sqlts.trim(), }; }; ================================================ FILE: test/fixture/server/routes/replace.ts ================================================ export default () => { // #3672 const { window: window$1 = globalThis } = {}; return { window: typeof window$1 === "function" }; }; ================================================ FILE: test/fixture/server/routes/rules/[...slug].ts ================================================ import { defineHandler } from "nitro/h3"; export default defineHandler((event) => event.url.pathname); ================================================ FILE: test/fixture/server/routes/static-flags.ts ================================================ export default async () => { return { dev: import.meta.dev, preset: import.meta.preset, prerender: import.meta.prerender, nitro: import.meta.nitro, server: import.meta.server, client: import.meta.client, baseURL: import.meta.baseURL, _asyncContext: import.meta._asyncContext, _tasks: import.meta._tasks, }; }; ================================================ FILE: test/fixture/server/routes/stream.ts ================================================ export default () => { const encoder = new TextEncoder(); const stream = new ReadableStream({ start(controller) { controller.enqueue(encoder.encode("nitro")); controller.enqueue(encoder.encode("is")); controller.enqueue(encoder.encode("awesome")); controller.close(); }, }); return stream; }; ================================================ FILE: test/fixture/server/routes/tasks/[...name].ts ================================================ import { defineHandler, getQuery } from "nitro/h3"; import { runTask } from "nitro/task"; export default defineHandler(async (event) => { const name = event.context.params!.name; const payload = { ...getQuery(event) }; const { result } = await runTask(name, { payload }); return { name, payload, result, }; }); ================================================ FILE: test/fixture/server/routes/wait-until.ts ================================================ import { defineHandler } from "nitro/h3"; const timeTakingOperation = async () => { // console.log("wait-until.ts: timeTakingOperation() start"); await new Promise((resolve) => setTimeout(resolve, 100)); // console.log("wait-until.ts: timeTakingOperation() done"); }; export default defineHandler((event) => { event.waitUntil(timeTakingOperation()); return "done"; }); ================================================ FILE: test/fixture/server/routes/wasm/dynamic-import.ts ================================================ import { defineLazyEventHandler, defineHandler } from "nitro/h3"; export default defineLazyEventHandler(async () => { // @ts-ignore const { sum } = await import("unwasm/examples/sum.wasm").then((r) => r.default()); return defineHandler(() => { return `2+3=${sum(2, 3)}`; }); }); ================================================ FILE: test/fixture/server/routes/wasm/static-import.ts ================================================ import { defineHandler, defineLazyEventHandler } from "nitro/h3"; // @ts-ignore import init, { sum } from "unwasm/examples/sum.wasm"; export default defineLazyEventHandler(async () => { await init(); return defineHandler(() => { return `2+3=${sum(2, 3)}`; }); }); ================================================ FILE: test/fixture/server/tasks/db/migrate.ts ================================================ import { defineTask } from "nitro/task"; export default defineTask({ meta: { description: "Run database migrations", }, run() { console.log("Running DB migration task..."); return { result: "Success" }; }, }); ================================================ FILE: test/fixture/server/tasks/test.ts ================================================ import { defineTask } from "nitro/task"; export default defineTask({ meta: { description: "task to debug", }, async run(taskEvent) { console.log("test task", taskEvent); if (taskEvent.payload.wait) { await new Promise((resolve) => setTimeout(resolve, Number(taskEvent.payload.wait))); } if (taskEvent.payload.error) { throw new Error("test error"); } return { result: taskEvent }; }, }); ================================================ FILE: test/fixture/server/utils/foo/bar/test.ts ================================================ export const testBarUtil = () => 12_345; ================================================ FILE: test/fixture/server/utils/foo/test.ts ================================================ export const testFooUtil = () => 1234; ================================================ FILE: test/fixture/server/utils/test.ts ================================================ export const testUtil = () => 123; ================================================ FILE: test/fixture/server.config.ts ================================================ export default { "server-config": true, }; ================================================ FILE: test/fixture/server.ts ================================================ export default { async fetch(req: Request) { const url = new URL(req.url); if (url.pathname === "/") { return new Response("server entry works!"); } return new Response("404 Not Found", { status: 404 }); }, }; ================================================ FILE: test/fixture/tsconfig.json ================================================ { "extends": "nitro/tsconfig", "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "mono-jsx" } } ================================================ FILE: test/fixture/vite.config.ts ================================================ import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()], }); ================================================ FILE: test/fixture/wrangler.toml ================================================ compatibility_flags = ["nodejs_compat"] ================================================ FILE: test/minimal/minimal.test.ts ================================================ import { afterAll, describe, expect, it } from "vitest"; import { createNitro, build, prepare } from "nitro/builder"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; import { mkdir, rm, stat } from "node:fs/promises"; import { glob } from "tinyglobby"; const fixtureDir = fileURLToPath(new URL("./", import.meta.url)); const tmpDir = fileURLToPath(new URL(".tmp", import.meta.url)); // Rounded up const bundleSizes: Record = { rollup: [16, 8], rolldown: [16, 8], vite: [16, 8], vite7: [16, 8], }; describe("minimal fixture", () => { const builders = ["rolldown", "rollup", "vite", "vite7"] as const; const results: any[] = []; for (const builder of builders) { for (const minify of [false, true]) { describe(`${builder} (${minify ? "minified" : "unminified"})`, () => { let buildTime: number, outDir: string; it("build", async () => { outDir = join(tmpDir, "output", builder + (minify ? "-min" : "")); await rm(outDir, { recursive: true, force: true }); await mkdir(outDir, { recursive: true }); const nitro = await createNitro({ rootDir: fixtureDir, minify, output: { dir: outDir }, // @ts-expect-error for testing __vitePkg__: builder, builder: builder.includes("vite") ? "vite" : (builder as "rollup" | "rolldown"), }); await prepare(nitro); const start = Date.now(); await build(nitro); buildTime = Date.now() - start; }); it("server entry works", async () => { const entry = join(outDir, "server/index.mjs"); const { fetch } = await import(entry).then((m) => m.default); const res = await fetch(new Request("http://localhost/")); expect(res.status).toBe(200); expect(await res.text()).toBe("ok"); }); it("bundle size", async () => { const { sizeKB } = await analyzeDir(outDir); const expectedSize = bundleSizes[builder]![minify ? 1 : 0]; expect(Math.round(sizeKB)).within(expectedSize - 1, expectedSize + 1); results.push({ builder: builder + (minify ? " (minified)" : ""), size: sizeKB.toFixed(2) + " kB", time: `${buildTime}ms`, }); }); }); } } if (process.env.TEST_DEBUG) { afterAll(() => { console.table(results); }); } }); async function analyzeDir(cwd: string) { const files = await glob("**/*", { cwd, dot: true }); let sizeBytes = 0; await Promise.all( files.map(async (file) => { const { size } = await stat(join(cwd, file)); sizeBytes += size; }) ); return { sizeBytes, sizeKB: sizeBytes / 1024, fileCount: files.length, }; } ================================================ FILE: test/minimal/nitro.config.ts ================================================ import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ preset: "standard", // sourcemap: false, }); ================================================ FILE: test/minimal/package.json ================================================ { "name": "nitro-test-fixture-minimal", "version": "1.0.0", "type": "module", "scripts": { "test": "TEST_DEBUG=1 vitest" }, "devDependencies": { "nitro": "latest", "vite": "latest" } } ================================================ FILE: test/minimal/server.ts ================================================ export default { fetch(_req: Request) { return new Response("ok"); }, }; ================================================ FILE: test/minimal/tsconfig.json ================================================ { "extends": "nitro/tsconfig" } ================================================ FILE: test/minimal/vite.config.mjs ================================================ import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [!process.env.TEST && nitro()], }); ================================================ FILE: test/presets/aws-lambda.test.ts ================================================ import type { APIGatewayProxyEvent, APIGatewayProxyEventV2 } from "aws-lambda"; import { resolve } from "pathe"; import { describe } from "vitest"; import { parseURL, parseQuery } from "ufo"; import { setupTest, testNitro } from "../tests.ts"; describe("nitro:preset:aws-lambda-v2", async () => { const ctx = await setupTest("aws-lambda"); testNitro(ctx, async () => { const { handler } = await import(resolve(ctx.outDir, "server/index.mjs")); return async ({ url, headers, method, body }) => { const { pathname, search } = parseURL(url); const event = { rawPath: pathname, headers: headers || {}, rawQueryString: search.slice(1), queryStringParameters: parseQuery(search) as Record, body: body || "", isBase64Encoded: false, version: "2", routeKey: "", requestContext: { accountId: "", apiId: "", domainName: "", domainPrefix: "", requestId: "", routeKey: "", stage: "", time: "", timeEpoch: 0, http: { path: url.pathname, protocol: "http", userAgent: "", sourceIp: "", method: method || "GET", }, }, } satisfies APIGatewayProxyEventV2; const res = await handler(event); return webResponse(res); }; }); }); describe("nitro:preset:aws-lambda-v1", async () => { const ctx = await setupTest("aws-lambda"); testNitro({ ...ctx, lambdaV1: true }, async () => { const { handler } = await import(resolve(ctx.outDir, "server/index.mjs")); return async ({ url, headers, method, body }) => { const { pathname, search } = parseURL(url); const event = { stageVariables: {}, resource: "", httpMethod: method || "GET", path: pathname, pathParameters: {}, queryStringParameters: parseQuery(search) as Record, multiValueQueryStringParameters: {}, headers: headers || {}, multiValueHeaders: {}, body: body || "", isBase64Encoded: false, requestContext: {} as any, } satisfies APIGatewayProxyEvent; const res = await handler(event); return webResponse(res); }; }); }); function webResponse(awsResponse: any) { const headers = new Headers(awsResponse.headers); const setCookie = awsResponse?.cookies /* v2 */ ?? awsResponse?.multiValueHeaders /* v1 */?.["set-cookie"] ?? []; headers.delete("set-cookie"); for (const cookie of setCookie) { if (Array.isArray(cookie)) { for (const c of cookie) { headers.append("set-cookie", c); } } else { headers.append("set-cookie", cookie); } } const body = awsResponse.isBase64Encoded ? Buffer.from(awsResponse.body, "base64") : (awsResponse.body as string); return new Response(body, { status: awsResponse.statusCode, headers, }); } ================================================ FILE: test/presets/azure-swa.test.ts ================================================ import { existsSync, promises as fsp } from "node:fs"; import { execa } from "execa"; import { getRandomPort, waitForPort } from "get-port-please"; import { resolve } from "pathe"; import { describe, expect, it } from "vitest"; import { setupTest, testNitro } from "../tests.ts"; describe("nitro:preset:azure-swa", { timeout: 10_000 }, async () => { const customConfig = { routes: [ { route: "/admin", allowedRoles: ["authenticated"], }, { route: "/logout", redirect: "/.auth/logout", }, { route: "/index.html", redirect: "/overridden-index", }, { route: "/", rewrite: "/api/server/overridden", }, ], responseOverrides: { 401: { statusCode: 302, redirect: "/.auth/login/aad", }, }, networking: { allowedIpRanges: ["10.0.0.0/24", "100.0.0.0/32", "192.168.100.0/22"], }, platform: { apiRuntime: "custom-runtime", }, }; const ctx = await setupTest("azure-swa", { config: { azure: { config: customConfig, }, }, }); if (process.env.TEST_AZURE) { testNitro(ctx, async () => { const port = await getRandomPort(); const apiPort = await getRandomPort(); // Avoids conflicts with other tests await new Promise((resolve) => setTimeout(resolve, 500)); // Make sure output is written to disk expect(existsSync(ctx.outDir)); const p = execa( "swa", `start .output/public --api-location .output/server --host 127.0.0.1 --port ${port} --api-port ${apiPort}`.split( " " ), { cwd: resolve(ctx.outDir, ".."), stdio: "inherit", // stderr: "inherit", // stdout: "ignore", } ); ctx.server = { url: `http://127.0.0.1:${port}`, close: () => p.kill(), } as any; await waitForPort(port, { host: "127.0.0.1", retries: 20 }); return async ({ url, ...opts }) => { const res = await ctx.fetch(url, opts); return res; }; }); } const config = await fsp .readFile(resolve(ctx.rootDir, "staticwebapp.config.json"), "utf8") .then((r) => JSON.parse(r)); it("generated the correct config", () => { expect(config).toMatchInlineSnapshot(` { "navigationFallback": { "rewrite": "/api/server", }, "networking": { "allowedIpRanges": [ "10.0.0.0/24", "100.0.0.0/32", "192.168.100.0/22", ], }, "platform": { "apiRuntime": "custom-runtime", }, "responseOverrides": { "401": { "redirect": "/.auth/login/aad", "statusCode": 302, }, }, "routes": [ { "allowedRoles": [ "authenticated", ], "route": "/admin", }, { "redirect": "/.auth/logout", "route": "/logout", }, { "rewrite": "/prerender-custom.html", "route": "/prerender-custom", }, { "rewrite": "/api/hey/index.html", "route": "/api/hey", }, { "rewrite": "/_swagger/index.html", "route": "/_swagger", }, { "rewrite": "/_scalar/index.html", "route": "/_scalar", }, { "rewrite": "/prerender/index.html", "route": "/prerender", }, { "redirect": "/overridden-index", "route": "/index.html", }, { "rewrite": "/api/server/overridden", "route": "/", }, ], } `); }); }); ================================================ FILE: test/presets/bun.test.ts ================================================ import { execa, execaCommandSync } from "execa"; import { getRandomPort, waitForPort } from "get-port-please"; import { resolve } from "pathe"; import { describe } from "vitest"; import { setupTest, testNitro } from "../tests.ts"; const hasBun = execaCommandSync("bun --version", { stdio: "ignore", reject: false }).exitCode === 0; describe.runIf(hasBun)("nitro:preset:bun", async () => { const ctx = await setupTest("bun"); testNitro(ctx, async () => { const port = await getRandomPort(); process.env.PORT = String(port); execa("bun", [resolve(ctx.outDir, "server/index.mjs")], { stdio: "inherit", }); ctx.server = { url: `http://127.0.0.1:${port}`, close: () => { // p.kill() }, } as any; await waitForPort(port); return async ({ url, ...opts }) => { const res = await ctx.fetch(url, opts); return res; }; }); }); ================================================ FILE: test/presets/cloudflare-module.test.ts ================================================ import { promises as fsp } from "node:fs"; import { Miniflare } from "miniflare"; import { resolve } from "pathe"; import { describe, expect, it } from "vitest"; import { setupTest, testNitro } from "../tests.ts"; describe("nitro:preset:cloudflare-module", async () => { const ctx = await setupTest("cloudflare-module"); testNitro(ctx, () => { const mf = new Miniflare({ modules: true, compatibilityDate: "2025-04-01", scriptPath: resolve(ctx.outDir, "server/index.mjs"), modulesRules: [{ type: "CompiledWasm", include: ["**/*.wasm"] }], assets: { directory: resolve(ctx.outDir, "public"), routerConfig: { has_user_worker: true }, assetConfig: { // https://developers.cloudflare.com/workers/static-assets/routing/#routing-configuration html_handling: "auto-trailing-slash" /* default */, not_found_handling: "none" /* default */, }, }, compatibilityFlags: ["nodejs_compat", "no_nodejs_compat_v2"], bindings: { ...ctx.env }, }); return async ({ url, headers, method, body }) => { const res = await mf.dispatchFetch("http://localhost" + url, { headers: headers || {}, method: method || "GET", redirect: "manual", body, }); return res as unknown as Response; }; }); it("should export the correct functions", async () => { const entry = await fsp.readFile(resolve(ctx.outDir, "server", "index.mjs"), "utf8"); expect(entry).toMatch(/export \{.*myScheduled.*\}/); }); it("should auto-generate cron triggers in wrangler.json", async () => { const wranglerConfig = await fsp .readFile(resolve(ctx.outDir, "server", "wrangler.json"), "utf8") .then((r) => JSON.parse(r)); expect(wranglerConfig.triggers).toEqual({ crons: ["* * * * *"], }); }); }); ================================================ FILE: test/presets/cloudflare-pages.test.ts ================================================ import { promises as fsp } from "node:fs"; import { Miniflare } from "miniflare"; import { resolve } from "pathe"; import { describe, expect, it } from "vitest"; import { isWindows } from "std-env"; import { setupTest, testNitro } from "../tests.ts"; describe.skipIf(isWindows)("nitro:preset:cloudflare-pages", async () => { const ctx = await setupTest("cloudflare-pages"); testNitro(ctx, () => { const mf = new Miniflare({ modules: true, compatibilityDate: "2025-04-01", scriptPath: resolve(ctx.outDir, "_worker.js", "index.js"), modulesRules: [{ type: "CompiledWasm", include: ["**/*.wasm"] }], compatibilityFlags: ["nodejs_compat", "no_nodejs_compat_v2"], sitePath: "", bindings: { ...ctx.env }, }); return async ({ url, headers, method, body }) => { const res = await mf.dispatchFetch("http://localhost" + url, { headers: headers || {}, method: method || "GET", redirect: "manual", body, }); return res as unknown as Response; }; }); it("should generate a _routes.json", async () => { const config = await fsp .readFile(resolve(ctx.outDir, "_routes.json"), "utf8") .then((r) => JSON.parse(r)); expect(config).toMatchInlineSnapshot(` { "exclude": [ "/blog/static/*", "/cf-pages-exclude/*", "/build/*", "/_openapi.json", "/_openapi.json.br", "/_openapi.json.gz", "/_openapi.json.zst", "/_scalar", "/_swagger", "/favicon.ico", "/foo.css", "/foo.js", "/json-string", "/prerender", "/prerender-custom", "/_scalar/index.html.br", "/_scalar/index.html.gz", "/_scalar/index.html.zst", "/_swagger/index.html.br", "/_swagger/index.html.gz", "/_swagger/index.html.zst", "/api/hello", "/api/hey", "/prerender/index.html.br", "/prerender/index.html.gz", "/prerender/index.html.zst", "/api/param/foo.json", "/api/param/hidden", "/api/param/prerender1", "/api/param/prerender3", "/api/param/prerender4", ], "include": [ "/*", ], "version": 1, } `); }); it("should export the correct functions", async () => { const entry = await fsp.readFile(resolve(ctx.outDir, "_worker.js", "index.js"), "utf8"); expect(entry).toMatch(/export \{.*myScheduled.*\}/); }); }); ================================================ FILE: test/presets/deno-server.test.ts ================================================ import { execa, execaCommandSync } from "execa"; import { getRandomPort, waitForPort } from "get-port-please"; import { describe } from "vitest"; import { setupTest, testNitro } from "../tests.ts"; const hasDeno = execaCommandSync("deno --version", { stdio: "ignore", reject: false }).exitCode === 0; describe.runIf(hasDeno)("nitro:preset:deno-server", async () => { const ctx = await setupTest("deno-server"); testNitro(ctx, async () => { const port = await getRandomPort(); execa("deno", ["task", "start"], { cwd: ctx.outDir, // stdio: "inherit", stdio: "ignore", env: { NITRO_PORT: String(port), NITRO_HOST: "127.0.0.1", }, }); ctx.server = { url: `http://127.0.0.1:${port}`, close: () => { // p.kill() }, } as any; await waitForPort(port, { delay: 1000, retries: 20, host: "127.0.0.1" }); return async ({ url, ...opts }) => { const res = await ctx.fetch(url, opts); return res; }; }); }); ================================================ FILE: test/presets/netlify.test.ts ================================================ import { promises as fsp } from "node:fs"; import type { Context as FunctionContext } from "@netlify/functions"; import { resolve } from "pathe"; import { describe, expect, it } from "vitest"; import { getStaticPaths } from "../../src/presets/netlify/utils.ts"; import { getPresetTmpDir, setupTest, testNitro } from "../tests.ts"; describe("nitro:preset:netlify", async () => { const ctx = await setupTest("netlify", { config: { output: { publicDir: resolve(getPresetTmpDir("netlify"), "dist"), }, netlify: { images: { remote_images: ["https://example.com/.*"], }, }, }, }); testNitro( ctx, async () => { const { default: handler } = (await import(resolve(ctx.outDir, "server/main.mjs"))) as { default: (req: Request, _ctx: FunctionContext) => Promise; }; return async ({ url: rawRelativeUrl, headers, method, body }) => { // creating new URL object to parse query easier const url = new URL(`https://example.com${rawRelativeUrl}`); const req = new Request(url, { headers: headers ?? {}, method, body, }); const res = await handler(req, {} as FunctionContext); return res; }; }, (_ctx, callHandler) => { it("adds route rules - redirects", async () => { const redirects = await fsp.readFile(resolve(ctx.outDir, "../dist/_redirects"), "utf8"); expect(redirects).toMatchInlineSnapshot(` "/rules/nested/override /other 302 /rules/redirect/wildcard/* https://nitro.build/:splat 302 /rules/redirect/obj https://nitro.build/ 301 /rules/nested/* /base 302 /rules/redirect /base 302 " `); }); it("adds route rules - headers", async () => { const headers = await fsp.readFile(resolve(ctx.outDir, "../dist/_headers"), "utf8"); expect(headers).toMatchInlineSnapshot(` "/rules/headers cache-control: s-maxage=60 /rules/cors access-control-allow-origin: * access-control-allow-methods: GET access-control-allow-headers: * access-control-max-age: 0 /rules/nested/* x-test: test /build/* cache-control: public, max-age=3600, immutable /* x-test: test " `); }); it("writes config.json", async () => { const config = await fsp .readFile(resolve(ctx.outDir, "../deploy/v1/config.json"), "utf8") .then((r) => JSON.parse(r)); expect(config).toMatchInlineSnapshot(` { "images": { "remote_images": [ "https://example.com/.*", ], }, } `); }); it("writes server/server.mjs with static paths excluded", async () => { const serverFunctionFile = await fsp.readFile( resolve(ctx.outDir, "server/server.mjs"), "utf8" ); expect(serverFunctionFile).toMatchInlineSnapshot(` "export { default } from "./main.mjs"; export const config = { name: "server handler", generator: "nitro@3.x", path: "/*", nodeBundler: "none", includedFiles: ["**"], excludedPath: ["/.netlify/*","/build/*"], preferStatic: true, };" `); }); describe("matching ISR route rule with no max-age", () => { it("sets Netlify-CDN-Cache-Control header with revalidation after 1 year and durable directive", async () => { const { headers } = await callHandler({ url: "/rules/isr" }); expect((headers as Record)["netlify-cdn-cache-control"]).toBe( "public, max-age=31536000, must-revalidate, durable" ); }); it("sets Cache-Control header with immediate revalidation", async () => { const { headers } = await callHandler({ url: "/rules/isr" }); expect((headers as Record)["cache-control"]).toBe( "public, max-age=0, must-revalidate" ); }); }); describe("matching ISR route rule with a max-age", () => { it("sets Netlify-CDN-Cache-Control header with SWC=1yr, given max-age, and durable directive", async () => { const { headers } = await callHandler({ url: "/rules/isr-ttl" }); expect((headers as Record)["netlify-cdn-cache-control"]).toBe( "public, max-age=60, stale-while-revalidate=31536000, durable" ); }); it("sets Cache-Control header with immediate revalidation", async () => { const { headers } = await callHandler({ url: "/rules/isr-ttl" }); expect((headers as Record)["cache-control"]).toBe( "public, max-age=0, must-revalidate" ); }); }); it("does not overwrite Cache-Control headers given a matching non-ISR route rule", async () => { const { headers } = await callHandler({ url: "/rules/dynamic" }); expect((headers as Record)["cache-control"]).not.toBeDefined(); expect((headers as Record)["netlify-cdn-cache-control"]).not.toBeDefined(); }); // Regression test for https://github.com/nitrojs/nitro/issues/2431 it("matches paths with a query string", async () => { const { headers } = await callHandler({ url: "/rules/isr-ttl?foo=bar", }); expect((headers as Record)["netlify-cdn-cache-control"]).toBe( "public, max-age=60, stale-while-revalidate=31536000, durable" ); }); } ); describe("getStaticPaths", () => { it("always returns `/.netlify/*`", () => { expect(getStaticPaths([], "/base")).toEqual(["/.netlify/*"]); }); it("returns a pattern with a leading slash for each non-fallthrough non-root public asset path", () => { const publicAssets = [ { fallthrough: true, baseURL: "with-fallthrough", dir: "with-fallthrough-dir", maxAge: 0, }, { fallthrough: true, dir: "with-fallthrough-no-baseURL-dir", maxAge: 0, }, { fallthrough: false, dir: "no-fallthrough-no-baseURL-dir", maxAge: 0, }, { fallthrough: false, dir: "no-fallthrough-root-baseURL-dir", baseURL: "/", maxAge: 0, }, { baseURL: "with-default-fallthrough", dir: "with-default-fallthrough-dir", maxAge: 0, }, { fallthrough: false, baseURL: "nested/no-fallthrough", dir: "nested/no-fallthrough-dir", maxAge: 0, }, ]; expect(getStaticPaths(publicAssets, "/base")).toEqual([ "/.netlify/*", "/base/with-default-fallthrough/*", "/base/nested/no-fallthrough/*", ]); }); }); }); ================================================ FILE: test/presets/nitro-dev.test.ts ================================================ import type { OpenAPI3 } from "../../src/types/openapi-ts.ts"; import { describe, expect, it } from "vitest"; import { setupTest, testNitro } from "../tests.ts"; describe("nitro:preset:nitro-dev", async () => { const ctx = await setupTest("nitro-dev"); testNitro( ctx, () => { return async ({ url, headers, method, body }) => { const res = await ctx.fetch(url, { headers, method, body, }); return res; }; }, (_ctx, callHandler) => { it.skipIf(process.env.OFFLINE)("returns correct status for devProxy", async () => { const { status } = await callHandler({ url: "/proxy/example" }); expect(status).toBe(200); }); describe("openAPI", () => { let spec: OpenAPI3; it("/_openapi.json", async () => { spec = ((await callHandler({ url: "/_openapi.json" })) as any).data; expect(spec.openapi).to.match(/^3\.\d+\.\d+$/); expect(spec.info.title).toBe("Nitro Test Fixture"); expect(spec.info.description).toBe("Nitro Test Fixture API"); }); it("defineRouteMeta works", () => { expect(spec.paths?.["/api/meta/test"]).toMatchInlineSnapshot(` { "get": { "description": "Test route description", "parameters": [ { "in": "query", "name": "test", "required": true, }, { "in": "query", "name": "val", "schema": { "enum": [ 0, 1, ], "type": "integer", }, }, ], "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Test", }, }, }, "description": "result", }, }, "tags": [ "test", ], }, } `); }); }); } ); }); ================================================ FILE: test/presets/node.test.ts ================================================ import { existsSync } from "node:fs"; import { resolve } from "pathe"; // import { isWindows } from "std-env"; import { describe, expect, it } from "vitest"; import { setupTest, startServer, testNitro } from "../tests.ts"; describe("nitro:preset:node-middleware", async () => { const ctx = await setupTest("node-middleware"); testNitro(ctx, async () => { const entryPath = resolve(ctx.outDir, "server/index.mjs"); const { middleware } = await import(entryPath); await startServer(ctx, middleware); return async ({ url, ...opts }) => { const res = await ctx.fetch(url, opts); return res; }; }); it("should handle nested cached route rules", async () => { const cached = await ctx.fetch("/rules/_/noncached/cached"); expect(cached.headers.get("etag")).toBeDefined(); const noncached = await ctx.fetch("/rules/_/noncached/noncached"); expect(noncached.headers.get("etag")).toBeNull(); const cached2 = await ctx.fetch("/rules/_/cached/cached"); expect(cached2.headers.get("etag")).toBeDefined(); const noncached2 = await ctx.fetch("/rules/_/cached/noncached"); expect(noncached2.headers.get("etag")).toBeNull(); }); it("should trace externals", () => { const serverNodeModules = resolve(ctx.outDir, "server/node_modules"); expect(existsSync(resolve(serverNodeModules, "@fixture/nitro-utils/extra.mjs"))).toBe(true); }); }); ================================================ FILE: test/presets/standard.test.ts ================================================ import { resolve } from "pathe"; import { describe } from "vitest"; import { setupTest, testNitro } from "../tests.ts"; describe("nitro:standard", async () => { const ctx = await setupTest("standard"); testNitro(ctx, async () => { const entryPath = resolve(ctx.outDir, "server/index.mjs"); const fetchHandler = await import(entryPath).then((m) => m.default.fetch); return async ({ url, ...init }) => { const res = await fetchHandler(new Request(`https://test.com${url}`, init)); return res; }; }); }); ================================================ FILE: test/presets/static.test.ts ================================================ import fsp from "node:fs/promises"; import { resolve } from "pathe"; import { describe, expect, it } from "vitest"; import { setupTest } from "../tests.ts"; describe("nitro:preset:static", async () => { const ctx = await setupTest("static"); it("should not generate a server folder", async () => { const contents = await fsp.readdir(resolve(ctx.outDir)); expect(contents).toMatchInlineSnapshot(` [ "nitro.json", "public", ] `); }); }); ================================================ FILE: test/presets/vercel.test.ts ================================================ import { promises as fsp } from "node:fs"; import { resolve, join, basename } from "pathe"; import { describe, expect, it, vi, beforeAll, afterAll } from "vitest"; import { setupTest, testNitro, fixtureDir } from "../tests.ts"; import { toFetchHandler } from "srvx/node"; describe("nitro:preset:vercel:web", async () => { const ctx = await setupTest("vercel", { outDirSuffix: "-web", }); testNitro( ctx, async () => { const { fetch: fetchHandler } = await import( resolve(ctx.outDir, "functions/__server.func/index.mjs") ).then((r) => r.default || r); return async ({ url, ...options }) => { const req = new Request(new URL(url, "https://example.com"), options); const res = await fetchHandler(req, { waitUntil: vi.fn(), }); return res; }; }, () => { it("should add route rules to config", async () => { const config = await fsp .readFile(resolve(ctx.outDir, "config.json"), "utf8") .then((r) => JSON.parse(r)); expect(config).toMatchInlineSnapshot(` { "crons": [ { "path": "/_vercel/cron", "schedule": "* * * * *", }, ], "framework": { "name": "nitro", "version": "3.x", }, "overrides": { "_scalar/index.html": { "path": "_scalar", }, "_swagger/index.html": { "path": "_swagger", }, "api/hey/index.html": { "path": "api/hey", }, "prerender/index.html": { "path": "prerender", }, }, "routes": [ { "headers": { "Location": "https://nitro.build/", }, "src": "/rules/redirect/obj", "status": 308, }, { "headers": { "Location": "https://nitro.build/$1", }, "src": "/rules/redirect/wildcard/(.*)", "status": 307, }, { "headers": { "Location": "/other", }, "src": "/rules/nested/override", "status": 307, }, { "headers": { "cache-control": "s-maxage=60", }, "src": "/rules/headers", }, { "headers": { "access-control-allow-headers": "*", "access-control-allow-methods": "GET", "access-control-allow-origin": "*", "access-control-max-age": "0", }, "src": "/rules/cors", }, { "headers": { "Location": "/base", }, "src": "/rules/redirect", "status": 307, }, { "headers": { "Location": "/base", "x-test": "test", }, "src": "/rules/nested/(.*)", "status": 307, }, { "headers": { "cache-control": "public, max-age=3600, immutable", }, "src": "/build/(.*)", }, { "headers": { "x-test": "test", }, "src": "/(.*)", }, { "dest": "https://cdn.jsdelivr.net/$1", "src": "/cdn/(.*)", }, { "continue": true, "headers": { "cache-control": "public,max-age=31536000,immutable", }, "src": "/build(.*)", }, { "handle": "filesystem", }, { "dest": "/rules/_/noncached/cached-isr?__isr_route=$__isr_route", "src": "(?<__isr_route>/rules/_/noncached/cached)", }, { "dest": "/__server", "src": "(?<__isr_route>/rules/_/cached/noncached)", }, { "dest": "/__server", "src": "(?<__isr_route>/rules/_/noncached/(?:.*))", }, { "dest": "/rules/_/cached/[...]-isr?__isr_route=$__isr_route", "src": "(?<__isr_route>/rules/_/cached/(?:.*))", }, { "dest": "/__server", "src": "(?<__isr_route>/rules/dynamic)", }, { "dest": "/rules/isr/[...]-isr?__isr_route=$__isr_route", "src": "(?<__isr_route>/rules/isr/(?:.*))", }, { "dest": "/rules/isr-ttl/[...]-isr?__isr_route=$__isr_route", "src": "(?<__isr_route>/rules/isr-ttl/(?:.*))", }, { "dest": "/rules/swr/[...]-isr?__isr_route=$__isr_route", "src": "(?<__isr_route>/rules/swr/(?:.*))", }, { "dest": "/rules/swr-ttl/[...]-isr?__isr_route=$__isr_route", "src": "(?<__isr_route>/rules/swr-ttl/(?:.*))", }, { "dest": "/wasm/static-import", "src": "/wasm/static-import", }, { "dest": "/wasm/dynamic-import", "src": "/wasm/dynamic-import", }, { "dest": "/wait-until", "src": "/wait-until", }, { "dest": "/virtual", "src": "/virtual", }, { "dest": "/stream", "src": "/stream", }, { "dest": "/static-flags", "src": "/static-flags", }, { "dest": "/route-group", "src": "/route-group", }, { "dest": "/replace", "src": "/replace", }, { "dest": "/raw", "src": "/raw", }, { "dest": "/prerender-custom.html", "src": "/prerender-custom.html", }, { "dest": "/prerender", "src": "/prerender", }, { "dest": "/node-compat", "src": "/node-compat", }, { "dest": "/modules", "src": "/modules", }, { "dest": "/jsx", "src": "/jsx", }, { "dest": "/json-string", "src": "/json-string", }, { "dest": "/imports", "src": "/imports", }, { "dest": "/icon.png", "src": "/icon.png", }, { "dest": "/file", "src": "/file", }, { "dest": "/fetch", "src": "/fetch", }, { "dest": "/errors/throw", "src": "/errors/throw", }, { "dest": "/errors/stack", "src": "/errors/stack", }, { "dest": "/errors/captured", "src": "/errors/captured", }, { "dest": "/env", "src": "/env", }, { "dest": "/context", "src": "/context", }, { "dest": "/config", "src": "/config", }, { "dest": "/assets/md", "src": "/assets/md", }, { "dest": "/assets/all", "src": "/assets/all", }, { "dest": "/api/upload", "src": "/api/upload", }, { "dest": "/api/storage/item", "src": "/api/storage/item", }, { "dest": "/api/methods/get", "src": "/api/methods/get", }, { "dest": "/api/methods/foo.get", "src": "/api/methods/foo.get", }, { "dest": "/api/meta/test", "src": "/api/meta/test", }, { "dest": "/api/kebab", "src": "/api/kebab", }, { "dest": "/api/hey", "src": "/api/hey", }, { "dest": "/api/hello", "src": "/api/hello", }, { "dest": "/api/headers", "src": "/api/headers", }, { "dest": "/api/echo", "src": "/api/echo", }, { "dest": "/api/db", "src": "/api/db", }, { "dest": "/api/cached", "src": "/api/cached", }, { "dest": "/500", "src": "/500", }, { "dest": "/_vercel/cron", "src": "/_vercel/cron", }, { "dest": "/_swagger", "src": "/_swagger", }, { "dest": "/_scalar", "src": "/_scalar", }, { "dest": "/_openapi.json", "src": "/_openapi.json", }, { "dest": "/assets/[id]", "src": "/assets/(?[^/]+)", }, { "dest": "/api/test/[-]/foo", "src": "/api/test/(?<_0>[^/]*)/foo", }, { "dest": "/api/param/[test-id]", "src": "/api/param/(?[^/]+)-id", }, { "dest": "/tasks/[...name]", "src": "/tasks/?(?.+)", }, { "dest": "/rules/[...slug]", "src": "/rules/?(?.+)", }, { "dest": "/api/wildcard/[...param]", "src": "/api/wildcard/?(?.+)", }, { "dest": "/__server", "src": "/(.*)", }, ], "version": 3, } `); }); it("should generate prerender config", async () => { const isrRouteConfig = await fsp.readFile( resolve(ctx.outDir, "functions/rules/isr/[...]-isr.prerender-config.json"), "utf8" ); expect(JSON.parse(isrRouteConfig)).toMatchObject({ expiration: false, allowQuery: ["q", "__isr_route"], }); }); const walkDir = async (path: string): Promise => { const items: string[] = []; const dirname = basename(path); const entries = await fsp.readdir(path, { withFileTypes: true }); for (const entry of entries) { if (entry.isFile()) { items.push(`${dirname}/${entry.name}`); } else if (entry.isSymbolicLink()) { items.push(`${dirname}/${entry.name} (symlink)`); } else if (/_\/|_.+|node_modules/.test(entry.name)) { items.push(`${dirname}/${entry.name}`); } else if (entry.isDirectory()) { items.push(...(await walkDir(join(path, entry.name))).map((i) => `${dirname}/${i}`)); } } items.sort(); return items; }; it("should generated expected functions", async () => { const functionsDir = resolve(ctx.outDir, "functions"); const functionsFiles = await walkDir(functionsDir); expect(functionsFiles).toMatchInlineSnapshot(` [ "functions/500.func (symlink)", "functions/__server.func", "functions/_openapi.json.func (symlink)", "functions/_scalar.func (symlink)", "functions/_swagger.func (symlink)", "functions/_vercel", "functions/api/cached.func (symlink)", "functions/api/db.func (symlink)", "functions/api/echo.func (symlink)", "functions/api/headers.func (symlink)", "functions/api/hello.func (symlink)", "functions/api/hey.func (symlink)", "functions/api/kebab.func (symlink)", "functions/api/meta/test.func (symlink)", "functions/api/methods/foo.get.func (symlink)", "functions/api/methods/get.func (symlink)", "functions/api/param/[test-id].func (symlink)", "functions/api/storage/item.func (symlink)", "functions/api/test/[-]/foo.func (symlink)", "functions/api/upload.func (symlink)", "functions/api/wildcard/[...param].func (symlink)", "functions/assets/[id].func (symlink)", "functions/assets/all.func (symlink)", "functions/assets/md.func (symlink)", "functions/config.func (symlink)", "functions/context.func (symlink)", "functions/env.func (symlink)", "functions/errors/captured.func (symlink)", "functions/errors/stack.func (symlink)", "functions/errors/throw.func (symlink)", "functions/fetch.func (symlink)", "functions/file.func (symlink)", "functions/icon.png.func (symlink)", "functions/imports.func (symlink)", "functions/json-string.func (symlink)", "functions/jsx.func (symlink)", "functions/modules.func (symlink)", "functions/node-compat.func (symlink)", "functions/prerender-custom.html.func (symlink)", "functions/prerender.func (symlink)", "functions/raw.func (symlink)", "functions/replace.func (symlink)", "functions/route-group.func (symlink)", "functions/rules/[...slug].func (symlink)", "functions/rules/_/cached/[...]-isr.func (symlink)", "functions/rules/_/cached/[...]-isr.prerender-config.json", "functions/rules/_/noncached/cached-isr.func (symlink)", "functions/rules/_/noncached/cached-isr.prerender-config.json", "functions/rules/isr-ttl/[...]-isr.func (symlink)", "functions/rules/isr-ttl/[...]-isr.prerender-config.json", "functions/rules/isr/[...]-isr.func (symlink)", "functions/rules/isr/[...]-isr.prerender-config.json", "functions/rules/swr-ttl/[...]-isr.func (symlink)", "functions/rules/swr-ttl/[...]-isr.prerender-config.json", "functions/rules/swr/[...]-isr.func (symlink)", "functions/rules/swr/[...]-isr.prerender-config.json", "functions/static-flags.func (symlink)", "functions/stream.func (symlink)", "functions/tasks/[...name].func (symlink)", "functions/virtual.func (symlink)", "functions/wait-until.func (symlink)", "functions/wasm/dynamic-import.func (symlink)", "functions/wasm/static-import.func (symlink)", ] `); }); } ); }); describe("nitro:preset:vercel:node", async () => { const ctx = await setupTest("vercel", { outDirSuffix: "-node", config: { vercel: { entryFormat: "node" }, }, }); testNitro(ctx, async () => { const nodeHandler = await import(resolve(ctx.outDir, "functions/__server.func/index.mjs")).then( (r) => r.default || r ); const fetchHandler = toFetchHandler(nodeHandler); return async ({ url, ...options }) => { const req = new Request(new URL(url, "https://example.com"), options); const res = await fetchHandler(req); return res; }; }); }); describe("nitro:preset:vercel:bun", async () => { const ctx = await setupTest("vercel", { outDirSuffix: "-bun", config: { preset: "vercel", vercel: { functions: { runtime: "bun1.x", }, }, }, }); it("should generate function config with bun runtime", async () => { const config = await fsp .readFile(resolve(ctx.outDir, "functions/__server.func/.vc-config.json"), "utf8") .then((r) => JSON.parse(r)); expect(config).toMatchInlineSnapshot(` { "handler": "index.mjs", "launcherType": "Nodejs", "runtime": "bun1.x", "shouldAddHelpers": false, "supportsResponseStreaming": true, } `); }); }); describe.skip("nitro:preset:vercel:bun-verceljson", async () => { const vercelJsonPath = join(fixtureDir, "vercel.json"); const ctx = await setupTest("vercel", { outDirSuffix: "-bun-verceljson", config: { preset: "vercel", }, }); beforeAll(async () => { // Need to make sure vercel.json is created before setupTest is called await fsp.writeFile(vercelJsonPath, JSON.stringify({ bunVersion: "1.x" })); }); afterAll(async () => { await fsp.unlink(vercelJsonPath).catch(() => {}); }); it("should detect bun runtime from vercel.json", async () => { const config = await fsp .readFile(resolve(ctx.outDir, "functions/__server.func/.vc-config.json"), "utf8") .then((r) => JSON.parse(r)); expect(config).toMatchInlineSnapshot(` { "handler": "index.mjs", "launcherType": "Nodejs", "runtime": "bun1.x", "shouldAddHelpers": false, "supportsResponseStreaming": true, } `); }); }); ================================================ FILE: test/presets/winterjs.test.ts ================================================ import { execa } from "execa"; import { getRandomPort, waitForPort } from "get-port-please"; import { describe } from "vitest"; import { setupTest, testNitro } from "../tests.ts"; const hasWasmer = false; // execaCommandSync("wasmer --version", { stdio: "ignore", reject: false }) // .exitCode === 0; describe.runIf(hasWasmer)("nitro:preset:winterjs", async () => { const ctx = await setupTest("winterjs"); testNitro(ctx, async () => { const port = await getRandomPort(); const p = execa( "wasmer", [ "run", "wasmer/winterjs", "--forward-host-env", "--net", "--mapdir", "app:" + ctx.outDir, "app/server/index.mjs", ], { stdio: "inherit", env: { // ...ctx.env, RUST_BACKTRACE: "full", LISTEN_IP: "127.0.0.1", PORT: String(port), }, } ); ctx.server = { url: `http://127.0.0.1:${port}`, close: () => p.kill(), } as any; await waitForPort(port, { delay: 1000, retries: 20, host: "127.0.0.1" }); return async ({ url, ...opts }) => { const res = await ctx.fetch(url, opts); return res; }; }); }); ================================================ FILE: test/scripts/gen-fixture-types.ts ================================================ import { fileURLToPath } from "mlly"; import { createNitro, writeTypes } from "nitro/builder"; import { resolve } from "pathe"; import { scanHandlers } from "../../src/scan.ts"; const prepare = async () => { const fixtureDir = fileURLToPath(new URL("../fixture", import.meta.url).href); const nitro = await createNitro({ rootDir: fixtureDir, serveStatic: true, output: { dir: resolve(fixtureDir, ".output", "types") }, }); await scanHandlers(nitro); await writeTypes(nitro); }; prepare(); ================================================ FILE: test/tests.ts ================================================ import { promises as fsp } from "node:fs"; import { Server, type RequestListener } from "node:http"; import { tmpdir } from "node:os"; import { formatDate } from "compatx"; import type { DateString } from "compatx"; import { defu } from "defu"; import destr from "destr"; import { fileURLToPath } from "mlly"; import { build, copyPublicAssets, createDevServer, createNitro, prepare, prerender, } from "nitro/builder"; import type { Nitro, NitroConfig } from "nitro/types"; import { fetch } from "ofetch"; import type { FetchOptions } from "ofetch"; import { join, resolve } from "pathe"; import { isWindows } from "std-env"; import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; export interface Context { preset: string; nitro?: Nitro; rootDir: string; outDir: string; fetch: (url: string, opts?: FetchOptions) => Promise; server?: { url: string; close: () => Promise }; isDev: boolean; isWorker: boolean; isLambda: boolean; isIsolated: boolean; supportsEnv: boolean; env: Record; lambdaV1?: boolean; // [key: string]: unknown; } // https://github.com/nitrojs/nitro/pull/1240 export const describeIf = (condition: boolean, title: string, factory: () => any) => condition ? describe(title, factory) : describe(title, () => { it.skip("skipped", () => { // Ignore }); }); export const fixtureDir = fileURLToPath(new URL("fixture", import.meta.url).href); export const getPresetTmpDir = (preset: string) => { if (preset.startsWith("cloudflare")) { return fileURLToPath(new URL(`.tmp/${preset}`, import.meta.url) as any /* remove me */); } return resolve(process.env.NITRO_TEST_TMP_DIR || join(tmpdir(), "nitro-tests"), preset); }; export async function setupTest( preset: string, opts: { config?: NitroConfig; compatibilityDate?: DateString; outDirSuffix?: string; } = {} ) { const presetTmpDir = getPresetTmpDir(preset + (opts.outDirSuffix || "")); await fsp.rm(presetTmpDir, { recursive: true }).catch(() => { // Ignore }); await fsp.mkdir(presetTmpDir, { recursive: true }); const ctx: Context = { preset, isDev: preset === "nitro-dev", isWorker: [ "cloudflare-worker", "cloudflare-module", "cloudflare-module-legacy", "cloudflare-pages", "netlify-edge", "vercel-edge", "winterjs", ].includes(preset), isLambda: ["aws-lambda", "netlify-legacy"].includes(preset), isIsolated: ["winterjs"].includes(preset), supportsEnv: !["winterjs"].includes(preset), rootDir: fixtureDir, outDir: resolve(fixtureDir, presetTmpDir, ".output"), env: { NITRO_HELLO: "world", CUSTOM_HELLO_THERE: "general", SECRET: "secret", APP_DOMAIN: "test.com", NITRO_DYNAMIC: "from-env", }, fetch: (url, opts) => fetch(new URL(url, ctx.server!.url), { redirect: "manual", ...(opts as any), }), }; // Set environment variables for process compatible presets for (const [name, value] of Object.entries(ctx.env)) { process.env[name] = value; } const config = defu(opts.config, { preset: ctx.preset, dev: ctx.isDev, rootDir: ctx.rootDir, runtimeConfig: { nitro: { envPrefix: "CUSTOM_", }, hello: "", helloThere: "", }, buildDir: resolve(fixtureDir, presetTmpDir, ".nitro"), serveStatic: !ctx.isDev && !ctx.isWorker, output: { dir: ctx.outDir, }, }); const nitro = (ctx.nitro = await createNitro(config, { compatibilityDate: opts.compatibilityDate || formatDate(new Date()), })); if (ctx.isDev) { // Setup development server const devServer = createDevServer(ctx.nitro); const server = await devServer.listen({}); ctx.server = { url: server.url!, close: () => server.close(), }; await prepare(ctx.nitro); const ready = new Promise((resolve) => { ctx.nitro!.hooks.hook("dev:reload", () => resolve()); }); await build(ctx.nitro); await ready; } else { // Production build await prepare(nitro); await copyPublicAssets(nitro); await prerender(nitro); await build(nitro); } afterAll(async () => { if (ctx.server) { await ctx.server.close(); } if (ctx.nitro) { await ctx.nitro.close(); } }); return ctx; } export async function startServer(ctx: Context, handle: RequestListener) { const server = new Server(handle); await new Promise((resolve, reject) => { server.on("error", reject); server.listen(0, () => resolve()); }); const port = (server.address() as any).port; ctx.server = { url: `http://localhost:${port}`, close: () => new Promise((resolve, reject) => { server.close((err) => { if (err) return reject(err); resolve(); }); }), }; } type TestHandlerResult = { data: any; status: number; statusText?: string; headers: Record; }; type TestHandler = (options: any) => Promise; export function testNitro( ctx: Context, getHandler: () => TestHandler | Promise, additionalTests?: ( ctx: Context, callHandler: (options: any) => Promise ) => void ) { let _handler: TestHandler; async function callHandler( options: any, callOpts: { binary?: boolean } = {} ): Promise { const result = await _handler(options); if ( !(result instanceof Response) && !["Response", "_Response"].includes(result.constructor.name) ) { throw new TypeError("Expected Response"); } const headers: Record = {}; for (const [key, value] of (result as Response).headers.entries()) { if (headers[key]) { if (!Array.isArray(headers[key])) { headers[key] = [headers[key] as string]; } if (Array.isArray(value)) { (headers[key] as string[]).push(...value); } else { (headers[key] as string[]).push(value); } } else { headers[key] = value; } } headers["set-cookie"] = (result as Response).headers.getSetCookie(); if (headers["set-cookie"].length === 0) { delete headers["set-cookie"]; } return { data: callOpts.binary ? Buffer.from(await (result as Response).arrayBuffer()) : destr(await (result as Response).text()), status: result.status, statusText: result.statusText, headers, }; } beforeAll(async () => { _handler = await getHandler(); }, 25_000); it("Server entry works", async () => { const { data, headers } = await callHandler({ url: "/" }); expect(data).toBe("server entry works!"); expect(headers["x-test"]).toBe("test"); }); it("API Works", async () => { const { data: helloData } = await callHandler({ url: "/api/hello" }); expect(helloData).to.toMatchObject({ message: "Hello API" }); if (ctx.nitro?.options.serveStatic) { // /api/hey is expected to be prerendered const { data: heyData } = await callHandler({ url: "/api/hey" }); expect(heyData).to.have.string("Hey API"); } const { data: kebabData } = await callHandler({ url: "/api/kebab" }); expect(kebabData).to.have.string("hello-world"); const { data: paramsData } = await callHandler({ url: "/api/param/test_param", }); expect(paramsData).toBe("test_param"); const { data: paramsData2 } = await callHandler({ url: "/api/wildcard/foo/bar/baz", }); expect(paramsData2).toBe("foo/bar/baz"); }); it("group routes", async () => { const { status } = await callHandler({ url: "/route-group" }); expect(status).toBe(200); const { status: apiStatus } = await callHandler({ url: "/route-group", }); expect(apiStatus).toBe(200); }); it("Handle 404 not found", async () => { const res = await callHandler({ url: "/api/not-found" }); expect(res.status).toBe(404); }); it("Virtual route", async () => { const res = await callHandler({ url: "/virtual" }); expect(res.status).toBe(200); expect(res.data).toBe("Hello from virtual entry!"); }); // TODO it.todo("Handle 405 method not allowed", async () => { const res = await callHandler({ url: "/api/upload" }); expect(res.status).toBe(405); }); it("handles route rules - redirects", async () => { const base = await callHandler({ url: "/rules/redirect" }); expect(base.status).toBe(307); expect(base.headers.location).toBe("/base"); const obj = await callHandler({ url: "/rules/redirect/obj" }); expect(obj.status).toBe(308); expect(obj.headers.location).toBe("https://nitro.build/"); const wildcard = await callHandler({ url: "/rules/redirect/wildcard/nuxt", }); expect(wildcard.status).toBe(307); expect(wildcard.headers.location).toBe("https://nitro.build/nuxt"); }); it("binary response", async () => { const { data } = await callHandler({ url: "/icon.png" }, { binary: true }); // Check if buffer is a png function isBufferPng(buffer: Buffer) { return buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4e && buffer[3] === 0x47; } expect(isBufferPng(data)).toBe(true); }); it.skipIf( // TODO: srvx reverse-compat bug with streaming? ctx.preset === "vercel" && ctx.nitro?.options.vercel?.entryFormat === "node" )("render JSX", async () => { const { data } = await callHandler({ url: "/jsx" }); expect(data).toMatch(/

    Hello JSX!<\/h1>/); }); it("replace", async () => { const { data } = await callHandler({ url: "/replace" }); expect(data).toMatchObject({ window: false }); }); it.runIf(ctx.nitro?.options.serveStatic)("handles custom Vary header", async () => { let headers = ( await callHandler({ url: "/foo.css", headers: { "Accept-Encoding": "gzip" }, }) ).headers; if (headers["vary"]) { expect(headers["vary"].includes("Origin")).toBeTruthy(); expect(headers["vary"].includes("Accept-Encoding")).toBeTruthy(); } headers = ( await callHandler({ url: "/foo.css", headers: { "Accept-Encoding": "" }, }) ).headers; if (headers["vary"]) { expect(headers["vary"]).toBe("Origin"); } headers = ( await callHandler({ url: "/foo.js", headers: { "Accept-Encoding": "gzip" }, }) ).headers; if (headers["vary"]) { expect(headers["vary"].includes("Origin")).toBeTruthy(); expect(headers["vary"].includes("Accept-Encoding")).toBeTruthy(); } }); it("handles route rules - headers", async () => { const { headers } = await callHandler({ url: "/rules/headers" }); expect(headers["cache-control"]).toBe("s-maxage=60"); }); it("handles route rules - cors", async () => { const expectedHeaders = { "access-control-allow-origin": "*", "access-control-allow-methods": "GET", "access-control-allow-headers": "*", "access-control-max-age": "0", }; const { headers } = await callHandler({ url: "/rules/cors" }); expect(headers).toMatchObject(expectedHeaders); }); describe("handles route rules - basic auth", () => { it("rejects request with bad creds", async () => { const { status, headers } = await callHandler({ url: "/rules/basic-auth", headers: { Authorization: "Basic " + btoa("user:wrongpass"), }, }); expect(status).toBe(401); expect(headers["www-authenticate"]).toBe('Basic realm="Secure Area"'); }); it("allows request with correct password", async () => { const { status } = await callHandler({ url: "/rules/basic-auth/test", headers: { Authorization: "Basic " + btoa("admin:secret"), }, }); expect(status).toBe(200); }); it("disabled basic-auth for sub-rules", async () => { const { status } = await callHandler({ url: "/rules/basic-auth/no-auth" }); expect(status).toBe(200); }); }); it("handles route rules - allowing overriding", async () => { const override = await callHandler({ url: "/rules/nested/override" }); expect(override.headers.location).toBe("/other"); expect(override.headers["x-test"]).toBe("test"); const base = await callHandler({ url: "/rules/nested/base" }); expect(base.headers.location).toBe("/base"); expect(base.headers["x-test"]).toBe("test"); }); it.skipIf( // TODO! ctx.preset === "vercel" && ctx.nitro?.options.vercel?.entryFormat === "node" && isWindows )("handles custom server assets", async () => { const { data: html, status: htmlStatus } = await callHandler({ url: "/file?filename=index.html", }); expect(htmlStatus).toBe(200); expect(html).toContain("

    nitro is amazing!

    "); const { data: txtFile, status: txtStatus } = await callHandler({ url: "/file?filename=test.txt", }); expect(txtStatus).toBe(200); expect(txtFile).toContain("this is an asset from a text file from nitro"); const { data: mdFile, status: mdStatus } = await callHandler({ url: "/assets/md", }); expect(mdStatus).toBe(200); expect(mdFile).toContain("# Hello world"); }); if (ctx.nitro!.options.serveStatic) { it("serve static asset /favicon.ico", async () => { const { status, headers } = await callHandler({ url: "/favicon.ico" }); expect(status).toBe(200); expect(headers.etag).toBeDefined(); expect(headers["content-type"]).toBe("image/vnd.microsoft.icon"); }); it("serve static asset /build/test.txt", async () => { const { status, headers } = await callHandler({ url: "/build/test.txt" }); expect(status).toBe(200); expect(headers.etag).toBe('"7-vxGfAKTuGVGhpDZqQLqV60dnKPw"'); expect(headers["content-type"]).toBe("text/plain; charset=utf-8"); }); it("stores content-type for prerendered routes", async () => { const { data, headers } = await callHandler({ url: "/api/param/prerender4", }); expect(data).toBe("prerender4"); expect(headers["content-type"]).toBe("text/plain; custom"); }); } it("shows 404 for /build/non-file", async () => { const { status } = await callHandler({ url: "/build/non-file" }); expect(status).toBe(404); }); it("find auto imported utils", async () => { const res = await callHandler({ url: "/imports" }); expect(res.data).toMatchObject({ testUtil: 123, testNestedUtil: 1234 + 12_345, }); }); it.skipIf(ctx.preset === "deno-server")("resolve module version conflicts", async () => { const { data } = await callHandler({ url: "/modules" }); expect(data).toMatchObject({ depA: "@fixture/nitro-lib@1.0.0+@fixture/nested-lib@1.0.0", depB: "@fixture/nitro-lib@2.0.1+@fixture/nested-lib@2.0.1", depLib: "@fixture/nitro-lib@2.0.0+@fixture/nested-lib@2.0.0", subpathLib: "@fixture/nitro-lib@2.0.0", extraUtils: "@fixture/nitro-utils/extra", }); }); it.skipIf(ctx.isIsolated)("useStorage (with base)", { retry: 5 }, async () => { const putRes = await callHandler({ url: "/api/storage/item?key=test:hello", method: "PUT", body: `"world"`, }); expect(putRes.data).toBe("world"); expect( ( await callHandler({ url: "/api/storage/item?key=:", }) ).data ).toMatchObject(["test:hello"]); expect( ( await callHandler({ url: "/api/storage/item?base=test&key=:", }) ).data ).toMatchObject(["hello"]); expect( ( await callHandler({ url: "/api/storage/item?base=test&key=hello", }) ).data ).toBe("world"); }); if (additionalTests) { additionalTests(ctx, callHandler); } it("runtime proxy", async () => { const { data } = await callHandler({ url: "/api/proxy?foo=bar", headers: { "x-test": "foobar", }, }); expect(data.url).toBe("/api/echo?foo=bar"); if (!(ctx.preset === "vercel" && ctx.nitro?.options.vercel?.entryFormat === "node")) { // TODO: Investigate why headers are missing in this case expect(data.headers["x-test"]).toBe("foobar"); } }); it("external proxy", async () => { const { data, headers, status } = await callHandler({ url: "/cdn/npm/bootstrap@5.3.8/dist/js/bootstrap.min.js", }); expect(status).toBe(200); expect(headers["etag"]).toMatch(/W\/".+"/); expect(data).toContain("Bootstrap"); }); it.skipIf(ctx.preset === "bun" /* TODO */)("stream", async () => { const { data } = await callHandler({ url: "/stream", }); expect(data).toBe("nitroisawesome"); }); it.skipIf(!ctx.supportsEnv)("config", async () => { const { data } = await callHandler({ url: "/config", }); expect(data).toMatchObject({ runtimeConfig: { dynamic: "from-env", url: "https://test.com", app: { baseURL: "/", }, }, sharedRuntimeConfig: { dynamic: ctx.preset === "cloudflare-module-legacy" ? "initial" : "from-env", // url: "https://test.com", app: { baseURL: "/", }, }, }); }); it("static build flags", async () => { const { data } = await callHandler({ url: "/static-flags" }); expect(data).toMatchObject({ dev: ctx.isDev, preset: ctx.preset, prerender: false, nitro: true, server: true, client: false, baseURL: "/", _asyncContext: true, _tasks: true, }); }); it("event.waitUntil", async () => { const res = await callHandler({ url: "/wait-until" }); expect(res.data).toBe("done"); }); describe("ignore", () => { it("server routes should be ignored", async () => { expect((await callHandler({ url: "/api/_ignored" })).status).toBe(404); expect((await callHandler({ url: "/_ignored" })).status).toBe(404); }); it.skipIf(ctx.isWorker || ctx.isDev)("public files should be ignored", async () => { expect((await callHandler({ url: "/_ignored.txt" })).status).toBe(404); expect((await callHandler({ url: "/favicon.ico" })).status).toBe(200); }); }); describe("headers", () => { it("handles headers correctly", async () => { const { headers } = await callHandler({ url: "/api/headers" }); expect(headers["x-foo"]).toBe("bar"); expect(headers["x-array"]).toMatch(/^foo,\s?bar$/); const expectedCookies: string | string[] = [ "foo=bar", "bar=baz", "test=value; Path=/", "test2=value; Path=/", ]; expect(headers["set-cookie"]).toMatchObject(expectedCookies); }); }); describe("errors", () => { it.skipIf(ctx.isIsolated)("captures errors", async () => { await callHandler({ url: "/errors/throw" }); const { data } = await callHandler({ url: "/errors/captured" }); const allErrorMessages = (data.allErrors || []).map((entry: any) => entry.message); expect(allErrorMessages).to.includes("Handled error"); }); it.skipIf( !ctx.nitro!.options.node || // TODO: Investigate ctx.preset === "bun" || ctx.preset === "deno-server" || ctx.preset === "nitro-dev" )("sourcemap works", async () => { const { data } = await callHandler({ url: "/errors/stack" }); expect(data.stack).toMatch("test/fixture/server/routes/errors/stack.ts"); }); for (const errorAction of ["throw", "return"]) { it(`handled errors (${errorAction})`, async () => { const res = await callHandler({ url: `/errors/throw?handled&action=${errorAction}` }); expect(res).toMatchObject({ status: 503, statusText: /deno|bun/.test(ctx.preset) ? "Service Unavailable" : /aws/.test(ctx.preset) ? "" : "Custom Status Text", headers: { "content-type": "application/json; charset=utf-8", "x-custom-error": "custom-value", }, data: { error: true, status: 503, statusText: "Custom Status Text", message: "Handled error", data: { custom: "data" }, custom: "body", }, }); }); it(`unhandled errors (${errorAction})`, async () => { const stderrMock = vi.spyOn(process.stderr, "write").mockImplementation(() => true); const consoleErrorMock = vi.spyOn(console, "error").mockImplementation(() => {}); let res; try { res = await callHandler({ url: `/errors/throw?unhandled&action=${errorAction}`, headers: { Accept: "application/json" }, }); } finally { stderrMock.mockRestore(); consoleErrorMock.mockRestore(); } // TODO // expect(consoleErrorMock).toHaveBeenCalledExactlyOnceWith( // expect.stringContaining("Unhandled error") // ); if (!ctx.isDev) { // Prod expect(res).toMatchObject({ status: 500, headers: { "content-type": "application/json; charset=utf-8", }, data: { error: true, unhandled: true, status: 500, }, }); } else { // Dev expect(res).toMatchObject({ status: 500, headers: { "content-type": "application/json; charset=utf-8", }, data: { error: true, unhandled: true, status: 500, message: "HTTPError", stack: expect.arrayContaining(["Unhandled error"]), }, }); } }); } }); describe("async context", () => { it.skipIf(!ctx.nitro!.options.node)("works", async () => { const { data } = await callHandler({ url: "/context?foo" }); expect(data).toMatchObject({ context: { path: "/context?foo", }, }); }); }); describe.skipIf(!ctx.supportsEnv)("environment variables", () => { it("can load environment variables from runtimeConfig", async () => { const { data } = await callHandler({ url: "/config" }); expect(data.runtimeConfig.hello).toBe("world"); expect(data.runtimeConfig.helloThere).toBe("general"); expect(data.runtimeConfig.secret).toBeUndefined(); }); }); describe("cache", () => { it.skipIf(ctx.isIsolated || (isWindows && ctx.preset === "nitro-dev"))( "should setItem before returning response the first time", async () => { const { data: { timestamp }, } = await callHandler({ url: "/api/cached" }); // TODO // expect(eventContextCache?.options.swr).toBe(true); const calls = await Promise.all([ callHandler({ url: "/api/cached" }), callHandler({ url: "/api/cached" }), callHandler({ url: "/api/cached" }), ]); for (const call of calls) { expect(call.data.timestamp).toBe(timestamp); // TODO // expect(call.data.eventContextCache.options.swr).toBe(true); } } ); }); describe("scanned files", () => { it("Allow having extra method in file name", async () => { expect((await callHandler({ url: "/api/methods/get" })).data).toBe("get"); expect((await callHandler({ url: "/api/methods/foo.get" })).data).toBe("foo.get"); }); }); describe.skipIf(ctx.preset === "cloudflare-worker")("wasm", () => { it("dynamic import wasm", async () => { expect((await callHandler({ url: "/wasm/dynamic-import" })).data).toBe("2+3=5"); }); it("static import wasm", async () => { expect((await callHandler({ url: "/wasm/static-import" })).data).toBe("2+3=5"); }); }); describe.skipIf( isWindows || !ctx.nitro!.options.node || ctx.isLambda || ctx.isWorker || ["bun", "deno-server", "deno-deploy", "netlify", "netlify-legacy"].includes(ctx.preset) )("Database", () => { it("works", async () => { const { data } = await callHandler({ url: "/api/db" }); expect(data).toMatchObject({ rows: [ { id: "1001", firstName: "John", lastName: "Doe", email: "", }, ], }); }); }); describe("Environment specific routes", () => { it("filters based on dev|prod", async () => { const { data } = await callHandler({ url: "/env" }); expect(data).toBe(ctx.isDev ? "dev env" : "prod env"); }); }); it("raw imports", async () => { const { data } = await callHandler({ url: "/raw" }); expect(data).toMatchObject({ sql: "--", sqlts: "--", }); }); it.skipIf( process.env.OFFLINE /* connect */ || ["cloudflare-worker", "cloudflare-module-legacy"].includes(ctx.preset) )("nodejs compatibility", async () => { const { data, status } = await callHandler({ url: "/node-compat" }); expect(status).toBe(200); for (const key in data) { if (ctx.preset === "vercel-edge" && key === "crypto:createHash") { continue; } if (ctx.preset === "deno-server" && key === "globals:BroadcastChannel") { continue; // unstable API } if ( ctx.preset.includes("cloudflare") && key.startsWith("globals:") && ctx.nitro!.options.builder === "rolldown" ) { continue; } expect(data[key], key).toBe(true); } }); } ================================================ FILE: test/unit/azure.utils.test.ts ================================================ import { describe, expect, it } from "vitest"; import { getAzureParsedCookiesFromHeaders } from "../../src/presets/azure/runtime/_utils.ts"; describe("getAzureParsedCookiesFromHeaders", () => { it("returns empty array if no cookies", () => { expect(getAzureParsedCookiesFromHeaders(new Headers({}))).toMatchObject([]); }); it("returns empty array if empty set-cookie header", () => { expect(getAzureParsedCookiesFromHeaders(new Headers({ "set-cookie": " " }))).toMatchObject([]); }); it("returns single cookie", () => { expect( getAzureParsedCookiesFromHeaders(new Headers({ "set-cookie": "foo=bar" })) ).toMatchObject([ { name: "foo", value: "bar", }, ]); }); it('returns cookie with "expires" attribute', () => { expect( getAzureParsedCookiesFromHeaders( new Headers({ "set-cookie": "foo=bar; expires=Thu, 01 Jan 1970 00:00:00 GMT", }) ) ).toMatchObject([ { name: "foo", value: "bar", expires: new Date("1970-01-01T00:00:00.000Z"), }, ]); }); it("returns a complex cookie", () => { expect( getAzureParsedCookiesFromHeaders( new Headers({ "set-cookie": "session=xyz; Path=/; Expires=Sun, 24 Mar 2024 09:13:27 GMT; HttpOnly; SameSite=Strict", }) ) ).toMatchObject([ { name: "session", value: "xyz", expires: new Date("2024-03-24T09:13:27.000Z"), path: "/", sameSite: "Strict", httpOnly: true, }, ]); }); it("returns multiple cookies", () => { expect( getAzureParsedCookiesFromHeaders( new Headers([ ["set-cookie", "foo=bar"], ["set-cookie", "baz=qux"], ]) ) ).toMatchObject([ { name: "foo", value: "bar", }, { name: "baz", value: "qux", }, ]); }); }); ================================================ FILE: test/unit/bump-version.test.ts ================================================ import { describe, it, expect, vi, beforeEach } from "vitest"; import { valid as semverValid } from "semver"; vi.mock("node:fs", () => ({ promises: { readFile: vi.fn(), writeFile: vi.fn(), }, })); const fetchMock = vi.fn(); vi.stubGlobal("fetch", fetchMock); function mockRegistry(versions: string[]) { fetchMock.mockResolvedValue({ ok: true, json: async () => ({ versions: Object.fromEntries(versions.map((v) => [v, {}])) }), }); } beforeEach(() => { vi.restoreAllMocks(); fetchMock.mockReset(); }); describe("fmtDate", async () => { const { fmtDate } = await import("../../scripts/bump-version.ts"); it("formats date as YYMMDD", () => { expect(fmtDate(new Date("2026-03-11"))).toBe("260311"); expect(fmtDate(new Date("2025-01-05"))).toBe("250105"); }); }); describe("resolveVersion", async () => { const { resolveVersion } = await import("../../scripts/bump-version.ts"); const cases = [ { name: "no existing versions", existing: [], expected: "3.0.260311-beta" }, { name: "one existing version", existing: ["3.0.260311-beta"], expected: "3.0.260311-beta.2" }, { name: "multiple existing versions", existing: ["3.0.260311-beta", "3.0.260311-beta.2"], expected: "3.0.260311-beta.3", }, { name: "other dates only", existing: ["3.0.260310-beta", "3.0.260310-beta.3"], expected: "3.0.260311-beta", }, { name: "without prerelease tag", existing: ["3.0.260311-1", "3.0.260311-2"], expected: "3.0.260311-3", prerelease: "", }, { name: "without prerelease, no existing", existing: [], expected: "3.0.260311", prerelease: "", }, ]; for (const { name, existing, expected, prerelease } of cases) { it(`${name}: ${existing.join(", ") || "(none)"} → ${expected}`, async () => { mockRegistry(existing); const version = await resolveVersion( "nitro", "260311", ...(prerelease !== undefined ? [prerelease] : []) ); expect(version).toBe(expected); expect(semverValid(version)).toBe(version); }); } }); ================================================ FILE: test/unit/chunks.test.ts ================================================ import { describe, expect, it } from "vitest"; import type { Nitro } from "nitro/types"; import { NODE_MODULES_RE, libChunkName, pathToPkgName, getChunkName, routeToFsPath, } from "../../src/build/chunks.ts"; function createChunk(name: string, moduleIds: string[]): { name: string; moduleIds: string[] } { return { name, moduleIds }; } function createNitro(overrides: Partial = {}): Nitro { return { options: { buildDir: "/build", tasks: {}, ...overrides.options }, routing: { routes: { routes: [] }, ...overrides.routing, }, ...overrides, } as unknown as Nitro; } describe("NODE_MODULES_RE", () => { it.each([ ["/foo/node_modules/bar/index.js", true], ["node_modules/bar/index.js", true], ["node_modules\\bar\\index.js", true], ["/foo/node_modules/nitro/dist/index.js", false], ["/foo/node_modules/nitro-nightly/dist/index.js", false], ["/foo/node_modules/.nitro", false], ["/foo/node_modules/.cache", false], ["/foo/src/bar.js", false], ])("%s → %s", (path, expected) => { expect(NODE_MODULES_RE.test(path)).toBe(expected); }); }); describe("pathToPkgName", () => { it.each([ ["/foo/node_modules/express/index.js", "express"], ["/foo/node_modules/@h3/core/index.js", "@h3/core"], ["C:\\proj\\node_modules\\express\\index.js", "express"], ["C:\\proj\\node_modules\\@h3\\core\\index.js", "@h3\\core"], ["/node_modules/nitro-nightly/dist/index.js", "nitro"], ["/node_modules/a/node_modules/b/index.js", "b"], ["/foo/src/bar.js", undefined], ])("%s → %s", (path, expected) => { expect(pathToPkgName(path)).toBe(expected); }); }); describe("libChunkName", () => { it.each([ ["/node_modules/express/index.js", "_libs/express"], ["/node_modules/@h3/core/index.js", "_libs/@h3/core"], ["/src/utils/foo.ts", undefined], ["/node_modules/nitro-nightly/dist/index.js", "_libs/nitro"], ])("%s → %s", (id, expected) => { expect(libChunkName(id)).toBe(expected); }); }); describe("routeToFsPath", () => { it.each([ ["/api/hello", "api/hello"], ["/api/users/:id", "api/users/[id]"], ["/", "index"], ["/api/users/:id/posts/*", "api/users/[id]/posts/[...]"], ])("%s → %s", (route, expected) => { expect(routeToFsPath(route)).toBe(expected); }); }); describe("getChunkName", () => { const nitro = createNitro(); it.each<[string, { name: string; moduleIds: string[] }, string]>([ ["rolldown-runtime", createChunk("rolldown-runtime", []), "_runtime.mjs"], ["_ chunks are preserved", createChunk("_shared", ["/src/foo.ts"]), "_shared.mjs"], [ "all node_modules (sorted a-z)", createChunk("vendor", ["/node_modules/express/index.js", "/node_modules/h3/dist/index.mjs"]), "_libs/express+h3.mjs", ], [ "single node_modules package", createChunk("vendor", ["/node_modules/a/index.js"]), "_libs/a.mjs", ], [ "node_modules names exceed 30 chars", createChunk("_libs/vendor", [ "/node_modules/some-very-long-package-name/index.js", "/node_modules/another-very-long-name/index.js", ]), "_libs/vendor+[...].mjs", ], [ "3 node_modules sorted a-z", createChunk("vendor", [ "/node_modules/zod/index.js", "/node_modules/ab/index.js", "/node_modules/h3/dist/index.mjs", ]), "_libs/ab+h3+zod.mjs", ], [ "scoped packages use __ separator", createChunk("vendor", ["/node_modules/@h3/core/index.js", "/node_modules/defu/index.js"]), "_libs/defu+h3__core.mjs", ], ["empty moduleIds (vacuous every())", createChunk("my-chunk", []), "_libs/_.mjs"], [ "virtual:raw modules", createChunk("raw", ["\0virtual:raw:foo", "#virtual:raw:bar"]), "_raw/[name].mjs", ], ["all virtual modules", createChunk("virt", ["\0something", "#other"]), "_virtual/[name].mjs"], ["wasm modules", createChunk("wasm", ["/src/module.wasm"]), "_wasm/[name].mjs"], [ "vite/services modules", createChunk("ssr", ["/vite/services/component.js"]), "_ssr/[name].mjs", ], ["buildDir modules", createChunk("build", ["/build/generated.js"]), "_build/[name].mjs"], [ "mixed virtual + wasm", createChunk("mixed", ["\0virtual:something", "/src/module.wasm"]), "_wasm/[name].mjs", ], ["fallback to _chunks", createChunk("misc", ["/src/utils/helper.ts"]), "_chunks/[name].mjs"], ])("%s → %s", (_label, chunk, expected) => { expect(getChunkName(chunk, nitro)).toBe(expected); }); it("returns _routes/.mjs for route handler", () => { const n = createNitro({ routing: { routes: { routes: [{ data: [{ route: "/api/hello", handler: "/src/routes/api/hello.ts" }] }], }, }, } as any); expect(getChunkName(createChunk("route", ["/src/routes/api/hello.ts"]), n)).toBe( "_routes/api/hello.mjs" ); }); it("returns _routes/.mjs for dynamic route", () => { const n = createNitro({ routing: { routes: { routes: [ { data: [{ route: "/api/users/:id", handler: "/src/routes/api/users/[id].ts" }], }, ], }, }, } as any); expect(getChunkName(createChunk("route", ["/src/routes/api/users/[id].ts"]), n)).toBe( "_routes/api/users/[id].mjs" ); }); it("returns _routes/index.mjs for root route", () => { const n = createNitro({ routing: { routes: { routes: [{ data: [{ route: "/", handler: "/src/routes/index.ts" }] }], }, }, } as any); expect(getChunkName(createChunk("route", ["/src/routes/index.ts"]), n)).toBe( "_routes/index.mjs" ); }); it("returns _tasks/[name].mjs for task handler", () => { const n = createNitro({ options: { buildDir: "/build", tasks: { "db:migrate": { handler: "/src/tasks/migrate.ts" } }, }, } as any); expect(getChunkName(createChunk("task", ["/src/tasks/migrate.ts"]), n)).toBe( "_tasks/[name].mjs" ); }); }); ================================================ FILE: test/unit/config-loader-env.test.ts ================================================ import { mkdtemp, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "pathe"; import { afterEach, describe, expect, it, vi } from "vitest"; vi.mock("nitro/meta", () => ({ version: "0.0.0-test", runtimeDir: "/tmp", presetsDir: "/tmp", pkgDir: "/tmp", runtimeDependencies: [], })); const originalNodeEnv = process.env.NODE_ENV; const tempDirs: string[] = []; async function createFixtureConfig() { const rootDir = await mkdtemp(join(tmpdir(), "nitro-config-env-")); tempDirs.push(rootDir); await writeFile( join(rootDir, "nitro.config.ts"), `export default defineNitroConfig({ preset: 'node-server', routeRules: { '/base': { headers: { 'x-env': 'base' } } }, $production: { routeRules: { '/prod': { headers: { 'x-env': 'production' } } } }, $development: { routeRules: { '/dev': { headers: { 'x-env': 'development' } } } } }) ` ); return rootDir; } afterEach(async () => { if (originalNodeEnv === undefined) { delete process.env.NODE_ENV; } else { process.env.NODE_ENV = originalNodeEnv; } for (const dir of tempDirs.splice(0, tempDirs.length)) { await rm(dir, { recursive: true, force: true }); } }); describe.skip("config loader env layers", () => { it("applies $production when NODE_ENV is unset and dev=false", async () => { delete process.env.NODE_ENV; const rootDir = await createFixtureConfig(); const { loadOptions } = await import("../../src/config/loader.ts"); const options = await loadOptions({ rootDir, dev: false }); expect(options.routeRules["/prod"]?.headers?.["x-env"]).toBe("production"); expect(options.routeRules["/dev"]).toBeUndefined(); expect(options.routeRules["/base"]?.headers?.["x-env"]).toBe("base"); }); it("applies $development when NODE_ENV is unset and dev=true", async () => { delete process.env.NODE_ENV; const rootDir = await createFixtureConfig(); const { loadOptions } = await import("../../src/config/loader.ts"); const options = await loadOptions({ rootDir, dev: true }); expect(options.routeRules["/dev"]?.headers?.["x-env"]).toBe("development"); expect(options.routeRules["/prod"]).toBeUndefined(); expect(options.routeRules["/base"]?.headers?.["x-env"]).toBe("base"); }); }); ================================================ FILE: test/unit/runtime-config.env.test.ts ================================================ import { describe, expect, it } from "vitest"; import { applyEnv } from "../../src/runtime/internal/runtime-config.ts"; describe("env utils", () => { describe("applyEnv", () => { const tests = [ { config: { a: "1", b: "2" }, env: { NITRO_A: "123" }, expected: { a: "123", b: "2" }, }, { config: { feature: { options: { option1: "original", option2: 123 } } }, env: { NITRO_FEATURE_OPTIONS_OPTION1: "env" }, expected: { feature: { options: { option1: "env", option2: 123 } } }, }, ]; for (const test of tests) { it(`Config: ${JSON.stringify(test.config)} Env: { ${Object.entries(test.env) .map(([key, value]) => `${key}=${JSON.stringify(value)}`) .join(" ")} }`, () => { for (const key in test.env) { process.env[key] = test.env[key as keyof typeof test.env] as string; } expect(applyEnv(test.config, { prefix: "NITRO_" })).toEqual(test.expected); for (const key in test.env) { delete process.env[key]; } }); } }); describe("envExpansion", () => { const tests = [ // envExpansion is false by default { config: { mail: "{{MAIL_HOST}}:3366" }, envOptions: {}, env: { MAIL_HOST: "http://localhost" }, expected: { mail: "{{MAIL_HOST}}:3366" }, }, // Fallback to the original string { config: { mail: "{{MAIL_HOST}}:3366" }, envOptions: { envExpansion: true }, env: {}, expected: { mail: "{{MAIL_HOST}}:3366" }, }, // Base usage { config: { mail: "{{MAIL_HOST}}" }, envOptions: { envExpansion: true }, env: { MAIL_HOST: "http://localhost" }, expected: { mail: "http://localhost" }, }, // With multiple envs { config: { mail: "{{MAIL_SCHEME}}://{{MAIL_HOST}}:{{MAIL_PORT}}" }, envOptions: { envExpansion: true }, env: { MAIL_SCHEME: "http", MAIL_HOST: "localhost", MAIL_PORT: "3366" }, expected: { mail: "http://localhost:3366" }, }, ]; for (const test of tests) { it(`Config: ${JSON.stringify(test.config)} ${JSON.stringify( test.envOptions )} Env: { ${Object.entries(test.env) .map(([key, value]) => `${key}=${JSON.stringify(value)}`) .join(" ")} }`, () => { for (const key in test.env) { process.env[key] = test.env[key as keyof typeof test.env]; } expect(applyEnv(test.config, test.envOptions)).toEqual(test.expected); for (const key in test.env) { delete process.env[key]; } }); } }); }); ================================================ FILE: test/unit/runtime-config.test.ts ================================================ import { describe, expect, it, vi } from "vitest"; import { normalizeRuntimeConfig } from "../../src/config/resolvers/runtime-config.ts"; import type { NitroConfig } from "nitro/types"; const defaultRuntimeConfig = { textProperty: "value", numberProperty: 42, booleanProperty: true, arrayProperty: ["A", "B", "C"], objectProperty: { innerProperty: "value", }, mixedArrayProperty: [ "A", "B", { inner: { innerProperty: "value", }, }, ], }; const nitroConfig: NitroConfig = { runtimeConfig: defaultRuntimeConfig, baseURL: "https://example.com", experimental: { envExpansion: false, }, }; describe("normalizeRuntimeConfig", () => { it("should not warn on a serializable runtime config", () => { const warnSpy = vi.spyOn(console, "warn"); normalizeRuntimeConfig(nitroConfig); expect(warnSpy).not.toHaveBeenCalled(); }); it("should not warn when primitive prototype is changed", () => { const warnSpy = vi.spyOn(console, "warn"); // https://github.com/nitrojs/nitro/pull/2902 (String.prototype as any).brokenFunction = () => undefined; normalizeRuntimeConfig(nitroConfig); expect(warnSpy).not.toHaveBeenCalled(); delete (String.prototype as any).brokenFunction; }); it("should throw a warning when runtimeConfig is not serializable", () => { // const originalWarn = console.warn; const spyWarn = (console.warn = vi.fn()); normalizeRuntimeConfig({ ...nitroConfig, runtimeConfig: { ...defaultRuntimeConfig, brokenProperty: new Map(), }, }); console.warn = spyWarn; expect(spyWarn).toHaveBeenCalledOnce(); }); }); ================================================ FILE: test/unit/sourcemap-min.test.ts ================================================ import { describe, expect, it } from "vitest"; import { sourcemapMinify } from "../../src/build/plugins/sourcemap-min.ts"; type BundleAsset = { type: "asset"; source: string }; function createSourcemapAsset(sourcemap: { sources?: string[]; sourcesContent?: string[]; mappings?: string; x_google_ignoreList?: number[]; }): BundleAsset { return { type: "asset", source: JSON.stringify({ version: 3, sources: [], mappings: "AAAA,CAAC", ...sourcemap, }), }; } function runPlugin(bundle: Record) { const plugin = sourcemapMinify(); (plugin.generateBundle as Function).call(null, {}, bundle); const results: Record> = {}; for (const [key, asset] of Object.entries(bundle)) { if (key.endsWith(".map")) { results[key] = JSON.parse(asset.source); } } return results; } describe("sourcemapMinify", () => { it("removes sourcesContent from all sourcemaps", () => { const bundle = { "index.mjs.map": createSourcemapAsset({ sources: ["src/index.ts"], sourcesContent: ["export default 42;"], }), }; const results = runPlugin(bundle); expect(results["index.mjs.map"].sourcesContent).toBeUndefined(); }); it("removes x_google_ignoreList from all sourcemaps", () => { const bundle = { "index.mjs.map": createSourcemapAsset({ sources: ["src/index.ts"], x_google_ignoreList: [0], }), }; const results = runPlugin(bundle); expect(results["index.mjs.map"].x_google_ignoreList).toBeUndefined(); }); it("wipes mappings for pure library chunks", () => { const bundle = { "_libs/express.mjs.map": createSourcemapAsset({ sources: [ "../../node_modules/express/index.js", "../../node_modules/express/lib/router.js", ], mappings: "AAAA,CAAC", }), }; const results = runPlugin(bundle); expect(results["_libs/express.mjs.map"].mappings).toBe(""); }); it("preserves mappings for pure user code chunks", () => { const bundle = { "_routes/api/hello.mjs.map": createSourcemapAsset({ sources: ["src/routes/api/hello.ts"], mappings: "AAAA,CAAC", }), }; const results = runPlugin(bundle); expect(results["_routes/api/hello.mjs.map"].mappings).toBe("AAAA,CAAC"); }); it("preserves mappings when library is hoisted into user chunk", () => { const bundle = { "_routes/api/hello.mjs.map": createSourcemapAsset({ sources: ["src/routes/api/hello.ts", "../../node_modules/some-lib/index.js"], mappings: "AAAA,CAAC", }), }; const results = runPlugin(bundle); expect(results["_routes/api/hello.mjs.map"].mappings).toBe("AAAA,CAAC"); }); it("skips non-sourcemap files", () => { const bundle = { "index.mjs": { type: "asset" as const, source: "console.log(42)" }, }; runPlugin(bundle as any); expect(bundle["index.mjs"].source).toBe("console.log(42)"); }); it("handles empty sources array", () => { const bundle = { "chunk.mjs.map": createSourcemapAsset({ sources: [], mappings: "AAAA,CAAC", }), }; const results = runPlugin(bundle); expect(results["chunk.mjs.map"].mappings).toBe(""); }); }); ================================================ FILE: test/unit/static-middleware.test.ts ================================================ import { beforeEach, describe, expect, it, vi } from "vitest"; import { mockEvent } from "h3"; import handler from "../../src/runtime/internal/static.ts"; const { getAsset, isPublicAssetURL, readAsset } = vi.hoisted(() => ({ getAsset: vi.fn(), isPublicAssetURL: vi.fn(), readAsset: vi.fn(), })); vi.mock("#nitro/virtual/public-assets", () => ({ getAsset, isPublicAssetURL, readAsset, })); function createEvent(pathname: string, acceptEncoding = "") { const event = mockEvent(`http://localhost${pathname}`, { headers: acceptEncoding ? { "accept-encoding": acceptEncoding } : undefined, }); event.res.headers.set("Vary", "Origin"); event.res.headers.set("Cache-Control", "max-age=3600"); return event; } describe("runtime static middleware", () => { beforeEach(() => { getAsset.mockReset(); isPublicAssetURL.mockReset(); readAsset.mockReset(); }); it("does not append Accept-Encoding vary when no asset is matched", async () => { getAsset.mockReturnValue(undefined); isPublicAssetURL.mockReturnValue(true); const event = createEvent("/foo-missing.css", "gzip"); expect(() => handler(event)).toThrow("404"); expect(event.res.headers.get("Vary")).toBe("Origin"); expect(event.res.headers.get("Cache-Control")).toBeNull(); }); it("appends Accept-Encoding vary when a compressed asset is matched", async () => { getAsset.mockImplementation((id: string) => { if (id === "/foo.css.gz") { return { etag: '"test"', mtime: Date.now(), type: "text/css", encoding: "gzip", size: 1, }; } return undefined; }); isPublicAssetURL.mockReturnValue(true); readAsset.mockResolvedValue("body"); const event = createEvent("/foo.css", "gzip"); await handler(event); expect(event.res.headers.get("Vary")).toContain("Origin"); expect(event.res.headers.get("Vary")).toContain("Accept-Encoding"); }); }); ================================================ FILE: test/unit/virtual.test.ts ================================================ import { describe, expect, it } from "vitest"; import { serverFetch } from "nitro/app"; describe("virtual modules", () => { it("app fetch", async () => { const res = await serverFetch("/"); expect(res.status).toBe(404); expect(await res.text()).to.include("Cannot find any route matching"); }); }); ================================================ FILE: test/unit/zephyr-preset.test.ts ================================================ import { afterEach, describe, expect, it, vi } from "vitest"; const ZEPHYR_PRESET_PATH = "../../src/presets/zephyr/preset.ts"; const importDepMock = vi.hoisted(() => vi.fn()); vi.mock("../../src/utils/dep.ts", () => ({ importDep: importDepMock, })); async function getZephyrPreset() { const { default: presets } = await import(ZEPHYR_PRESET_PATH); return presets[0]; } describe("zephyr preset", () => { afterEach(() => { vi.restoreAllMocks(); vi.clearAllMocks(); vi.resetModules(); delete (globalThis as any).__nitroDeploying__; delete process.env.NITRO_INTERNAL_ZEPHYR_SKIP_DEPLOY_ON_BUILD; }); it("extends base-worker", async () => { const preset = await getZephyrPreset(); expect(preset.extends).toBe("base-worker"); expect(preset.output?.publicDir).toBe("{{ output.dir }}/client/{{ baseURL }}"); expect(preset.commands?.deploy).toBeUndefined(); }); it("adds cloudflare unenv presets", async () => { const preset = await getZephyrPreset(); const hooks = preset.hooks!; const nitro = { options: { preset: "zephyr", output: { dir: "/tmp/zephyr-output", serverDir: "/tmp/zephyr-output/server", }, unenv: [], }, logger: { info: vi.fn(), success: vi.fn(), }, } as any; await hooks["build:before"]?.(nitro); expect(nitro.options.unenv).toHaveLength(2); expect(nitro.options.unenv[0].meta?.name).toBe("nitro:cloudflare-externals"); expect(nitro.options.unenv[1].meta?.name).toBe("nitro:cloudflare-node-compat"); expect(nitro.logger.info).not.toHaveBeenCalled(); expect(nitro.logger.success).not.toHaveBeenCalled(); }); it("deploys on compiled hook by default", async () => { const uploadOutputToZephyr = vi.fn().mockResolvedValue({ deploymentUrl: "https://example.zephyr-cloud.io", entrypoint: "server/index.mjs", }); importDepMock.mockResolvedValue({ uploadOutputToZephyr }); (globalThis as any).__nitroDeploying__ = true; const preset = await getZephyrPreset(); const hooks = preset.hooks!; const nitro = { options: { rootDir: "/tmp/project", baseURL: "/docs/", output: { dir: "/tmp/zephyr-output", publicDir: "client/docs", }, }, logger: { info: vi.fn(), success: vi.fn(), }, } as any; await hooks.compiled?.(nitro); expect(importDepMock).toHaveBeenCalledWith({ id: "zephyr-agent", reason: "deploying to Zephyr", dir: "/tmp/project", }); expect(uploadOutputToZephyr).toHaveBeenCalledWith({ rootDir: "/tmp/project", baseURL: "/docs/", outputDir: "/tmp/zephyr-output", publicDir: "/tmp/zephyr-output/client/docs", }); expect(nitro.logger.success).toHaveBeenCalledWith( "[zephyr-nitro-preset] Zephyr deployment succeeded: https://example.zephyr-cloud.io" ); expect(nitro.logger.info).not.toHaveBeenCalled(); }); it("can skip deploy on build", async () => { const uploadOutputToZephyr = vi.fn().mockResolvedValue({ deploymentUrl: "https://example.zephyr-cloud.io", entrypoint: "server/index.mjs", }); importDepMock.mockResolvedValue({ uploadOutputToZephyr }); const preset = await getZephyrPreset(); const hooks = preset.hooks!; const nitro = { options: { zephyr: { deployOnBuild: false, }, output: { dir: "/tmp/zephyr-output", }, }, logger: { info: vi.fn(), success: vi.fn(), }, } as any; await hooks.compiled?.(nitro); expect(importDepMock).not.toHaveBeenCalled(); expect(uploadOutputToZephyr).not.toHaveBeenCalled(); expect(nitro.logger.info).toHaveBeenCalledWith( "[zephyr-nitro-preset] Zephyr deploy skipped on build." ); expect(nitro.logger.success).not.toHaveBeenCalled(); }); }); ================================================ FILE: test/vite/hmr-fixture/api/state.ts ================================================ import { state } from "../shared.ts"; export default () => ({ state }); ================================================ FILE: test/vite/hmr-fixture/app/entry-client.ts ================================================ import { state } from "../shared.ts"; document.getElementById("client-state-value")!.textContent = state + " (modified)"; ================================================ FILE: test/vite/hmr-fixture/app/entry-server.ts ================================================ import { html } from "nitro/h3"; import { serverFetch } from "nitro"; import { state } from "../shared.ts"; export default { fetch: async () => { const apiData = (await serverFetch("/api/state").then((res) => res.json())) as { state: number; }; const viteClientScript = ""; const clientScript = ""; return html` ${viteClientScript}

    SSR Page

    [SSR] state: ${state}

    [API] state: ${apiData.state}

    [Client] state: ?

    ${clientScript} `; }, }; ================================================ FILE: test/vite/hmr-fixture/shared.ts ================================================ export const state = 1; ================================================ FILE: test/vite/hmr-fixture/tsconfig.json ================================================ { "extends": "nitro/tsconfig" } ================================================ FILE: test/vite/hmr-fixture/vite.config.ts ================================================ import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro({ serverDir: "./" })], }); ================================================ FILE: test/vite/hmr.test.ts ================================================ import { join } from "pathe"; import { readFileSync, writeFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; import type { ViteDevServer } from "vite"; import { describe, test, expect, beforeAll, afterEach, afterAll } from "vitest"; const { createServer } = (await import( process.env.NITRO_VITE_PKG || "vite" )) as typeof import("vite"); describe("vite:hmr", { sequential: true }, () => { let server: ViteDevServer; let serverURL: string; const wsMessages: any[] = []; const rootDir = fileURLToPath(new URL("./hmr-fixture", import.meta.url)); const files = { client: openFileForEditing(join(rootDir, "app/entry-client.ts")), api: openFileForEditing(join(rootDir, "api/state.ts")), shared: openFileForEditing(join(rootDir, "shared.ts")), ssr: openFileForEditing(join(rootDir, "app/entry-server.ts")), }; beforeAll(async () => { process.chdir(rootDir); server = await createServer({ root: rootDir }); const originalSend = server.ws.send.bind(server.ws); server.ws.send = function (payload: any) { wsMessages.push(payload); return originalSend(payload); }; await server.listen("0" as unknown as number); const addr = server.httpServer?.address() as { port: number; address: string; family: string }; serverURL = `http://${addr.family === "IPv6" ? `[${addr.address}]` : addr.address}:${addr.port}`; const html = await fetch(serverURL).then((r) => r.text()); expect(html).toContain("

    SSR Page

    "); expect(html).toContain("[SSR] state: 1"); expect(html).toContain("[API] state: 1"); }, 30_000); afterAll(async () => { await server?.close(); }); afterEach(async () => { wsMessages.length = 0; let restored = false; for (const file of Object.values(files)) { if (file.restore()) { restored = true; } } if (restored) { await waitFor(() => wsMessages.length > 0, 500); } wsMessages.length = 0; }); test("editing API entry", async () => { files.api.update((content) => content.replace("({ state })", '({ state: state + " (modified)" })') ); await pollResponse(`${serverURL}/api/state`, /modified/); expect(wsMessages).toMatchObject([{ type: "full-reload" }]); }); test("Editing client entry (no full-reload)", async () => { files.client.update((content) => content.replace(`+ ""`, `+ " (modified)"`)); await pollResponse(`${serverURL}/app/entry-client.ts`, /modified/); expect(wsMessages.length).toBe(0); }); test("editing SSR entry (no full-reload)", async () => { files.ssr.update((content) => content.replace("

    SSR Page

    ", "

    Modified SSR Page

    ") ); await pollResponse(serverURL, /Modified SSR Page/); expect(wsMessages.length).toBe(0); }); test("Editing shared entry", async () => { files.shared.update((content) => content.replace(`state = 1`, `state = 2`)); await pollResponse( `${serverURL}`, (txt) => txt.includes("state: 2") && !txt.includes("state: 1") ); expect(wsMessages).toMatchObject([{ type: "full-reload" }]); }); }); function openFileForEditing(path: string) { const originalContent = readFileSync(path, "utf-8"); return { path, update(cb: (content: string) => string) { const currentContent = readFileSync(path, "utf-8"); const newContent = cb(currentContent); writeFileSync(path, newContent); }, restore() { if (readFileSync(path, "utf-8") !== originalContent) { writeFileSync(path, originalContent); return true; } return false; }, }; } function waitFor(check: () => boolean, duration: number): Promise { const start = Date.now(); return new Promise((resolve) => { const poll = () => { if (check() || Date.now() - start > duration) { resolve(); } else { setTimeout(poll, 10); } }; poll(); }); } function pollResponse( url: string, match: RegExp | ((txt: string) => boolean), timeout = 5000 ): Promise { const start = Date.now(); let lastResponse = ""; return new Promise((resolve, reject) => { const check = async () => { try { const response = await fetch(url); lastResponse = await response.text(); if (typeof match === "function" ? match(lastResponse) : match.test(lastResponse)) { resolve(lastResponse); } else if (Date.now() - start > timeout) { reject( new Error( `Timeout waiting for response to match ${match} at ${url}. Last response: ${lastResponse}` ) ); } else { setTimeout(check, 100); } } catch (err) { reject(err); } }; check(); }); } ================================================ FILE: tsconfig.json ================================================ { "extends": "./lib/tsconfig.json", "compilerOptions": { "module": "NodeNext", "moduleResolution": "NodeNext", "allowSyntheticDefaultImports": true, "erasableSyntaxOnly": true, "noUncheckedIndexedAccess": false, "noImplicitReturns": false, "jsx": "preserve", "lib": ["es2022", "webworker", "dom.iterable"], "types": ["node", "@cloudflare/workers-types"] }, "exclude": [ "examples/import-alias/**", "examples/vite-rsc/**", "test/fixture/server/routes/jsx.tsx", "examples/vite-ssr-html/vite.config.ts", "examples/vite-ssr-solid/src/entry-server.tsx", "examples/vite-ssr-solid/src/entry-client.tsx" ] } ================================================ FILE: vitest.config.ts ================================================ import { defineConfig } from "vitest/config"; process.env.NODE_ENV = "production"; export default defineConfig({ test: { testTimeout: 30_000, coverage: { reporter: ["text", "clover", "json"], include: ["src/**/*.ts", "!src/types/**/*.ts"], }, include: ["test/**/*.test.ts"], }, });