Skip to content

Conversation

@RihanArfan
Copy link
Member

@RihanArfan RihanArfan commented Jan 28, 2026

❓ 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

Adds examples section to the docs

πŸ“ Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

@RihanArfan RihanArfan requested a review from pi0 as a code owner January 28, 2026 18:32
@vercel
Copy link

vercel bot commented Jan 28, 2026

@RihanArfan is attempting to deploy a commit to the Nitro Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Jan 28, 2026

πŸ“ Walkthrough

Walkthrough

Adds an examples system: Nuxt Content collection for examples, a Sandbox iframe component, examples layouts/pages, a server route to serve raw example markdown, example utilities, ~30+ example README files, and small docs package/workspace dependency updates.

Changes

Cohort / File(s) Summary
Docs UI Components & Pages
docs/.docs/components/Sandbox.vue, docs/.docs/layouts/examples.vue, docs/.docs/pages/examples/[...slug].vue, docs/.docs/pages/examples/index.vue
New Vue SFCs: Sandbox iframe, examples layout, example detail page, and examples index. Inspect data fetching (useAsyncData/queryCollection), path normalization, SEO/head config, StackBlitz URL construction, and client/server boundaries.
Nuxt Content Config & Landing Page
docs/.docs/content.config.ts, docs/4.examples/0.index.md
Adds examples content collection sourcing README.md under ../../examples with schema (category, icon) and a static Examples index page. Check include/exclude patterns and resolve(cwd) usage.
Server Route: raw example markdown
docs/.docs/server/routes/raw/examples/[...slug].md.get.ts
New server handler mapping /raw/examples/<name>.md β†’ content /examples/<name>/readme, validates slug, injects title/description when missing, and returns text/markdown; charset=utf-8. Verify slug handling and content augmentation.
Examples Content
examples/*/README.md, examples/vite-ssr-*/*, ...
Adds ~30+ example README files across many example directories (routing, integrations, SSR, plugins, DB, caching, websockets, tRPC, etc.). Primarily documentation/frontmatterβ€”check frontmatter consistency and paths.
Docs Utilities
docs/.docs/utils/examples.ts
Exports categoryOrder and categoryIcons constants used by the examples UI for ordering and icons.
Docs package & workspace config
docs/package.json, docs/pnpm-workspace.yaml
Adds deps automd, zod; updates devDependency undocs to npm:undocs-nightly@latest. Minor YAML quoting change. Confirm dependency intent and no script regressions.
Other docs plumbing
docs/.docs/components/*, docs/.docs/layouts/*, docs/.docs/pages/*, docs/.docs/server/*
Multiple new files under .docs (layouts, components, pages, server routes). Review imports/relative paths and SSR vs client-only usage across these files.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

πŸš₯ Pre-merge checks | βœ… 3
βœ… Passed checks (3 passed)
Check name Status Explanation
Title check βœ… Passed The title 'docs: add examples' follows conventional commits format with a 'docs' type prefix and a clear, concise description of the changes.
Description check βœ… Passed The PR description is related to the changeset, stating 'Adds examples section to the docs' which aligns with the documentation additions across multiple example files and new documentation pages.
Docstring Coverage βœ… Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
πŸ§ͺ Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❀️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 28, 2026

Open in StackBlitz

npm i https://pkg.pr.new/nitrojs/nitro@3984

commit: 2d61ed2

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

πŸ€– Fix all issues with AI agents
In `@examples/auto-imports/README.md`:
- Around line 33-42: The example contradicts the auto-import claim by explicitly
importing makeGreeting; remove the explicit import line and use makeGreeting
directly in the handler so utilities from server/utils/hello.ts are shown as
auto-imported; update the snippet containing defineHandler and the usage of
makeGreeting("Nitro") accordingly and ensure no import of makeGreeting remains
in that example.

In `@examples/cached-handler/README.md`:
- Around line 22-38: Replace the fragile string-match in shouldBypassCache with
proper query parsing: import getQuery from "nitro/h3" and change the
shouldBypassCache handler (currently using req.url.includes(...)) to accept the
event, call getQuery(event), and return a boolean based on query.skipCache
(e.g., query.skipCache === 'true' or appropriate truthy check). Update any
parameter name from { req } to event where needed and remove the
req.url.includes usage.

In `@examples/custom-error-handler/README.md`:
- Around line 26-31: The current defineErrorHandler returns raw error.message
and always a 500 Response; change it to map known error types to appropriate
status codes (e.g., validation/auth errors β†’ 4xx, others β†’ 5xx), sanitize the
message returned to clients (use a generic message like "Internal Server Error"
or a safe error.code) and log the full error server-side (including stack)
before creating the Response; update the handler in defineErrorHandler and the
Response creation to use the computed status and sanitized client message while
ensuring full error details are written to your logger.

In `@examples/middleware/README.md`:
- Around line 27-35: The example uses an invalid export defineMiddleware from
"nitro/h3"; update the import and wrapper to use defineEventHandler instead:
replace the import of defineMiddleware with defineEventHandler and change the
default export wrapper from defineMiddleware((event) => { ... }) to
defineEventHandler((event) => { ... }) so event.context.auth is set using the
supported Nitro API.

In `@examples/renderer/README.md`:
- Around line 27-44: The renderer function currently interpolates
user-controlled values (url.pathname and apiRes) directly into the HTML string,
creating an XSS risk; fix it by introducing/using an HTML-escape utility to
sanitize url.pathname and apiRes before interpolation (e.g., a helper that
replaces &, <, >, ", ' and / with entities) and replace the direct references in
the template with the escaped values so the Response body uses the sanitized
outputs from renderer.

In `@examples/vite-ssr-preact/README.md`:
- Around line 26-39: The server entry imports App from "./app.jsx" but renders
<Root url={url} />, causing a runtime error; update the render call in export
default.fetch to use the imported component (App) β€” e.g., replace the <Root
url={url} /> usage with <App url={url} /> β€” or alternatively change the import
to import Root from "./app.jsx" so the rendered component and import name match
(refer to the renderToReadableStream call and the App/Root identifiers).
🟑 Minor comments (20)
examples/vite-ssr-tsr-react/README.md-11-24 (1)

11-24: Add a language to the project tree code fence.

MD040 flags the fence without a language. Use text (or bash) for clarity and lint compliance.

βœ… Suggested change
-```
+```text
 vite-ssr-tsr-react/
 β”œβ”€β”€ src/
 β”‚   β”œβ”€β”€ main.tsx          # App entry with router
 β”‚   β”œβ”€β”€ routes/
 β”‚   β”‚   β”œβ”€β”€ __root.tsx    # Root layout
 β”‚   β”‚   └── index.tsx     # Home route
 β”‚   β”œβ”€β”€ routeTree.gen.ts  # Generated route tree
 β”‚   └── assets/
 β”‚       └── main.css
 β”œβ”€β”€ index.html
 β”œβ”€β”€ vite.config.mjs
 └── tsconfig.json
</details>

</blockquote></details>
<details>
<summary>examples/plugins/README.md-32-33 (1)</summary><blockquote>

`32-33`: **Use a valid Content-Type value.**

`html; charset=utf-8` is not a valid MIME type; use `text/html; charset=utf-8`.

<details>
<summary>πŸ“ Suggested fix</summary>

```diff
-  hooks.hook("response", (event) => {
-    event.headers.set("content-type", "html; charset=utf-8");
-  });
+  hooks.hook("response", (event) => {
+    event.headers.set("content-type", "text/html; charset=utf-8");
+  });
examples/plugins/README.md-11-19 (1)

11-19: Add a language to the fenced block.

Markdownlint MD040: the fence at Line 11 should specify a language (e.g., text) to avoid lint errors.

πŸ“ Suggested fix
-```
+```text
 plugins/
 β”œβ”€β”€ server/
 β”‚   └── plugins/
 β”‚       └── test.ts       # Custom plugin
 β”œβ”€β”€ server.ts             # Main handler
 β”œβ”€β”€ nitro.config.ts
 └── vite.config.ts
</details>

</blockquote></details>
<details>
<summary>examples/cached-handler/README.md-11-16 (1)</summary><blockquote>

`11-16`: **Add language specifier to fenced code block.**

The fenced code block showing the directory structure should have a language identifier for proper rendering and consistency.

<details>
<summary>πŸ“ Proposed fix</summary>

```diff
-```
+```txt
 cached-handler/
 β”œβ”€β”€ server.ts             # Cached handler
 β”œβ”€β”€ nitro.config.ts
 └── vite.config.ts
</details>

</blockquote></details>
<details>
<summary>examples/vite-ssr-tss-react/README.md-11-25 (1)</summary><blockquote>

`11-25`: **Add language specifier to the project structure code block.**

The fenced code block showing the project structure should include a language specifier for better rendering and linting compliance.

<details>
<summary>πŸ“ Proposed fix</summary>

```diff
-```
+```text
 vite-ssr-tss-react/
 β”œβ”€β”€ src/
 β”‚   β”œβ”€β”€ router.tsx        # Router configuration
examples/hello-world/README.md-11-16 (1)

11-16: Add language specifier to the project structure code block.

The fenced code block showing the project structure should include a language specifier for better rendering and linting compliance.

πŸ“ Proposed fix
-```
+```text
 hello-world/
 β”œβ”€β”€ server.ts         # Main server entry
 β”œβ”€β”€ nitro.config.ts   # Nitro configuration
examples/import-alias/README.md-11-20 (1)

11-20: Add language specifier to the project structure code block.

The fenced code block showing the project structure should include a language specifier for better rendering and linting compliance.

πŸ“ Proposed fix
-```
+```text
 import-alias/
 β”œβ”€β”€ server/
 β”‚   β”œβ”€β”€ routes/
examples/shiki/README.md-11-17 (1)

11-17: Add language specifier to the project structure code block.

The fenced code block showing the project structure should include a language specifier for better rendering and linting compliance.

πŸ“ Proposed fix
-```
+```text
 shiki/
 β”œβ”€β”€ index.html            # HTML with server scripts
 β”œβ”€β”€ styles.css            # Syntax theme styles
examples/vite-ssr-html/README.md-11-20 (1)

11-20: Add language specifier to the project structure code block.

The fenced code block showing the project structure should include a language specifier for better rendering and linting compliance.

πŸ“ Proposed fix
-```
+```text
 vite-ssr-html/
 β”œβ”€β”€ app/
 β”‚   └── entry-server.ts    # Server-side entry point
examples/auto-imports/README.md-11-19 (1)

11-19: Add language specifier to the project structure code block.

The fenced code block showing the project structure should include a language specifier for better rendering and linting compliance.

πŸ“ Proposed fix
-```
+```text
 auto-imports/
 β”œβ”€β”€ server/
 β”‚   └── utils/
examples/import-alias/README.md-26-35 (1)

26-35: Remove .ts extensions from import paths.

The import statements include .ts file extensions (lines 27-28), which is non-standard for TypeScript. TypeScript module resolution automatically locates .ts files without explicit extensions. Update the imports to use extensionless paths: import { sum } from "~server/utils/math" and import { rand } from "#server/utils/math". This aligns with TypeScript best practices and the approach used in other Nitro examples.

examples/server-fetch/README.md-11-18 (1)

11-18: Add a language tag to the project-structure fence.
Markdownlint (MD040) will fail because the fenced block has no language; use text/plaintext.

πŸ’‘ Suggested fix
-```
+```text
 server-fetch/
 β”œβ”€β”€ routes/
 β”‚   β”œβ”€β”€ index.ts          # Main route using server fetch
 β”‚   └── hello.ts          # Internal API route
 β”œβ”€β”€ nitro.config.ts
 └── vite.config.ts

</details>

</blockquote></details>
<details>
<summary>examples/vite-ssr-solid/README.md-11-20 (1)</summary><blockquote>

`11-20`: **Add a language tag to the project-structure fence.**  
Markdownlint (MD040) will fail because the fenced block has no language; use `text`/`plaintext`.

<details>
<summary>πŸ’‘ Suggested fix</summary>

```diff
-```
+```text
 vite-ssr-solid/
 β”œβ”€β”€ src/
 β”‚   β”œβ”€β”€ app.tsx           # Solid app component
 β”‚   β”œβ”€β”€ entry-client.tsx  # Client hydration entry
 β”‚   β”œβ”€β”€ entry-server.tsx  # Server render entry
 β”‚   └── styles.css
 β”œβ”€β”€ vite.config.mjs
 └── tsconfig.json

</details>

</blockquote></details>
<details>
<summary>examples/vite-ssr-vue-router/README.md-11-25 (1)</summary><blockquote>

`11-25`: **Add a language tag to the project-structure fence.**  
Markdownlint (MD040) will fail because the fenced block has no language; use `text`/`plaintext`.

<details>
<summary>πŸ’‘ Suggested fix</summary>

```diff
-```
+```text
 vite-ssr-vue-router/
 β”œβ”€β”€ app/
 β”‚   β”œβ”€β”€ app.vue           # Root Vue component
 β”‚   β”œβ”€β”€ entry-client.ts   # Client hydration entry
 β”‚   β”œβ”€β”€ entry-server.ts   # Server render entry
 β”‚   β”œβ”€β”€ routes.ts         # Route definitions
 β”‚   β”œβ”€β”€ pages/
 β”‚   β”‚   β”œβ”€β”€ index.vue
 β”‚   β”‚   β”œβ”€β”€ about.vue
 β”‚   β”‚   └── not-found.vue
 β”‚   └── styles.css
 β”œβ”€β”€ vite.config.mjs
 └── tsconfig.json

</details>

</blockquote></details>
<details>
<summary>examples/websocket/README.md-11-18 (1)</summary><blockquote>

`11-18`: **Add a language tag to the project-structure fence.**  
Markdownlint (MD040) will fail because the fenced block has no language; use `text`/`plaintext`.

<details>
<summary>πŸ’‘ Suggested fix</summary>

```diff
-```
+```text
 websocket/
 β”œβ”€β”€ routes/
 β”‚   └── _ws.ts            # WebSocket handler
 β”œβ”€β”€ index.html            # Client demo
 β”œβ”€β”€ nitro.config.ts
 └── vite.config.ts

</details>

</blockquote></details>
<details>
<summary>examples/vite-ssr-solid/README.md-26-29 (1)</summary><blockquote>

`26-29`: **Fix import path in README example.**

The README.md shows `import { App } from "./app.jsx"` but the actual file is `app.tsx`. This mismatch will cause copy-paste failures.

<details>
<summary>Suggested fix</summary>

```diff
-import { App } from "./app.jsx";
+import { App } from "./app.tsx";
examples/vite-ssr-vue-router/README.md-31-48 (1)

31-48: The README example code is incomplete and cannot be run as-is.

The code snippet on lines 31-48 references href and htmlTemplate without defining them. The actual implementation in the file defines both (lines 15-16 and 49-61), so the README should either show the complete, working code or clearly mark these as placeholders.

πŸ’‘ Suggested fix
 import { createSSRApp } from "vue";
 import { renderToString } from "vue/server-renderer";
 import { RouterView, createMemoryHistory, createRouter } from "vue-router";
 import { routes } from "./routes.ts";
 
+const htmlTemplate = (body: string): string => `<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <title>Vue Router Custom Framework</title>
+</head>
+<body>
+  <div id="root">${body}</div>
+</body>
+</html>`;
+
 async function handler(request: Request): Promise<Response> {
   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 html = await renderToString(app);
   return new Response(htmlTemplate(html), {
docs/.docs/pages/examples/[...slug].vue-12-33 (1)

12-33: Normalize route.path before querying the collection.

If trailing slashes are enabled, queryCollection(...).path(${route.path}/readme) can miss the document and throw a 404. Normalize once and reuse it for the query and exampleName.

πŸ› οΈ Proposed fix
-const { data: page } = await useAsyncData(kebabCase(route.path), () =>
-  queryCollection('examples').path(`${route.path}/readme`).first(),
-)
+const normalizedPath = route.path.replace(/\/$/, '')
+const { data: page } = await useAsyncData(kebabCase(normalizedPath), () =>
+  queryCollection('examples').path(`${normalizedPath}/readme`).first(),
+)

 // Extract example name from route (e.g., "/examples/vite-ssr-html" -> "vite-ssr-html")
 const exampleName = computed(() => {
-  return route.path.replace(/^\/examples\//, '')
+  return normalizedPath.replace(/^\/examples\//, '')
 })
docs/.docs/components/Sandbox.vue-12-22 (1)

12-22: Guard against missing repo to avoid broken embeds.

props are optional, but url always returns a string; if src and repo are both absent you’ll build a .../undefined/... URL and the loading state will never render. Consider returning undefined when the required inputs are missing.

πŸ› οΈ Proposed fix
 const url = computed(() => {
   if (props.src) {
     return props.src
   }
+  if (!props.repo) {
+    return undefined
+  }
   const base = `https://stackblitz.com/github/${props.repo}/tree/${props.branch || 'main'}/${props.dir || ''}`
   const params = new URLSearchParams({
     embed: '1',
     file: props.file || 'README.md',
     theme: colorMode.value,
   })
   return `${base}?${params.toString()}`
 })
docs/.docs/pages/examples/[...slug].vue-104-109 (1)

104-109: Default the Sandbox file to README.md instead of vite.config.ts.

Seven examples in the repository do not have vite.config.ts (vite-nitro-plugin, vite-ssr-preact, vite-ssr-react, vite-ssr-solid, vite-ssr-tsr-react, vite-ssr-tss-react, vite-ssr-vue-router), which will result in a broken or empty initial view in the Sandbox. Additionally, at least one example uses vite.config.mjs instead of .ts. Since all 31 examples have README.md, default to that and override only for Vite-specific examples that require it.

🧹 Nitpick comments (19)
examples/vite-rsc/README.md (1)

11-24: Add a language to the fenced code block.

Markdownlint MD040 is triggered because the fenced block lacks a language specifier. Use text (or bash if you want CLI styling).

examples/middleware/README.md (1)

11-19: Consider adding language specifier to fenced code block.

For the tree structure, you could add text as the language specifier to silence the markdownlint warning.

πŸ“ Suggested change
-```
+```text
 middleware/
 β”œβ”€β”€ server/
 β”‚   └── middleware/
 β”‚       └── auth.ts       # Auth middleware
 β”œβ”€β”€ server.ts             # Main handler
 β”œβ”€β”€ nitro.config.ts
 └── vite.config.ts

</details>

</blockquote></details>
<details>
<summary>examples/express/README.md (1)</summary><blockquote>

`11-16`: **Consider specifying a language for the fenced code block.**

While directory trees are often left plain, adding a language specifier like `text` or `plaintext` would satisfy the markdownlint rule and improve consistency.



<details>
<summary>πŸ“ Proposed fix</summary>

```diff
-```
+```text
 express/
 β”œβ”€β”€ server.node.ts        # Express app (Node.js specific)
 β”œβ”€β”€ nitro.config.ts
 └── vite.config.ts
</details>

</blockquote></details>
<details>
<summary>examples/database/README.md (1)</summary><blockquote>

`11-19`: **Add a language to the fenced block to satisfy MD040.**

Use a plain-text fence for the tree output.


<details>
<summary>πŸ”§ Suggested change</summary>

```diff
-```
+```text
 database/
 β”œβ”€β”€ server.ts             # Database operations
 β”œβ”€β”€ tasks/
 β”‚   └── db/
 β”‚       └── migrate.ts    # Migration task
 β”œβ”€β”€ nitro.config.ts
 └── vite.config.ts
</details>

</blockquote></details>
<details>
<summary>examples/vite-nitro-plugin/README.md (1)</summary><blockquote>

`11-15`: **Add a language to the fenced block to satisfy MD040.**

Use a plain-text fence for the tree output.


<details>
<summary>πŸ”§ Suggested change</summary>

```diff
-```
+```text
 vite-nitro-plugin/
 β”œβ”€β”€ vite.config.mjs       # Vite config with Nitro plugin
 └── tsconfig.json
</details>

</blockquote></details>
<details>
<summary>examples/nano-jsx/README.md (1)</summary><blockquote>

`11-16`: **Add a language to the fenced block to satisfy MD040.**

Use a plain-text fence for the tree output.


<details>
<summary>πŸ”§ Suggested change</summary>

```diff
-```
+```text
 nano-jsx/
 β”œβ”€β”€ server.tsx            # JSX server entry
 β”œβ”€β”€ nitro.config.ts
 └── vite.config.ts
</details>

</blockquote></details>
<details>
<summary>examples/fastify/README.md (1)</summary><blockquote>

`11-16`: **Add a language to the fenced block to satisfy MD040.**

Use a plain-text fence for the tree output.


<details>
<summary>πŸ”§ Suggested change</summary>

```diff
-```
+```text
 fastify/
 β”œβ”€β”€ server.node.ts        # Fastify app (Node.js specific)
 β”œβ”€β”€ nitro.config.ts
 └── vite.config.ts
</details>

</blockquote></details>
<details>
<summary>examples/vite-trpc/README.md (1)</summary><blockquote>

`11-17`: **Add a language to the fenced block to satisfy MD040.**

Use a plain-text fence for the tree output.


<details>
<summary>πŸ”§ Suggested change</summary>

```diff
-```
+```text
 vite-trpc/
 β”œβ”€β”€ server/
 β”‚   └── trpc.ts          # tRPC router and procedures
 β”œβ”€β”€ index.html           # Frontend entry
 └── vite.config.ts
</details>

</blockquote></details>
<details>
<summary>examples/custom-error-handler/README.md (2)</summary><blockquote>

`11-17`: **Add language identifier to fenced code block.**

The project structure code block should specify a language for better rendering and accessibility.

<details>
<summary>πŸ“ Suggested fix</summary>

```diff
-```
+```text
 custom-error-handler/
 β”œβ”€β”€ error.ts              # Custom error handler
 β”œβ”€β”€ server.ts             # Main handler
 β”œβ”€β”€ nitro.config.ts
 └── vite.config.ts
</details>

As per coding guidelines: static analysis hint MD040 indicates fenced code blocks should have a language specified.

---

`34-36`: **Fix parameter name inconsistency.**

The code uses `_event` (line 26) but the documentation refers to it as `event`. Consider updating the documentation to match the code, or vice versa.


```diff
 The error handler receives:
 - `error` - The error that was thrown
-- `event` - The H3 event object
+- `_event` - The H3 event object (prefixed with underscore when unused)
examples/elysia/README.md (1)

11-16: Add language identifier to fenced code block.

The project structure code block should specify a language for better rendering and accessibility.

πŸ“ Suggested fix
-```
+```text
 elysia/
 β”œβ”€β”€ server.ts             # Elysia app
 β”œβ”€β”€ nitro.config.ts
 └── vite.config.ts
</details>

As per coding guidelines: static analysis hint MD040 indicates fenced code blocks should have a language specified.

</blockquote></details>
<details>
<summary>examples/runtime-config/README.md (1)</summary><blockquote>

`11-17`: **Add language identifier to fenced code block.**

The project structure code block should specify a language for better rendering and accessibility.

<details>
<summary>πŸ“ Suggested fix</summary>

```diff
-```
+```text
 runtime-config/
 β”œβ”€β”€ .env                  # Environment variables
 β”œβ”€β”€ server.ts             # Access runtime config
 β”œβ”€β”€ nitro.config.ts       # Define config schema
 └── vite.config.ts
</details>

As per coding guidelines: static analysis hint MD040 indicates fenced code blocks should have a language specified.

</blockquote></details>
<details>
<summary>examples/virtual-routes/README.md (1)</summary><blockquote>

`11-15`: **Add language identifier to fenced code block.**

The project structure code block should specify a language for better rendering and accessibility.

<details>
<summary>πŸ“ Suggested fix</summary>

```diff
-```
+```text
 virtual-routes/
 β”œβ”€β”€ nitro.config.ts       # Virtual route configuration
 └── vite.config.ts
</details>

As per coding guidelines: static analysis hint MD040 indicates fenced code blocks should have a language specified.

</blockquote></details>
<details>
<summary>examples/renderer/README.md (2)</summary><blockquote>

`11-18`: **Add language identifier to fenced code block.**

The project structure code block should specify a language for better rendering and accessibility.

<details>
<summary>πŸ“ Suggested fix</summary>

```diff
-```
+```text
 renderer/
 β”œβ”€β”€ api/
 β”‚   └── hello.ts          # API route
 β”œβ”€β”€ renderer.ts           # Custom renderer
 β”œβ”€β”€ nitro.config.ts
 └── vite.config.ts
</details>

As per coding guidelines: static analysis hint MD040 indicates fenced code blocks should have a language specified.

---

`27-27`: **Remove unused `req` parameter from type signature.**

The type signature includes `req: Request` but only `url` is destructured and used in the function.


```diff
-export default async function renderer({ url }: { req: Request; url: URL }) {
+export default async function renderer({ url }: { url: URL }) {
examples/api-routes/README.md (2)

11-22: Add language identifier to fenced code block.

The project structure code block should specify a language for better rendering and accessibility.

πŸ“ Suggested fix
-```
+```text
 api-routes/
 β”œβ”€β”€ api/
 β”‚   β”œβ”€β”€ hello.ts          # GET /api/hello
 β”‚   β”œβ”€β”€ hello/
 β”‚   β”‚   └── [name].ts     # GET /api/hello/:name (dynamic route)
 β”‚   β”œβ”€β”€ test.get.ts       # GET /api/test
 β”‚   └── test.post.ts      # POST /api/test
 β”œβ”€β”€ index.html
 β”œβ”€β”€ nitro.config.ts
 └── vite.config.ts
</details>

As per coding guidelines: static analysis hint MD040 indicates fenced code blocks should have a language specified.

---

`44-44`: **Avoid non-null assertion operator for safer error handling.**

Using the non-null assertion operator (`!`) on `event.context.params` could cause runtime errors if params are unexpectedly undefined.


<details>
<summary>✨ Suggested improvement</summary>

```diff
-  return `Hello ${event.context.params!.name}!`;
+  const name = event.context.params?.name ?? 'Guest';
+  return `Hello ${name}!`;

Or with explicit error handling:

  const name = event.context.params?.name;
  if (!name) {
    throw createError({ statusCode: 400, message: 'Name parameter required' });
  }
  return `Hello ${name}!`;
docs/package.json (1)

7-12: Confirm zod is runtime‑required; otherwise move to devDependencies.
If zod is only used for docs build/schema validation, it can live in devDependencies to minimize runtime surface.

πŸ’‘ Suggested fix
-  "dependencies": {
-    "zod": "^4.3.6"
-  },
   "devDependencies": {
     "shaders": "^2.2.43",
-    "undocs": "^0.4.15"
+    "undocs": "^0.4.15",
+    "zod": "^4.3.6"
   }
Based on learnings: Avoid introducing new dependencies unless strictly necessaryβ€”add them to `devDependencies` unless required in runtime logic.
docs/.docs/pages/examples/[...slug].vue (1)

35-38: Use appConfig for StackBlitz and Sandbox like GitHub links already do.

GitHub links on lines 93 and 123 already use appConfig.docs.github and appConfig.docs.branch. Apply the same pattern to the StackBlitz URL and Sandbox component for consistency across forks and non-main branches.

♻️ Suggested refactor
+const docsRepo = appConfig.docs.github || 'nitrojs/nitro'
+const docsBranch = appConfig.docs.branch || 'main'

 const stackblitzUrl = computed(() => {
-  return `https://stackblitz.com/fork/github/nitrojs/nitro/tree/main/examples/${exampleName.value}`
+  return `https://stackblitz.com/fork/github/${docsRepo}/tree/${docsBranch}/examples/${exampleName.value}`
 })
       <Sandbox
-        repo="nitrojs/nitro"
-        branch="main"
+        :repo="docsRepo"
+        :branch="docsBranch"
         :dir="`examples/${exampleName}`"
         file="vite.config.ts"
         class="!mb-6"
       />

Comment on lines 22 to 38
```ts [server.ts]
import { html } from "nitro/h3";
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)
<br />(<a href="?skipCache=true">skip cache</a>)
`;
},
{
shouldBypassCache: ({ req }) => req.url.includes("skipCache=true")
}
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ› οΈ Refactor suggestion | 🟠 Major

Use proper query parameter parsing instead of string matching.

The shouldBypassCache implementation uses req.url.includes("skipCache=true") which is fragile and not best practice. This string matching approach will incorrectly match URLs like ?foo=skipCache=true&bar=1 or fail to match ?skipCache=1.

Consider using proper query parameter parsing with h3's getQuery utility.

♻️ Proposed refactor using proper query parameter parsing
 ```ts [server.ts]
-import { html } from "nitro/h3";
+import { html, getQuery } from "nitro/h3";
 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)
       <br />(<a href="?skipCache=true">skip cache</a>)
     `;
   },
   {
-    shouldBypassCache: ({ req }) => req.url.includes("skipCache=true")
+    shouldBypassCache: (event) => {
+      const query = getQuery(event);
+      return query.skipCache === 'true';
+    }
   }
 );
</details>

As per coding guidelines: examples should reflect best practices.

<details>
<summary>πŸ€– Prompt for AI Agents</summary>

In @examples/cached-handler/README.md around lines 22 - 38, Replace the fragile
string-match in shouldBypassCache with proper query parsing: import getQuery
from "nitro/h3" and change the shouldBypassCache handler (currently using
req.url.includes(...)) to accept the event, call getQuery(event), and return a
boolean based on query.skipCache (e.g., query.skipCache === 'true' or
appropriate truthy check). Update any parameter name from { req } to event where
needed and remove the req.url.includes usage.


</details>

<!-- fingerprinting:phantom:triton:puma -->

<!-- This is an auto-generated comment by CodeRabbit -->

Comment on lines +26 to +31
export default defineErrorHandler((error, _event) => {
return new Response(`Custom Error Handler: ${error.message}`, {
status: 500,
headers: { "Content-Type": "text/plain" },
});
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid exposing raw error messages and consider error type handling.

Directly exposing error.message in the response may leak sensitive implementation details or stack traces. Additionally, returning a hardcoded 500 status for all errors doesn't account for client errors (4xx) or other error types.

Consider:

  • Sanitizing error messages for production environments
  • Using appropriate status codes based on error type
  • Logging full error details server-side while returning generic messages to clients
πŸ”’ Suggested improvements
 export default defineErrorHandler((error, _event) => {
+  // Determine appropriate status code
+  const statusCode = error.statusCode || error.status || 500;
+  
+  // Sanitize message for production
+  const message = process.env.NODE_ENV === 'production' 
+    ? 'An error occurred' 
+    : `Custom Error Handler: ${error.message}`;
+
-  return new Response(`Custom Error Handler: ${error.message}`, {
-    status: 500,
+  return new Response(message, {
+    status: statusCode,
     headers: { "Content-Type": "text/plain" },
   });
 });
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export default defineErrorHandler((error, _event) => {
return new Response(`Custom Error Handler: ${error.message}`, {
status: 500,
headers: { "Content-Type": "text/plain" },
});
});
export default defineErrorHandler((error, _event) => {
// Determine appropriate status code
const statusCode = error.statusCode || error.status || 500;
// Sanitize message for production
const message = process.env.NODE_ENV === 'production'
? 'An error occurred'
: `Custom Error Handler: ${error.message}`;
return new Response(message, {
status: statusCode,
headers: { "Content-Type": "text/plain" },
});
});
πŸ€– Prompt for AI Agents
In `@examples/custom-error-handler/README.md` around lines 26 - 31, The current
defineErrorHandler returns raw error.message and always a 500 Response; change
it to map known error types to appropriate status codes (e.g., validation/auth
errors β†’ 4xx, others β†’ 5xx), sanitize the message returned to clients (use a
generic message like "Internal Server Error" or a safe error.code) and log the
full error server-side (including stack) before creating the Response; update
the handler in defineErrorHandler and the Response creation to use the computed
status and sanitized client message while ensuring full error details are
written to your logger.

Comment on lines 27 to 35
```ts [server/middleware/auth.ts]
import { defineMiddleware } from "nitro/h3";

export default defineMiddleware((event) => {
event.context.auth = {
name: "User " + Math.round(Math.random() * 100)
};
});
```
Copy link

@coderabbitai coderabbitai bot Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | πŸ”΄ Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for defineMiddleware in the codebase
rg "defineMiddleware" -i --type ts --type js -C 3

Repository: nitrojs/nitro

Length of output: 457


🏁 Script executed:

#!/bin/bash
# Check middleware definitions and exports in the repository
rg "export.*middleware|defineEventHandler|eventHandler|defineMiddleware" --type ts --type js | head -30

Repository: nitrojs/nitro

Length of output: 3264


🏁 Script executed:

#!/bin/bash
# Look for examples of middleware usage in the examples directory
fd -e md -e ts -e js "middleware" examples/ 2>/dev/null | xargs ls -la 2>/dev/null

Repository: nitrojs/nitro

Length of output: 2626


🌐 Web query:

Nitro H3 middleware defineMiddleware API 2025

πŸ’‘ Result:

In Nitro (Nuxt/Nitro runtime) there isn’t a core defineMiddleware() API in h3/nitro. Middleware is typically defined as an event handler and auto-registered from the middleware directory:

  • Nitro v3 docs: put files in middleware/ and export a handler (example uses defineHandler from nitro/h3)β€”middleware should generally not return a value. (v3.nitro.build)
  • Nitro (stable docs): same idea under server/middleware/, typically using defineEventHandler. (nitro.build)
// server/middleware/auth.ts  (Nuxt/Nitro)
export default defineEventHandler((event) => {
  event.context.user = { id: "123" }
})

Within h3 itself, β€œmiddleware” is usually registered via app.use(...), and for lifecycle-style middleware you can use utilities like onRequest, onResponse, and onError. (h3.dev)

If what you saw was defineMiddleware({ onRequest, onBeforeResponse }), that API is from Vinxi, not Nitro/h3. (vinxi.vercel.app)

Citations:


Replace defineMiddleware with defineEventHandler in the middleware example.

defineMiddleware is not a valid export from "nitro/h3". The correct API for Nitro middleware is defineEventHandler. Update the import and function wrapper:

import { defineEventHandler } from "nitro/h3";

export default defineEventHandler((event) => {
  event.context.auth = {
    name: "User " + Math.round(Math.random() * 100)
  };
});
πŸ€– Prompt for AI Agents
In `@examples/middleware/README.md` around lines 27 - 35, The example uses an
invalid export defineMiddleware from "nitro/h3"; update the import and wrapper
to use defineEventHandler instead: replace the import of defineMiddleware with
defineEventHandler and change the default export wrapper from
defineMiddleware((event) => { ... }) to defineEventHandler((event) => { ... })
so event.context.auth is set using the supported Nitro API.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

defineMiddleware is a valid export

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for defineMiddleware export in the source code
echo "=== Searching for defineMiddleware export definition ==="
rg "export.*defineMiddleware" --type ts -C 2

echo -e "\n=== Searching for defineMiddleware function definition ==="
rg "function defineMiddleware|const defineMiddleware|export.*defineMiddleware" --type ts -C 3

echo -e "\n=== Check h3 module exports ==="
fd -e ts -e js "index" src/ | xargs rg "defineMiddleware" 2>/dev/null || echo "No matches in index files"

echo -e "\n=== Look for middleware utilities ==="
rg "defineMiddleware" src/ --type ts -A 5 -B 2

Length of output: 1016


🏁 Script executed:

#!/bin/bash
# Look for h3 re-exports in Nitro source
echo "=== Check nitro/h3 module exports ==="
fd "h3" src/ --type f -e ts | xargs cat 2>/dev/null | head -100

echo -e "\n=== Search for all define* exports from h3 ==="
rg "from ['\"]h3['\"]" src/ --type ts -C 2

echo -e "\n=== Check type definitions for defineMiddleware ==="
rg "defineMiddleware" --type-add 'dts:*.d.ts' --type dts -C 3

echo -e "\n=== Look at h3 package itself ==="
cat node_modules/h3/dist/index.d.ts 2>/dev/null | grep -i "middleware" | head -20 || echo "h3 types not found in node_modules"

echo -e "\n=== Check if defineMiddleware is in h3 package ==="
cat node_modules/h3/dist/index.d.ts 2>/dev/null | grep "defineMiddleware" || echo "defineMiddleware not found in h3 types"

Length of output: 11737


🏁 Script executed:

#!/bin/bash
# Check if defineMiddleware is defined anywhere in this PR's changes
echo "=== Check all files in examples/middleware ==="
fd . examples/middleware --type f -x cat {}

echo -e "\n=== Check if there's a local defineMiddleware implementation ==="
rg "defineMiddleware\s*=" examples/ --type ts --type js -C 5

echo -e "\n=== Check package versions being used ==="
cat package.json | grep -A 20 '"h3"' || echo "No h3 in root package.json"

echo -e "\n=== Check for any utils or helpers that define it ==="
fd -e ts -e js . examples/middleware | xargs cat

Length of output: 3319


🏁 Script executed:

#!/bin/bash
# Check what nitro/h3 actually exports
echo "=== Check lib/index exports (the public API) ==="
cat lib/index.d.mts 2>/dev/null | grep -i "middleware" -A 2 -B 2 || echo "lib/index.d.mts not found"

echo -e "\n=== Check for nitro/h3 re-exports ==="
fd -e ts -e mts "h3" lib/ src/runtime/ --exec cat {} | grep -E "export.*from.*h3|defineMiddleware" | head -30

echo -e "\n=== Look for the nitro/h3 module definition ==="
rg "nitro/h3" lib/ src/ --type-add 'mts:*.mts' --type mts --type ts -l

echo -e "\n=== Check what lib/h3 exports ==="
cat lib/h3.d.mts 2>/dev/null || echo "lib/h3.d.mts not found"

echo -e "\n=== Search for middleware-related exports in lib ==="
fd . lib/ --type f | xargs grep -l "middleware" 2>/dev/null

Length of output: 310


🌐 Web query:

h3 v2 defineMiddleware API 2025

πŸ’‘ Result:

In h3 v2 (2025 beta / 2.0.0+), defineMiddleware is essentially a type helper: it returns the input middleware unchanged at runtime. The implementation is literally function defineMiddleware(input) { return input; }. [1]

API / usage (h3 v2)

import { defineMiddleware } from "h3";

export const auth = defineMiddleware(async (event, next) => {
  // ...pre
  const res = await next();
  // ...post
  return res;
});

Middleware in v2 is registered via app.use(...) (optionally with match options like method, route patterns, etc.), and you can also use the built-in middleware factories onRequest, onResponse, and onError. [2]

Related v2 migration note (important for 2025+)

In v2, the older defineRequestMiddleware / defineResponseMiddleware utilities were removed; use v2 middleware / hooks instead. [3]

Timeline context: H3 v2 beta was announced June 10, 2025. [4]

Sources

[1] https://app.unpkg.com/h3%402.0.1-rc.5/files/dist/h3.mjs
[2] https://h3.dev/guide/basics/middleware
[3] https://h3.dev/migration
[4] https://www.h3.dev/blog/v2-beta


@RihanArfan, you're absolutely right, and I apologize for the incorrect comment!

I've confirmed that defineMiddleware is a valid export in h3 v2. Since this repository uses h3 ^2.0.1-rc.11 and nitro/h3 re-exports everything from h3 (export * from "h3"), the defineMiddleware import is correct.

In h3 v2, defineMiddleware is a type helper for defining middleware with proper typing. The example code is using the correct API.

Thank you for the correction!

πŸ°πŸ’­

  /)_/)
 (β€’α΄—β€’)  Oops! My mistake.
 />πŸ“š  Learning about h3 v2...

Comment on lines 27 to 44
export default async function renderer({ url }: { req: Request; url: URL }) {
const apiRes = await fetch("/api/hello").then((res) => res.text());

return new Response(
`<!DOCTYPE html>
<html>
<head>
<title>Custom Renderer</title>
</head>
<body>
<h1>Hello from custom renderer!</h1>
<p>Current path: ${url.pathname}</p>
<p>API says: ${apiRes}</p>
</body>
</html>`,
{ headers: { "content-type": "text/html; charset=utf-8" } }
);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | πŸ”΄ Critical

Escape user-controlled data to prevent XSS vulnerabilities.

The template directly interpolates url.pathname and apiRes without escaping, which could lead to XSS attacks if these values contain HTML or JavaScript.

πŸ”’ Suggested fix with HTML escaping
 import { fetch } from "nitro";

+function escapeHtml(text: string): string {
+  return text
+    .replace(/&/g, '&amp;')
+    .replace(/</g, '&lt;')
+    .replace(/>/g, '&gt;')
+    .replace(/"/g, '&quot;')
+    .replace(/'/g, '&#039;');
+}
+
 export default async function renderer({ url }: { req: Request; url: URL }) {
   const apiRes = await fetch("/api/hello").then((res) => res.text());

   return new Response(
     `<!DOCTYPE html>
     <html>
     <head>
       <title>Custom Renderer</title>
     </head>
     <body>
       <h1>Hello from custom renderer!</h1>
-      <p>Current path: ${url.pathname}</p>
-      <p>API says: ${apiRes}</p>
+      <p>Current path: ${escapeHtml(url.pathname)}</p>
+      <p>API says: ${escapeHtml(apiRes)}</p>
     </body>
     </html>`,
     { headers: { "content-type": "text/html; charset=utf-8" } }
   );
 }
πŸ€– Prompt for AI Agents
In `@examples/renderer/README.md` around lines 27 - 44, The renderer function
currently interpolates user-controlled values (url.pathname and apiRes) directly
into the HTML string, creating an XSS risk; fix it by introducing/using an
HTML-escape utility to sanitize url.pathname and apiRes before interpolation
(e.g., a helper that replaces &, <, >, ", ' and / with entities) and replace the
direct references in the template with the escaped values so the Response body
uses the sanitized outputs from renderer.

Comment on lines 26 to 39
```tsx [src/entry-server.tsx]
import { renderToReadableStream } from "preact-render-to-string/stream";
import { App } from "./app.jsx";

export default {
async fetch(request: Request) {
const url = new URL(request.url);
const htmlStream = renderToReadableStream(<Root url={url} />);
return new Response(htmlStream, {
headers: { "Content-Type": "text/html;charset=utf-8" },
});
},
};
```
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | πŸ”΄ Critical

Fix component name mismatch.

The code imports App from "./app.jsx" but uses <Root url={url} /> in the render call. This will cause a runtime error.

πŸ› Proposed fix
 import { renderToReadableStream } from "preact-render-to-string/stream";
 import { App } from "./app.jsx";

 export default {
   async fetch(request: Request) {
     const url = new URL(request.url);
-    const htmlStream = renderToReadableStream(<Root url={url} />);
+    const htmlStream = renderToReadableStream(<App url={url} />);
     return new Response(htmlStream, {
       headers: { "Content-Type": "text/html;charset=utf-8" },
     });
   },
 };
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```tsx [src/entry-server.tsx]
import { renderToReadableStream } from "preact-render-to-string/stream";
import { App } from "./app.jsx";
export default {
async fetch(request: Request) {
const url = new URL(request.url);
const htmlStream = renderToReadableStream(<Root url={url} />);
return new Response(htmlStream, {
headers: { "Content-Type": "text/html;charset=utf-8" },
});
},
};
```
πŸ€– Prompt for AI Agents
In `@examples/vite-ssr-preact/README.md` around lines 26 - 39, The server entry
imports App from "./app.jsx" but renders <Root url={url} />, causing a runtime
error; update the render call in export default.fetch to use the imported
component (App) β€” e.g., replace the <Root url={url} /> usage with <App url={url}
/> β€” or alternatively change the import to import Root from "./app.jsx" so the
rendered component and import name match (refer to the renderToReadableStream
call and the App/Root identifiers).

@pi0
Copy link
Member

pi0 commented Jan 28, 2026

undocs merged to main we can try with nightly.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

πŸ€– Fix all issues with AI agents
In `@docs/.docs/pages/examples/`[...slug].vue:
- Around line 12-53: Normalize route.path once and reuse that normalized value
in all content queries and derived computations to avoid double slashes: create
a single normalizedPath (derived from route.path with trailing slash removed)
and use normalizedPath.value wherever the code currently uses route.path
(including inside kebabCase for useAsyncData keys,
queryCollection('examples').path(...)/queryCollectionItemSurroundings calls,
exampleName computation, prerenderRoutes/joinURL, etc.), and update any string
concatenations to use `${normalizedPath.value}/readme` so no queries produce
`//readme`.

In `@docs/.docs/server/routes/raw/examples/`[...slug].md.get.ts:
- Around line 20-27: The code assumes page.body.value, page.title, and
page.description exist and mutates page.body.value directly; fix by creating a
local safe copy of the body before modification (e.g., clone page.body or
page.body.value into a new array), guard that page.body and page.body.value are
arrays (provide empty array fallback), and guard page.title/page.description
with sensible defaults before unshifting the H1/blockquote; then call setHeader
and pass the modified clone into stringify (preserving the { type: 'minimark' }
option) so the original cached page object is not mutated.

In `@examples/cached-handler/README.md`:
- Around line 13-20: Update the README's unlabeled fenced block that contains
the directory tree by adding a language tag (e.g., change the opening "```" to
"```text") so the tree block is recognized by markdownlint; locate the fenced
block showing the lines starting with "β”œβ”€β”€ nitro.config.ts" and replace its
opening fence accordingly.

In `@examples/database/README.md`:
- Around line 31-57: The handler in server.ts (export default defineHandler) is
performing schema changes (db.sql`DROP TABLE IF EXISTS users` and db.sql`CREATE
TABLE IF NOT EXISTS users`) on every request which is unsafe; remove those
DROP/CREATE calls from the handler and either (a) move the schema setup into a
startup/init script or migration task run once at application bootstrap, or (b)
update the example handler to assume the users table exists and only demonstrate
the runtime operations (using useDatabase(), db.sql`INSERT INTO users ...`,
db.sql`SELECT * FROM users WHERE id = ${userId}`), and add a note in README that
schema creation should be done via migrations or an initialization file rather
than inside defineHandler.
- Around line 65-91: The migration task in migrate.ts uses a destructive DROP
TABLE IF EXISTS which will wipe data; update the task to avoid destructive
operations by removing the DROP TABLE call in the defineTask exported default
(the async run function that uses useDatabase and db.sql) and instead only run
CREATE TABLE IF NOT EXISTS, or rename this file to reset.ts (or add a separate
tasks/db/reset.ts) to make clear it is a destructive reset; ensure the migration
example uses an additive pattern (CREATE TABLE IF NOT EXISTS and ALTER TABLE
statements or a versioned migration approach) within the run function rather
than dropping tables.

In `@examples/fastify/README.md`:
- Around line 11-18: The fenced directory tree in README.md is missing a
language tag causing MD040; update the opening fence for the tree (the triple
backticks that precede the directory list) to include a language like text
(e.g., change ``` to ```text) so the block is recognized by markdownlint.

In `@examples/import-alias/README.md`:
- Line 28: Fix the typo in the README heading by changing the heading text "##
Importingi Using Aliases" to "## Importing Using Aliases" (update the line that
contains the heading string "## Importingi Using Aliases").
- Line 48: Remove the stray closing automd comment <!-- /automd --> that has no
matching opening directive; locate the orphaned token in the README and delete
that comment so only valid automd directives remain.

In `@examples/mono-jsx/README.md`:
- Around line 27-30: The README example text mismatches: change the "mongo-jsx"
typo to "mono-jsx" so the exported JSX component and heading match the project
name β€” update the snippet returned by the default export (the JSX fragment
containing <html> and <h1>) to read "Nitro + mono-jsx works!" instead of "Nitro
+ mongo-jsx works!".

In `@examples/nano-jsx/README.md`:
- Around line 11-18: Update the generated fenced code block so it includes a
language identifier (e.g., change the opening "```" to "```text") in the README
generation step; modify the automd template/config that emits the directory tree
(the template producing the triple-backtick block containing "β”œβ”€β”€
nitro.config.ts ... └── vite.config.ts") to emit "```text" (or
"```plaintext"/"```tree") as the fence opening so the README.md output includes
the language identifier.

In `@examples/plugins/README.md`:
- Line 38: The content-type header value set via event.headers.set is using an
invalid MIME type ("html; charset=utf-8"); update the call to
event.headers.set("content-type", "text/html; charset=utf-8") so the correct
MIME type for HTML is used (locate the event.headers.set invocation in the
example and replace the value accordingly).

In `@examples/shiki/README.md`:
- Around line 13-23: The code fence containing the directory tree in README.md
is missing a language specifier (MD040); update the opening fence for the tree
block (the triple-backtick that precedes the directory listing) to include a
language token such as text (i.e., change ``` to ```text) so markdownlint stops
flagging it and the block remains rendered the same.

In `@examples/vite-ssr-preact/README.md`:
- Line 89: Update the import for the App component so its extension matches the
actual file name: replace the current import statement importing App from
"./app.jsx" with an import that points to "./app.tsx" (or remove the explicit
extension if your bundler resolves .tsx), ensuring the symbol App imported from
app.tsx is used consistently.
🧹 Nitpick comments (16)
examples/hono/README.md (1)

11-18: Add a language tag to the directory tree fence.

markdownlint flags the fence without a language. Use text (or plain) for the tree block.

βœ… Proposed fix
-```
+```text
 β”œβ”€β”€ nitro.config.ts
 β”œβ”€β”€ package.json
 β”œβ”€β”€ README.md
 β”œβ”€β”€ server.ts
 β”œβ”€β”€ tsconfig.json
 └── vite.config.ts

</details>

</blockquote></details>
<details>
<summary>examples/vite-ssr-preact/README.md (1)</summary><blockquote>

`13-23`: **Add language identifier to fenced code block.**

The fenced code block displaying the directory tree should specify a language identifier (e.g., `text` or leave it as plain markdown) to satisfy markdown linting rules.

<details>
<summary>πŸ“ Proposed fix</summary>

```diff
-```
+```text
 β”œβ”€β”€ src/
 β”‚   β”œβ”€β”€ app.tsx
 β”‚   β”œβ”€β”€ entry-client.tsx
examples/database/README.md (1)

46-46: Use a realistic email address in the example.

The empty string '' for the email field doesn't demonstrate realistic usage. Consider using 'john.doe@example.com' to show proper data insertion.

✨ Suggested change
-  await db.sql`INSERT INTO users VALUES (${userId}, 'John', 'Doe', '')`;
+  await db.sql`INSERT INTO users VALUES (${userId}, 'John', 'Doe', 'john.doe@example.com')`;
examples/server-fetch/README.md (1)

13-22: Add a language tag to the dir-tree fence.

Markdown lint flags the fenced block without a language; please add one (e.g., text).

πŸ› οΈ Suggested fix
-```
+```text
 β”œβ”€β”€ routes/
 β”‚   β”œβ”€β”€ hello.ts
 β”‚   └── index.ts
 β”œβ”€β”€ nitro.config.ts
 β”œβ”€β”€ package.json
 β”œβ”€β”€ README.md
 β”œβ”€β”€ tsconfig.json
 └── vite.config.ts
</details>

</blockquote></details>
<details>
<summary>examples/vite-ssr-react/README.md (1)</summary><blockquote>

`13-23`: **Add a language tag to the dir-tree fence.**

Markdown lint flags the fenced block without a language; please add one (e.g., `text`).  


<details>
<summary>πŸ› οΈ Suggested fix</summary>

```diff
-```
+```text
 β”œβ”€β”€ src/
 β”‚   β”œβ”€β”€ app.tsx
 β”‚   β”œβ”€β”€ entry-client.tsx
 β”‚   β”œβ”€β”€ entry-server.tsx
 β”‚   └── styles.css
 β”œβ”€β”€ package.json
 β”œβ”€β”€ README.md
 β”œβ”€β”€ tsconfig.json
 └── vite.config.mjs
</details>

</blockquote></details>
<details>
<summary>examples/vite-nitro-plugin/README.md (1)</summary><blockquote>

`13-18`: **Add a language tag to the dir-tree fence.**

Markdown lint flags the fenced block without a language; please add one (e.g., `text`).  


<details>
<summary>πŸ› οΈ Suggested fix</summary>

```diff
-```
+```text
 β”œβ”€β”€ package.json
 β”œβ”€β”€ README.md
 β”œβ”€β”€ tsconfig.json
 └── vite.config.mjs
</details>

</blockquote></details>
<details>
<summary>examples/vite-ssr-tss-react/README.md (1)</summary><blockquote>

`13-28`: **Add a language tag to the dir-tree fence.**

Markdown lint flags the fenced block without a language; please add one (e.g., `text`).  


<details>
<summary>πŸ› οΈ Suggested fix</summary>

```diff
-```
+```text
 β”œβ”€β”€ src/
 β”‚   β”œβ”€β”€ routes/
 β”‚   β”‚   β”œβ”€β”€ __root.tsx
 β”‚   β”‚   └── index.tsx
 β”‚   β”œβ”€β”€ styles/
 β”‚   β”‚   └── app.css
 β”‚   β”œβ”€β”€ router.tsx
 β”‚   └── routeTree.gen.ts
 β”œβ”€β”€ .gitignore
 β”œβ”€β”€ package.json
 β”œβ”€β”€ README.md
 β”œβ”€β”€ server.ts
 β”œβ”€β”€ tsconfig.json
 └── vite.config.mjs
</details>

</blockquote></details>
<details>
<summary>examples/auto-imports/README.md (1)</summary><blockquote>

`13-23`: **Add a language tag to the dir-tree fence.**

Markdown lint flags the fenced block without a language; please add one (e.g., `text`).  


<details>
<summary>πŸ› οΈ Suggested fix</summary>

```diff
-```
+```text
 β”œβ”€β”€ server/
 β”‚   └── utils/
 β”‚       └── hello.ts
 β”œβ”€β”€ nitro.config.ts
 β”œβ”€β”€ package.json
 β”œβ”€β”€ README.md
 β”œβ”€β”€ server.ts
 β”œβ”€β”€ tsconfig.json
 └── vite.config.ts
</details>

</blockquote></details>
<details>
<summary>examples/mono-jsx/README.md (1)</summary><blockquote>

`11-18`: **Add a language tag to the dir-tree fence.**

Markdown lint flags the fenced block without a language; please add one (e.g., `text`).  


<details>
<summary>πŸ› οΈ Suggested fix</summary>

```diff
-```
+```text
 β”œβ”€β”€ nitro.config.ts
 β”œβ”€β”€ package.json
 β”œβ”€β”€ README.md
 β”œβ”€β”€ server.tsx
 β”œβ”€β”€ tsconfig.json
 └── vite.config.ts
</details>

</blockquote></details>
<details>
<summary>examples/middleware/README.md (1)</summary><blockquote>

`13-23`: **Add a language to the dir-tree fenced block.**

Markdownlint flags this block because it lacks a language tag. Consider using `text` (or `bash`) for consistency.

<details>
<summary>Suggested fix</summary>

```diff
-```
+```text
 β”œβ”€β”€ server/
 β”‚   └── middleware/
 β”‚       └── auth.ts
 β”œβ”€β”€ nitro.config.ts
 β”œβ”€β”€ package.json
 β”œβ”€β”€ README.md
 β”œβ”€β”€ server.ts
 β”œβ”€β”€ tsconfig.json
 └── vite.config.ts
examples/renderer/README.md (1)

13-22: Add a language to the dir-tree fenced block.

Markdownlint flags this block because it lacks a language tag. Consider using text.

Suggested fix
-```
+```text
 β”œβ”€β”€ api/
 β”‚   └── hello.ts
 β”œβ”€β”€ nitro.config.ts
 β”œβ”€β”€ package.json
 β”œβ”€β”€ README.md
 β”œβ”€β”€ renderer.ts
 β”œβ”€β”€ tsconfig.json
 └── vite.config.ts
examples/vite-ssr-tsr-react/README.md (1)

13-27: Add a language to the dir-tree fenced block.

Markdownlint flags this block because it lacks a language tag. Consider using text.

Suggested fix
-```
+```text
 β”œβ”€β”€ src/
 β”‚   β”œβ”€β”€ assets/
 β”‚   β”‚   └── main.css
 β”‚   β”œβ”€β”€ routes/
 β”‚   β”‚   β”œβ”€β”€ __root.tsx
 β”‚   β”‚   └── index.tsx
 β”‚   β”œβ”€β”€ main.tsx
 β”‚   └── routeTree.gen.ts
 β”œβ”€β”€ index.html
 β”œβ”€β”€ package.json
 β”œβ”€β”€ README.md
 β”œβ”€β”€ tsconfig.json
 └── vite.config.mjs
examples/vite-trpc/README.md (1)

13-22: Add a language to the dir-tree fenced block.

Markdownlint flags this block because it lacks a language tag. Consider using text.

Suggested fix
-```
+```text
 β”œβ”€β”€ server/
 β”‚   └── trpc.ts
 β”œβ”€β”€ .gitignore
 β”œβ”€β”€ index.html
 β”œβ”€β”€ package.json
 β”œβ”€β”€ README.md
 β”œβ”€β”€ tsconfig.json
 └── vite.config.ts
examples/vite-rsc/README.md (2)

13-34: Add a language to the dir-tree fenced block.

Markdownlint flags this block because it lacks a language tag. Consider using text.

Suggested fix
-```
+```text
 β”œβ”€β”€ app/
 β”‚   β”œβ”€β”€ assets/
 β”‚   β”‚   β”œβ”€β”€ nitro.svg
 β”‚   β”‚   β”œβ”€β”€ react.svg
 β”‚   β”‚   └── vite.svg
 β”‚   β”œβ”€β”€ framework/
 β”‚   β”‚   β”œβ”€β”€ entry.browser.tsx
 β”‚   β”‚   β”œβ”€β”€ entry.rsc.tsx
 β”‚   β”‚   β”œβ”€β”€ entry.ssr.tsx
 β”‚   β”‚   β”œβ”€β”€ error-boundary.tsx
 β”‚   β”‚   └── request.tsx
 β”‚   β”œβ”€β”€ action.tsx
 β”‚   β”œβ”€β”€ client.tsx
 β”‚   β”œβ”€β”€ index.css
 β”‚   └── root.tsx
 β”œβ”€β”€ .gitignore
 β”œβ”€β”€ package.json
 β”œβ”€β”€ README.md
 β”œβ”€β”€ tsconfig.json
 └── vite.config.ts

170-179: Add rel="noopener noreferrer" to external links with target="_blank".

This keeps the example aligned with security best practices.

Suggested fix
-        <a href="https://vite.dev" target="_blank">
+        <a href="https://vite.dev" target="_blank" rel="noopener noreferrer">
           <img src={viteLogo} className="logo" alt="Vite logo" />
         </a>
-        <a href="https://react.dev/reference/rsc/server-components" target="_blank">
+        <a href="https://react.dev/reference/rsc/server-components" target="_blank" rel="noopener noreferrer">
           <img src={reactLogo} className="logo react" alt="React logo" />
         </a>
-        <a href="https://v3.nitro.build" target="_blank">
+        <a href="https://v3.nitro.build" target="_blank" rel="noopener noreferrer">
           <img src={nitroLogo} className="logo" alt="Nitro logo" />
         </a>

Based on learnings: Add examples in examples/ for new integrations and ensure they reflect best practices.

examples/vite-ssr-vue-router/README.md (1)

13-29: Add a language to the dir-tree fenced block.

Markdownlint flags this block because it lacks a language tag. Consider using text.

Suggested fix
-```
+```text
 β”œβ”€β”€ app/
 β”‚   β”œβ”€β”€ pages/
 β”‚   β”‚   β”œβ”€β”€ about.vue
 β”‚   β”‚   β”œβ”€β”€ index.vue
 β”‚   β”‚   └── not-found.vue
 β”‚   β”œβ”€β”€ app.vue
 β”‚   β”œβ”€β”€ entry-client.ts
 β”‚   β”œβ”€β”€ entry-server.ts
 β”‚   β”œβ”€β”€ routes.ts
 β”‚   β”œβ”€β”€ shims.d.ts
 β”‚   └── styles.css
 β”œβ”€β”€ package.json
 β”œβ”€β”€ README.md
 β”œβ”€β”€ tsconfig.json
 └── vite.config.mjs

Comment on lines +12 to +53
const { data: page } = await useAsyncData(kebabCase(route.path), () =>
queryCollection('examples').path(`${route.path}/readme`).first(),
)
if (!page.value) {
throw createError({
statusCode: 404,
statusMessage: 'Example not found',
message: `${route.path} does not exist`,
fatal: true,
})
}

const { data: surround } = await useAsyncData(`${kebabCase(route.path)}-surround`, () => {
return queryCollectionItemSurroundings('examples', `${route.path}/readme`, {
fields: ['description'],
})
})

// Extract example name from route (e.g., "/examples/vite-ssr-html" -> "vite-ssr-html")
const exampleName = computed(() => {
return route.path.replace(/^\/examples\//, '')
})

const breadcrumb = computed(() => [
{ label: 'Examples', icon: 'i-lucide-folder-code', to: '/examples' },
{ label: page.value?.title || exampleName.value },
])

usePageSEO({
title: `${page.value?.title} - ${appConfig.site.name}`,
ogTitle: page.value?.title,
description: page.value?.description,
})

const path = computed(() => route.path.replace(/\/$/, ''))
prerenderRoutes([joinURL('/raw', `${path.value}.md`)])
useHead({
link: [
{
rel: 'alternate',
href: joinURL(appConfig.site.url, 'raw', `${path.value}.md`),
type: 'text/markdown',
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor

🧩 Analysis chain

🏁 Script executed:

fd -t f "\[\.\.\.slug\]\.vue" docs/

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

find . -name "*slug*" -type f | head -20

Repository: nitrojs/nitro

Length of output: 206


🏁 Script executed:

cat -n ./docs/.docs/pages/examples/[...slug].vue

Repository: nitrojs/nitro

Length of output: 4173


🏁 Script executed:

find . -name "nuxt.config.*" -type f | head -5

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

fd -t f "nuxt" docs/ | head -20

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

fd -t f -e config "nuxt" . | head -20

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

ls -la docs/.docs/ | head -30

Repository: nitrojs/nitro

Length of output: 510


🏁 Script executed:

cat docs/.docs/content.config.ts

Repository: nitrojs/nitro

Length of output: 582


🏁 Script executed:

find . -maxdepth 2 -name "nuxt.config.*" -o -name ".nuxtrc*"

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

rg "route\.path" docs/ -A 2 -B 2

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

rg "trailing" docs/ -i

Repository: nitrojs/nitro

Length of output: 715


🏁 Script executed:

rg "useAsyncData" docs/.docs/pages/ -A 3 -B 1

Repository: nitrojs/nitro

Length of output: 1571


🏁 Script executed:

rg "queryCollection.*path.*route\.path" docs/

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

cat docs/.docs/content.config.ts

Repository: nitrojs/nitro

Length of output: 582


Consolidate trailing slash normalization to avoid double slashes in content queries.

Using route.path directly in content queries (lines 12-14, 24-28) can produce //readme if a trailing slash is present. Line 46 already normalizes for the raw markdown URLβ€”apply the same normalization consistently across all queries and path computations by normalizing once at the top.

Consolidate normalizations
+const normalizedPath = computed(() => route.path.replace(/\/$/, ''))
 const { data: page } = await useAsyncData(kebabCase(route.path), () =>
-  queryCollection('examples').path(`${route.path}/readme`).first(),
+  queryCollection('examples').path(`${normalizedPath.value}/readme`).first(),
 )
 if (!page.value) {
   throw createError({
     statusCode: 404,
     statusMessage: 'Example not found',
     message: `${route.path} does not exist`,
     fatal: true,
   })
 }
 
-const { data: surround } = await useAsyncData(`${kebabCase(route.path)}-surround`, () => {
-  return queryCollectionItemSurroundings('examples', `${route.path}/readme`, {
+const { data: surround } = await useAsyncData(`${kebabCase(normalizedPath.value)}-surround`, () => {
+  return queryCollectionItemSurroundings('examples', `${normalizedPath.value}/readme`, {
     fields: ['description'],
   })
 })
 
 // Extract example name from route (e.g., "/examples/vite-ssr-html" -> "vite-ssr-html")
 const exampleName = computed(() => {
-  return route.path.replace(/^\/examples\//, '')
+  return normalizedPath.value.replace(/^\/examples\//, '')
 })
 
 const breadcrumb = computed(() => [
   { label: 'Examples', icon: 'i-lucide-folder-code', to: '/examples' },
   { label: page.value?.title || exampleName.value },
 ])
 
 usePageSEO({
   title: `${page.value?.title} - ${appConfig.site.name}`,
   ogTitle: page.value?.title,
   description: page.value?.description,
 })
 
-const path = computed(() => route.path.replace(/\/$/, ''))
-prerenderRoutes([joinURL('/raw', `${path.value}.md`)])
+prerenderRoutes([joinURL('/raw', `${normalizedPath.value}.md`)])
 useHead({
   link: [
     {
       rel: 'alternate',
-      href: joinURL(appConfig.site.url, 'raw', `${path.value}.md`),
+      href: joinURL(appConfig.site.url, 'raw', `${normalizedPath.value}.md`),
       type: 'text/markdown',
     },
   ],
 })
πŸ€– Prompt for AI Agents
In `@docs/.docs/pages/examples/`[...slug].vue around lines 12 - 53, Normalize
route.path once and reuse that normalized value in all content queries and
derived computations to avoid double slashes: create a single normalizedPath
(derived from route.path with trailing slash removed) and use
normalizedPath.value wherever the code currently uses route.path (including
inside kebabCase for useAsyncData keys,
queryCollection('examples').path(...)/queryCollectionItemSurroundings calls,
exampleName computation, prerenderRoutes/joinURL, etc.), and update any string
concatenations to use `${normalizedPath.value}/readme` so no queries produce
`//readme`.

Comment on lines +20 to +27
// Add title and description to the top of the page if missing
if (page.body.value[0]?.[0] !== 'h1') {
page.body.value.unshift(['blockquote', {}, page.description])
page.body.value.unshift(['h1', {}, page.title])
}

setHeader(event, 'Content-Type', 'text/markdown; charset=utf-8')
return stringify({ ...page.body, type: 'minimark' }, { format: 'markdown/html' })
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor

🧩 Analysis chain

🏁 Script executed:

find . -name "[...slug].md.get.ts" -o -name "*.md.get.ts" | head -20

Repository: nitrojs/nitro

Length of output: 116


🏁 Script executed:

git ls-files | grep -E "(routes|examples)" | head -30

Repository: nitrojs/nitro

Length of output: 1149


🏁 Script executed:

cat -n "docs/.docs/server/routes/raw/examples/[...slug].md.get.ts"

Repository: nitrojs/nitro

Length of output: 1374


🏁 Script executed:

# Search for how queryCollection is used elsewhere
rg "queryCollection" --type ts --type js -A 3 -B 1 | head -60

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Check if there are other examples of handling page.body
rg "page\.body" --type ts --type js -B 2 -A 2

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Search for any Nuxt Content related documentation or usage
rg "page\.body|page\.title|page\.description" -A 2 -B 2

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Check for any TypeScript types or interfaces that define the page structure
rg "body\s*:\s*" --type ts | head -20

Repository: nitrojs/nitro

Length of output: 1457


🏁 Script executed:

# Look at the imports and see what queryCollection returns
rg "queryCollection" --type ts --type js -B 5 -A 10

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Check if there's any TypeScript definition or documentation about the page object
rg "@nuxt/content" --type ts --type json

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Look for any validation or checks on page properties in the codebase
rg "page\?" --type ts -B 2 -A 2

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Check if page.body.value is accessed elsewhere and if there are null checks
rg "\.body\.value" --type ts -B 1 -A 1

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Check if there are other similar route handlers in the docs
fd ".get.ts" docs/.docs/server/routes/ | head -20

Repository: nitrojs/nitro

Length of output: 114


🏁 Script executed:

# Look at the package.json to understand the `@nuxt/content` version being used
cat docs/package.json 2>/dev/null || cat package.json | grep -A 5 -B 5 "@nuxt/content"

Repository: nitrojs/nitro

Length of output: 292


🏁 Script executed:

# Check if there are defensive patterns used in other docs routes
fd ".get.ts" docs/ -x cat {} \; | head -100

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Check root package.json for dependencies
cat package.json | grep -A 100 "dependencies" | head -50

Repository: nitrojs/nitro

Length of output: 1599


🏁 Script executed:

# Look for any tests or examples related to this specific route
fd "slug" docs/ --type f

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Check if there's documentation about the page structure
find docs -name "*.md" -type f | xargs grep -l "body\|title\|description" 2>/dev/null | head -5

Repository: nitrojs/nitro

Length of output: 191


🏁 Script executed:

# Check if there are any TypeScript type definitions in the project
fd "*.d.ts" docs/ | head -10

Repository: nitrojs/nitro

Length of output: 342


🏁 Script executed:

# Look for any validation or type checking of the page object
rg "page\." docs/.docs/server/ -B 2 -A 2

Repository: nitrojs/nitro

Length of output: 1021


🏁 Script executed:

# Check the Nuxt Content documentation online or look for imports
rg "from.*content" docs/.docs/server/ -B 1 -A 1

Repository: nitrojs/nitro

Length of output: 274


🏁 Script executed:

# Search for any error handling or try-catch around queryCollection calls
rg "queryCollection|\.first\(\)" docs/.docs/server/ -B 5 -A 5

Repository: nitrojs/nitro

Length of output: 1723


🏁 Script executed:

# Look for any tests or example data to understand page structure
find docs -name "*.json" -o -name "*.yaml" | xargs grep -l "title\|description" 2>/dev/null | head -5

Repository: nitrojs/nitro

Length of output: 99


🏁 Script executed:

# Check if there's a nuxt-content configuration file
fd "content.config\|nuxt.config" docs/

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Check the docs directory structure to see what examples exist
ls -la docs/.docs/content/examples/ 2>/dev/null || ls -la examples/ | head -20

Repository: nitrojs/nitro

Length of output: 1183


🏁 Script executed:

# Look for any README files in examples that might show the expected structure
find examples -name "README.md" | head -5 | xargs cat | head -50

Repository: nitrojs/nitro

Length of output: 1123


🏁 Script executed:

# Check if there's any validation code or error handling in similar files
fd "\[.*\]\..*\.ts" docs/.docs/server/routes/ | xargs cat 2>/dev/null

Repository: nitrojs/nitro

Length of output: 1178


🏁 Script executed:

# Check if there are any tests for this route
fd "slug.*test\|test.*slug" docs/

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Look for any other examples of defensive body handling in the codebase
rg "body\.value" -B 3 -A 3 --type ts

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Check if Nuxt Content is listed as a dependency anywhere
rg "@nuxt/content" --type json

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Check if there's any documentation about the page structure from Nuxt Content
find docs -name "*.md" | xargs grep -l "queryCollection\|page\.body\|page\.title" 2>/dev/null | head -5

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Look for any examples or tests in the repository that show how page is used
rg "stringify.*page" -B 5 -A 2

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Search for how unshift is used in the codebase
rg "unshift" --type ts -B 2 -A 2

Repository: nitrojs/nitro

Length of output: 4504


🏁 Script executed:

# Check if there are integration tests or fixtures that might show what a page object looks like
find docs -name "*.json" -o -name "*.yaml" -o -name "*.yml" | xargs grep -l "body" 2>/dev/null | head -5

Repository: nitrojs/nitro

Length of output: 76


🏁 Script executed:

# Check the Undocs source to understand if it wraps Nuxt Content
rg "undocs" package.json docs/package.json

Repository: nitrojs/nitro

Length of output: 205


🏁 Script executed:

# Look for any README or documentation files in the docs directory
find docs -maxdepth 2 -name "README*" -o -name "*.md" | head -10 | xargs cat 2>/dev/null | head -100

Repository: nitrojs/nitro

Length of output: 4698


🏁 Script executed:

# Let me check if there are any actual README files that might be parsed to understand the expected structure
find examples -name "README.md" -exec head -15 {} \; | head -50

Repository: nitrojs/nitro

Length of output: 2223


🏁 Script executed:

# Check if there's any type definition or interface that defines the page structure from Nuxt Content
rg "interface.*page|type.*page" docs/.docs/ --type ts

Repository: nitrojs/nitro

Length of output: 105


🏁 Script executed:

cat -n docs/.docs/content.config.ts

Repository: nitrojs/nitro

Length of output: 715


🏁 Script executed:

# Let's also check if there's any actual data in the content directory that shows what a real page looks like
find docs -path "*content*" -name "*.md" -o -name "*.json" 2>/dev/null | head -10

Repository: nitrojs/nitro

Length of output: 74


🏁 Script executed:

# Check if there's a lockfile or version info for `@nuxt/content`
grep -r "@nuxt/content" docs/pnpm-lock.yaml 2>/dev/null | head -5

Repository: nitrojs/nitro

Length of output: 742


🏁 Script executed:

# Look for any other routes that use page.title or page.description
rg "page\.(title|description|body)" docs/.docs/server/ --type ts

Repository: nitrojs/nitro

Length of output: 532


🏁 Script executed:

# Check if there are any Vue components that might show how the page object is used
rg "page\." docs/.docs/pages/ --type vue --type ts | head -20

Repository: nitrojs/nitro

Length of output: 84


Add guards for optional page properties before injection.

The code assumes page.body.value, page.title, and page.description always exist, but they may be undefined depending on the README structure. This can cause crashes or render undefined values in the output. Additionally, mutating page.body.value directly modifies the cached object.

Safer implementation
  // Add title and description to the top of the page if missing
-  if (page.body.value[0]?.[0] !== 'h1') {
-    page.body.value.unshift(['blockquote', {}, page.description])
-    page.body.value.unshift(['h1', {}, page.title])
+  const body = Array.isArray(page.body?.value) ? [...page.body.value] : []
+  if (body[0]?.[0] !== 'h1') {
+    if (page.description) body.unshift(['blockquote', {}, page.description])
+    if (page.title) body.unshift(['h1', {}, page.title])
   }

   setHeader(event, 'Content-Type', 'text/markdown; charset=utf-8')
-  return stringify({ ...page.body, type: 'minimark' }, { format: 'markdown/html' })
+  return stringify({ ...page.body, value: body, type: 'minimark' }, { format: 'markdown/html' })
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Add title and description to the top of the page if missing
if (page.body.value[0]?.[0] !== 'h1') {
page.body.value.unshift(['blockquote', {}, page.description])
page.body.value.unshift(['h1', {}, page.title])
}
setHeader(event, 'Content-Type', 'text/markdown; charset=utf-8')
return stringify({ ...page.body, type: 'minimark' }, { format: 'markdown/html' })
// Add title and description to the top of the page if missing
const body = Array.isArray(page.body?.value) ? [...page.body.value] : []
if (body[0]?.[0] !== 'h1') {
if (page.description) body.unshift(['blockquote', {}, page.description])
if (page.title) body.unshift(['h1', {}, page.title])
}
setHeader(event, 'Content-Type', 'text/markdown; charset=utf-8')
return stringify({ ...page.body, value: body, type: 'minimark' }, { format: 'markdown/html' })
πŸ€– Prompt for AI Agents
In `@docs/.docs/server/routes/raw/examples/`[...slug].md.get.ts around lines 20 -
27, The code assumes page.body.value, page.title, and page.description exist and
mutates page.body.value directly; fix by creating a local safe copy of the body
before modification (e.g., clone page.body or page.body.value into a new array),
guard that page.body and page.body.value are arrays (provide empty array
fallback), and guard page.title/page.description with sensible defaults before
unshifting the H1/blockquote; then call setHeader and pass the modified clone
into stringify (preserving the { type: 'minimark' } option) so the original
cached page object is not mutated.

Comment on lines 31 to 57
<!-- automd:file src="server.ts" code -->

```ts [server.ts]
import { defineHandler } from "nitro/h3";
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,
};
});
```

<!-- /automd -->
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | πŸ”΄ Critical

Critical: Do not drop/recreate tables in request handlers.

The example drops and recreates the users table on every HTTP request. This is extremely dangerous and does not reflect best practices:

  • Data loss risk: All existing data is destroyed on each request
  • Race conditions: Concurrent requests will cause table-level conflicts
  • Performance: Schema operations are expensive and block the database

Schema setup should happen once during application initialization or via migrations, not in request handlers.

As per coding guidelines, examples in examples/ must reflect best practices.

πŸ”§ Proposed fix: Separate schema setup from request handling

Option 1: Move schema setup to a separate initialization file or startup hook, then have the handler only query/insert data.

Option 2: Update the example to assume the schema already exists and focus purely on demonstrating the query API:

 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', 'john.doe@example.com')`;
 
   // Query for users
   const { rows } = await db.sql`SELECT * FROM users WHERE id = ${userId}`;
 
   return {
     rows,
   };
 });

And add a note in the documentation that the schema should be created via the migration task shown later in the example.

πŸ€– Prompt for AI Agents
In `@examples/database/README.md` around lines 31 - 57, The handler in server.ts
(export default defineHandler) is performing schema changes (db.sql`DROP TABLE
IF EXISTS users` and db.sql`CREATE TABLE IF NOT EXISTS users`) on every request
which is unsafe; remove those DROP/CREATE calls from the handler and either (a)
move the schema setup into a startup/init script or migration task run once at
application bootstrap, or (b) update the example handler to assume the users
table exists and only demonstrate the runtime operations (using useDatabase(),
db.sql`INSERT INTO users ...`, db.sql`SELECT * FROM users WHERE id =
${userId}`), and add a note in README that schema creation should be done via
migrations or an initialization file rather than inside defineHandler.

Comment on lines 65 to 91
<!-- automd:file src="tasks/db/migrate.ts" code -->

```ts [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!",
};
},
});
```

<!-- /automd -->
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reconsider dropping tables in migrations.

The migration task uses DROP TABLE IF EXISTS, which destroys all existing data. While acceptable for development/demo resets, production migrations typically use CREATE TABLE IF NOT EXISTS without the drop, or implement proper versioned migrations with schema alterations.

Consider either:

  1. Renaming this to reset.ts or seed.ts to clarify it's a destructive reset operation
  2. Showing a proper additive migration pattern that doesn't destroy data

As per coding guidelines, examples should reflect best practices.

♻️ Suggested approach for non-destructive migrations
 export default defineTask({
   meta: {
-    description: "Run database migrations",
+    description: "Initialize database schema",
   },
   async run() {
     const db = useDatabase();
 
-    console.log("Running database migrations...");
+    console.log("Initializing database schema...");
 
-    // Create users table
-    await db.sql`DROP TABLE IF EXISTS users`;
+    // Create users table if it doesn't exist
     await db.sql`CREATE TABLE IF NOT EXISTS users ("id" TEXT PRIMARY KEY, "firstName" TEXT, "lastName" TEXT, "email" TEXT)`;
 
     return {
-      result: "Database migrations complete!",
+      result: "Database schema initialized!",
     };
   },
 });

Or create a separate tasks/db/reset.ts for the destructive DROP operation used during development.

πŸ€– Prompt for AI Agents
In `@examples/database/README.md` around lines 65 - 91, The migration task in
migrate.ts uses a destructive DROP TABLE IF EXISTS which will wipe data; update
the task to avoid destructive operations by removing the DROP TABLE call in the
defineTask exported default (the async run function that uses useDatabase and
db.sql) and instead only run CREATE TABLE IF NOT EXISTS, or rename this file to
reset.ts (or add a separate tasks/db/reset.ts) to make clear it is a destructive
reset; ensure the migration example uses an additive pattern (CREATE TABLE IF
NOT EXISTS and ALTER TABLE statements or a versioned migration approach) within
the run function rather than dropping tables.

Comment on lines +27 to +30
export default () => (
<html>
<h1>Nitro + mongo-jsx works!</h1>
</html>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor

Fix mono-jsx typo in example output.

The snippet says β€œmongo-jsx” but the example and heading are β€œmono-jsx”.

πŸ› οΈ Suggested fix
 export default () => (
   <html>
-    <h1>Nitro + mongo-jsx works!</h1>
+    <h1>Nitro + mono-jsx works!</h1>
   </html>
 );
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export default () => (
<html>
<h1>Nitro + mongo-jsx works!</h1>
</html>
export default () => (
<html>
<h1>Nitro + mono-jsx works!</h1>
</html>
);
πŸ€– Prompt for AI Agents
In `@examples/mono-jsx/README.md` around lines 27 - 30, The README example text
mismatches: change the "mongo-jsx" typo to "mono-jsx" so the exported JSX
component and heading match the project name β€” update the snippet returned by
the default export (the JSX fragment containing <html> and <h1>) to read "Nitro
+ mono-jsx works!" instead of "Nitro + mongo-jsx works!".

export default definePlugin((nitroApp) => {
const hooks = useNitroHooks();
hooks.hook("response", (event) => {
event.headers.set("content-type", "html; charset=utf-8");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor

Incorrect MIME type for HTML content.

The content-type value "html; charset=utf-8" is invalid. The correct MIME type for HTML is "text/html; charset=utf-8".

πŸ”§ Proposed fix
-    event.headers.set("content-type", "html; charset=utf-8");
+    event.headers.set("content-type", "text/html; charset=utf-8");
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
event.headers.set("content-type", "html; charset=utf-8");
event.headers.set("content-type", "text/html; charset=utf-8");
πŸ€– Prompt for AI Agents
In `@examples/plugins/README.md` at line 38, The content-type header value set via
event.headers.set is using an invalid MIME type ("html; charset=utf-8"); update
the call to event.headers.set("content-type", "text/html; charset=utf-8") so the
correct MIME type for HTML is used (locate the event.headers.set invocation in
the example and replace the value accordingly).

Comment on lines 13 to 23
```
β”œβ”€β”€ api/
β”‚ └── highlight.ts
β”œβ”€β”€ index.html
β”œβ”€β”€ nitro.config.ts
β”œβ”€β”€ package.json
β”œβ”€β”€ README.md
β”œβ”€β”€ styles.css
β”œβ”€β”€ tsconfig.json
└── vite.config.ts
```
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor

Add a language to the dir-tree fence (MD040).
The directory tree block lacks a language specifier; markdownlint will keep flagging this.

βœ… Suggested fix
-```
+```text
 β”œβ”€β”€ api/
 β”‚   └── highlight.ts
 β”œβ”€β”€ index.html
 β”œβ”€β”€ nitro.config.ts
 β”œβ”€β”€ package.json
 β”œβ”€β”€ README.md
 β”œβ”€β”€ styles.css
 β”œβ”€β”€ tsconfig.json
 └── vite.config.ts
-```
+```
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```
β”œβ”€β”€ api/
β”‚ └── highlight.ts
β”œβ”€β”€ index.html
β”œβ”€β”€ nitro.config.ts
β”œβ”€β”€ package.json
β”œβ”€β”€ README.md
β”œβ”€β”€ styles.css
β”œβ”€β”€ tsconfig.json
└── vite.config.ts
```
🧰 Tools
πŸͺ› markdownlint-cli2 (0.20.0)

13-13: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

πŸ€– Prompt for AI Agents
In `@examples/shiki/README.md` around lines 13 - 23, The code fence containing the
directory tree in README.md is missing a language specifier (MD040); update the
opening fence for the tree block (the triple-backtick that precedes the
directory listing) to include a language token such as text (i.e., change ``` to
```text) so markdownlint stops flagging it and the block remains rendered the
same.

```tsx [entry-server.tsx]
import "./styles.css";
import { renderToReadableStream } from "preact-render-to-string/stream";
import { App } from "./app.jsx";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor

Fix import file extension to match actual file.

The import uses .jsx extension, but the directory tree (line 15) shows the file is app.tsx. This mismatch could cause module resolution issues.

πŸ”§ Proposed fix
-import { App } from "./app.jsx";
+import { App } from "./app.tsx";
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { App } from "./app.jsx";
import { App } from "./app.tsx";
πŸ€– Prompt for AI Agents
In `@examples/vite-ssr-preact/README.md` at line 89, Update the import for the App
component so its extension matches the actual file name: replace the current
import statement importing App from "./app.jsx" with an import that points to
"./app.tsx" (or remove the explicit extension if your bundler resolves .tsx),
ensuring the symbol App imported from app.tsx is used consistently.

@vercel
Copy link

vercel bot commented Jan 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
nitro.build Ready Ready Preview, Comment Jan 31, 2026 6:25pm

Request Review

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

πŸ€– Fix all issues with AI agents
In `@docs/package.json`:
- Around line 7-10: The package.json currently lists automd and zod under
"dependencies" but those are transitively provided by undocs (automd) and
`@nuxt/content` (zod) and are only used at build-time (see content.config.ts
referencing z from `@nuxt/content`); either remove "automd" and "zod" from
dependencies entirely, or move them to "devDependencies" with a brief comment in
package.json explaining why they’re pinned separately (e.g., version override or
build-time tooling), and ensure any justification references the packages by
name (automd, zod) and the supplying packages (undocs, `@nuxt/content`) so future
maintainers understand the decision.
🧹 Nitpick comments (1)
docs/package.json (1)

13-13: Using @latest with nightly builds may impact reproducibility, but is likely intentional for development.

The undocs-nightly@latest dependency will resolve to different versions across installs since nightly builds are designed to track the latest development version. While this reduces reproducibility, it appears intentional for the docs build to always use the latest undocs features during development. Consider adding a comment to the devDependencies clarifying whether this is temporary for testing or permanent, and document the decision in migration notes if this represents a shift in the docs tooling approach.

Comment on lines +7 to +10
"dependencies": {
"automd": "^0.4.3",
"zod": "^4.3.6"
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if automd or zod are used in server routes or runtime code

# Search for automd imports in docs directory
echo "=== Searching for automd usage ==="
rg -n "from ['\"]automd['\"]|require\(['\"]automd['\"]" docs/ 2>/dev/null || echo "No automd imports found"

# Search for zod imports in docs directory  
echo "=== Searching for zod usage ==="
rg -n "from ['\"]zod['\"]|require\(['\"]zod['\"]" docs/ 2>/dev/null || echo "No zod imports found"

# Check if there are server routes that might need runtime deps
echo "=== Checking for server routes ==="
find docs -type f \( -name "*.ts" -o -name "*.js" -o -name "*.tsx" -o -name "*.jsx" \) 2>/dev/null | head -20

# Check the docs package.json for scripts to understand build process
echo "=== Package.json scripts ==="
jq '.scripts' docs/package.json 2>/dev/null || cat docs/package.json | grep -A 10 '"scripts"'

Repository: nitrojs/nitro

Length of output: 431


🏁 Script executed:

#!/bin/bash
# Check automd config and usage context
echo "=== automd.config.ts content ==="
cat docs/.config/automd.config.ts 2>/dev/null || echo "File not found"

echo -e "\n=== examples server route ==="
cat docs/.docs/server/routes/raw/examples/\[...\].md.get.ts 2>/dev/null || find docs -name "*examples*.get.ts" -exec cat {} \;

echo -e "\n=== Check if zod/automd are in undocs dependencies ==="
npm ls undocs 2>/dev/null | head -5 || echo "Checking undocs package..."

# Also check if these are listed in any imports or require statements indirectly
echo -e "\n=== Check content.config.ts for schema/validation ==="
cat docs/.docs/content.config.ts 2>/dev/null || echo "File not found"

# Check the actual root package.json to see if undocs lists these as dependencies
echo -e "\n=== Root package.json undocs entry ==="
jq '.dependencies.undocs // .devDependencies.undocs' package.json 2>/dev/null || grep -A 2 "undocs" package.json | head -5

Repository: nitrojs/nitro

Length of output: 982


🏁 Script executed:

#!/bin/bash
# Check full docs/package.json to understand all dependencies
echo "=== Full docs/package.json ==="
cat docs/package.json

# Check if `@nuxt/content` is listed and includes zod
echo -e "\n=== Search for `@nuxt/content` in root package.json ==="
grep -i "nuxt/content" package.json

# Verify if standalone zod is imported anywhere in docs (including indirect)
echo -e "\n=== Check if zod is used directly (not via `@nuxt/content`) ==="
rg "import.*\{.*z.*\}" docs/ --type ts --type tsx --type js -A 2 | head -30

# Check if undocs is the main driver of automd and zod
echo -e "\n=== Check what undocs package includes ==="
npm info undocs 2>/dev/null | grep -A 5 -B 5 -i "automd\|zod" || echo "Could not fetch undocs info"

Repository: nitrojs/nitro

Length of output: 755


🏁 Script executed:

#!/bin/bash
# Final verification: Check if there's any rationale in git history or comments for these explicit deps
echo "=== Check git log for context on automd/zod addition ==="
git log --oneline -20 docs/package.json 2>/dev/null | head -10

# Check if automd is actually needed as explicit dep (maybe for CLI usage?)
echo -e "\n=== Check if automd CLI is used directly ==="
grep -r "automd" docs/ --include="*.sh" --include="*.json" 2>/dev/null | grep -v node_modules

# Verify undocs doesn't re-export automd/zod to dependent projects
echo -e "\n=== Check if docs is a package meant to be used as a dependency ==="
grep -E '"main"|"exports"|"types"' docs/package.json

Repository: nitrojs/nitro

Length of output: 342


Remove redundant dependencies or move to devDependencies with justification.

automd and zod appear to be already managed by undocs:

  • undocs includes automd@^0.4.2 (docs lists ^0.4.3)
  • @nuxt/content (included in undocs) provides zod; content.config.ts uses z from @nuxt/content, not standalone zod

These are build-time tools and have no direct usage in the docs source. Either remove them entirely or move to devDependencies with documentation explaining why they're needed separately from undocs. If kept due to version pinning or explicit requirements, add a comment clarifying the rationale.

πŸ€– Prompt for AI Agents
In `@docs/package.json` around lines 7 - 10, The package.json currently lists
automd and zod under "dependencies" but those are transitively provided by
undocs (automd) and `@nuxt/content` (zod) and are only used at build-time (see
content.config.ts referencing z from `@nuxt/content`); either remove "automd" and
"zod" from dependencies entirely, or move them to "devDependencies" with a brief
comment in package.json explaining why they’re pinned separately (e.g., version
override or build-time tooling), and ensure any justification references the
packages by name (automd, zod) and the supplying packages (undocs,
`@nuxt/content`) so future maintainers understand the decision.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

πŸ€– Fix all issues with AI agents
In `@examples/api-routes/README.md`:
- Around line 79-89: Update the import source for defineHandler in the shown
POST handler so it matches the recommended pattern used elsewhere: change the
import from "h3" to "nitro/h3" for the defineHandler import and exported handler
(the async default export using defineHandler). Also find the other repeated
code block later in the document that uses defineHandler and apply the same
import change so all examples consistently import defineHandler from "nitro/h3".

In `@examples/hello-world/README.md`:
- Line 79: Remove the orphaned closing automd HTML comment by deleting the
standalone "<!-- /automd -->" token found in the README.md (no matching "<!--
automd:... -->" opener exists); simply remove that closing comment so the file
no longer contains the stray "<!-- /automd -->" marker.

In `@examples/shiki/README.md`:
- Around line 123-142: The handler exported as export default async ({ req }: {
req: Request }) => { ... } currently accepts any method and unbounded body;
change it to accept only POST by checking req.method and returning a 405
response for other methods, and enforce a basic request-body size limit (e.g.,
read up to a configured max bytes and return 413 Payload Too Large if exceeded)
before calling req.text(); keep using highlighter.codeToHtml for rendering and
return the same HTML response on success. Ensure responses include appropriate
Content-Type and a short message for 405/413 cases.
- Around line 197-205: The template is interpolating the result of hl which
returns a Promise from serverFetch, causing `[object Promise]` if not awaited;
change the server script so hl awaits serverFetch (or make hl async and then
call `const highlighted = await hl(code)`), store the resolved HTML string
(e.g., highlighted) in a variable in the server script, and interpolate that
variable with {{{ highlighted }}} so the template receives a string rather than
a Promise.

In `@examples/vite-rsc/README.md`:
- Around line 282-287: The README references incorrect paths β€” update the two
list items that say "Edit src/client.tsx" and "Edit src/root.tsx" to match the
example layout by changing them to "Edit app/client.tsx" and "Edit app/root.tsx"
respectively so the path references align with the project structure used
elsewhere in the example.
- Around line 276-277: The JSX calls getServerCounter() synchronously which
returns a Promise; make the Root component async, await getServerCounter()
during render (e.g., const serverCount = await getServerCounter()), and replace
{getServerCounter()} with the awaited value (e.g., {serverCount}); keep the
existing updateServerCounter.bind(null, 1) form action unchanged and ensure the
component signature is updated to async so the awaited value is rendered instead
of a Promise.

In `@examples/vite-ssr-solid/README.md`:
- Around line 267-273: The import in entry-client.tsx references the wrong
extension ("./app.jsx") causing module resolution failures; update the import to
match the actual module filename by changing the import source to "./app.tsx" in
the entry-client.tsx file (the hydrate call and document selector remain
unchanged) so the module loader imports the correct App component.
- Around line 74-80: The import in entry-client.tsx references the wrong
extension ("./app.jsx") causing module resolution errors; update the import in
the hydrate call to point to the actual file ("./app.tsx") so hydrate(() => <App
/>, document.querySelector("#app")!) imports the App component from the correct
module.
- Around line 82-120: The import in entry-server.tsx uses the wrong file
extension ("./app.jsx") which does not match the actual module (app.tsx); update
the import statement in the module that defines fetch and calls
renderToStringAsync(() => <App />) to import "./app.tsx" so the App symbol
resolves correctly at runtime and module resolution errors are avoided.
- Around line 217-256: The import path for the App component is using the wrong
file extension; update the import statement in entry-server.tsx from "import {
App } from \"./app.jsx\";" to reference the actual file "app.tsx" so module
resolution succeeds; verify the App symbol and subsequent renderToStringAsync(()
=> <App />) usage still resolve and adjust any other import extensions (e.g.,
entry-client/entry-server asset queries) if they mismatch actual filenames.

In `@examples/vite-ssr-vue-router/README.md`:
- Around line 22-31: The devDependencies entry uses an unstable Vite version
("vite": "beta"); update the package devDependencies by replacing the "vite":
"beta" entry with the stable release (e.g., "vite": "7.3.0" or a caret-pinned
"^7.3.0") so the README example reflects best practices and stable behavior;
look for the "devDependencies" block and the "vite": "beta" key in the shown
diff to make the change.
🧹 Nitpick comments (3)
examples/vite-trpc/README.md (1)

153-165: Clarify the in-memory counter is demo-only.

The global counter implies stateful storage; in production this will reset on reloads and can behave unpredictably in serverless or multi-instance deployments. Consider adding a short note that this is a demo and suggest durable storage for real apps.

✏️ Suggested doc note
 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.
+
+> Note: The in-memory counter is for demonstration only. For production use, persist state in a database or durable store.
Based on learnings: "Applies to examples/**/* : Add examples in `examples/` for new integrations and ensure they reflect best practices".

Also applies to: 258-258

examples/vite-ssr-solid/README.md (1)

14-28: Consider using stable dependency versions.

Using "vite": "beta" and "nitro": "latest" may not reflect best practices for examples. Beta versions can have instability, and latest can introduce unexpected breaking changes. Consider pinning to specific stable versions to ensure the example remains reproducible and reliable.

As per coding guidelines, examples should reflect best practices.

examples/vite-rsc/README.md (1)

260-268: Add rel="noreferrer" for external target="_blank" links.

This avoids reverse‑tabnabbing and is a best‑practice for examples.
As per coding guidelines: β€œAdd examples in examples/ for new integrations and ensure they reflect best practices”.

Suggested update
-        <a href="https://vite.dev" target="_blank">
+        <a href="https://vite.dev" target="_blank" rel="noreferrer">
...
-        <a href="https://react.dev/reference/rsc/server-components" target="_blank">
+        <a href="https://react.dev/reference/rsc/server-components" target="_blank" rel="noreferrer">
...
-        <a href="https://v3.nitro.build" target="_blank">
+        <a href="https://v3.nitro.build" target="_blank" rel="noreferrer">

Comment on lines +79 to +89
```ts [api/test.post.ts]
import { defineHandler } from "h3";

export default defineHandler(async (event) => {
const body = await event.req.json();
return {
message: "Test post handler",
body,
};
});
```
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor

Inconsistent import source for defineHandler.

The POST handler imports from "h3" while all other handlers import from "nitro/h3". For consistency and to demonstrate the recommended pattern, consider using "nitro/h3" throughout the documentation.

✏️ Suggested fix
-import { defineHandler } from "h3";
+import { defineHandler } from "nitro/h3";

Apply the same fix to the repeated code block at lines 155-165.

πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```ts [api/test.post.ts]
import { defineHandler } from "h3";
export default defineHandler(async (event) => {
const body = await event.req.json();
return {
message: "Test post handler",
body,
};
});
```
πŸ€– Prompt for AI Agents
In `@examples/api-routes/README.md` around lines 79 - 89, Update the import source
for defineHandler in the shown POST handler so it matches the recommended
pattern used elsewhere: change the import from "h3" to "nitro/h3" for the
defineHandler import and exported handler (the async default export using
defineHandler). Also find the other repeated code block later in the document
that uses defineHandler and apply the same import change so all examples
consistently import defineHandler from "nitro/h3".

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.


<!-- /automd -->
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor

Orphaned closing automd comment.

Line 79 has a closing <!-- /automd --> comment without a corresponding opening <!-- automd:... --> directive. This appears to be a leftover that should be removed.

✏️ Suggested fix
-<!-- /automd -->
-
 Add the Nitro plugin to Vite and it handles the rest: dev server, hot reloading, and production builds.
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<!-- /automd -->
Add the Nitro plugin to Vite and it handles the rest: dev server, hot reloading, and production builds.
πŸ€– Prompt for AI Agents
In `@examples/hello-world/README.md` at line 79, Remove the orphaned closing
automd HTML comment by deleting the standalone "<!-- /automd -->" token found in
the README.md (no matching "<!-- automd:... -->" opener exists); simply remove
that closing comment so the file no longer contains the stray "<!-- /automd -->"
marker.

Comment on lines +123 to +142
```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" },
});
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor

Add a method guard and basic input limits.

The example accepts any method and unbounded body. For best-practice examples, it’s worth showing a POST-only handler and rejecting oversized payloads. As per coding guidelines, examples should reflect best practices.

βœ… Suggested adjustment
 export default async ({ req }: { req: Request }) => {
+  if (req.method !== "POST") {
+    return new Response("Method Not Allowed", { status: 405 });
+  }
   const code = await req.text();
+  if (code.length > 100_000) {
+    return new Response("Payload too large", { status: 413 });
+  }
   const html = await highlighter.codeToHtml(code, {
     lang: "ts",
     theme: "vitesse-dark",
   });
As per coding guidelines, add examples in `examples/` for new integrations and ensure they reflect best practices.
πŸ€– Prompt for AI Agents
In `@examples/shiki/README.md` around lines 123 - 142, The handler exported as
export default async ({ req }: { req: Request }) => { ... } currently accepts
any method and unbounded body; change it to accept only POST by checking
req.method and returning a 405 response for other methods, and enforce a basic
request-body size limit (e.g., read up to a configured max bytes and return 413
Payload Too Large if exceeded) before calling req.text(); keep using
highlighter.codeToHtml for rendering and return the same HTML response on
success. Ensure responses include appropriate Content-Type and a short message
for 405/413 cases.

Comment on lines +197 to +205
<script server>
const hl = (code) =>
serverFetch("/api/highlight", {
method: "POST",
body: code,
});
</script>
<pre><code>{{{ hl(`console.log("πŸ’š Simple is beautiful!");`) }}}</code></pre>
</div>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Ensure server-rendered output isn’t a Promise.

serverFetch returns a Promise; if Nitro server templates don’t auto-await, this will render [object Promise] instead of highlighted HTML. Consider awaiting before interpolation.

βœ… Possible fix
-      <script server>
-        const hl = (code) =>
-          serverFetch("/api/highlight", {
-            method: "POST",
-            body: code,
-          });
-      </script>
-      <pre><code>{{{ hl(`console.log("πŸ’š Simple is beautiful!");`) }}}</code></pre>
+      <script server>
+        const hl = async (code) =>
+          await serverFetch("/api/highlight", {
+            method: "POST",
+            body: code,
+          });
+      </script>
+      <pre><code>{{{ await hl(`console.log("πŸ’š Simple is beautiful!");`) }}}</code></pre>
Does Nitro server-side template interpolation ({{{ }}}) auto-await Promises returned from server scripts? Are `await` expressions allowed inside `{{{ }}}`?
πŸ€– Prompt for AI Agents
In `@examples/shiki/README.md` around lines 197 - 205, The template is
interpolating the result of hl which returns a Promise from serverFetch, causing
`[object Promise]` if not awaited; change the server script so hl awaits
serverFetch (or make hl async and then call `const highlighted = await
hl(code)`), store the resolved HTML string (e.g., highlighted) in a variable in
the server script, and interpolate that variable with {{{ highlighted }}} so the
template receives a string rather than a Promise.

Comment on lines +276 to +277
<form action={updateServerCounter.bind(null, 1)}>
<button>Server Counter: {getServerCounter()}</button>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ› οΈ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, locate the vite-rsc example and explore its structure
find . -type f -path "*/vite-rsc/*" -name "*.ts*" -o -path "*/vite-rsc/*" -name "*.jsx" -o -path "*/vite-rsc/*" -name "*.tsx" | head -20

Repository: nitrojs/nitro

Length of output: 441


🏁 Script executed:

# Also search for getServerCounter definition
rg "getServerCounter" --type ts --type tsx --type js --type jsx -A 5

Repository: nitrojs/nitro

Length of output: 84


🌐 Web query:

React Server Components RSC pattern for calling async server functions in component render

πŸ’‘ Result:

Key rule

  • You can await async work during render only in a Server Component. (e.g., fetch, DB/ORM, filesystem). In frameworks like Next.js App Router, make the Server Component async and await inside it. [2]
  • You cannot call a β€œServer Function / Server Action” during a Client Component’s initial render. Server Functions ("use server") are meant to be invoked via an action (form submit) or user interaction (e.g., button click), i.e., after render. [1]

Pattern A (recommended): Server Component does the async work during render

// app/page.tsx (Server Component)
export default async function Page() {
  const posts = await getPostsFromDbOrApi(); // db query or fetch
  return <PostList posts={posts} />;
}

This is the standard RSC data-fetching model. [2]


Pattern B: Server Function (β€œuse server”) is passed to a Client Component and called on interaction

// app/actions.ts
export async function createPost() {
  'use server'
  await db.posts.create({ /* ... */ })
}

// app/page.tsx (Server Component)
import { createPost } from './actions'
import CreateButton from './CreateButton' // Client Component

export default function Page() {
  return <CreateButton onCreate={createPost} />
}

// app/CreateButton.tsx
'use client'
export default function CreateButton({ onCreate }) {
  return <button onClick={() => onCreate()}>Create</button>
}

React/your framework turns the "use server" function into a server-executed callable reference for the Client Component. [1]


What not to do

  • Don’t make Client Components async (they can’t be async functions). [3]
  • Don’t call Server Functions/Actions β€œinline” during initial render; trigger them from an action/interaction instead. [1]

If you need client-rendered data, but still want Suspense-style rendering

In Next.js, a common pattern is: Server Component creates the promise (optionally cached) β†’ Client Component consumes it via use() (often via context). Next.js documents combining React.cache + context to share a fetched promise across Server/Client boundaries. [4]

Sources: React Server Functions docs [1], Next.js fetching data in Server Components [2], Next.js β€œno async client component” error guidance [3], Next.js notes on sharing data with React.cache + context [4].


🏁 Script executed:

# Search for getServerCounter definition without file type issues
rg "getServerCounter" -A 5 --max-count 20

Repository: nitrojs/nitro

Length of output: 3172


🏁 Script executed:

# Read the vite-rsc README to see the actual code context
head -300 ./examples/vite-rsc/README.md | tail -50

Repository: nitrojs/nitro

Length of output: 1553


🏁 Script executed:

# Also check if there's source code files
rg "function getServerCounter|const getServerCounter" -A 5

Repository: nitrojs/nitro

Length of output: 739


Make the Root component async and await the server function call.

getServerCounter() is an async function but is being called synchronously in JSX. This renders a Promise instead of the actual value. Per RSC best practices and the coding guidelines requiring examples to reflect best practices, make the component async and await the call during render:

Suggested fix
-export function Root(props: { url: URL }) {
+export async function Root(props: { url: URL }) {
+  const serverCount = await getServerCounter();
   return (
     <div id="root">
       {/* ... */}
       <div className="card">
         <form action={updateServerCounter.bind(null, 1)}>
-          <button>Server Counter: {getServerCounter()}</button>
+          <button>Server Counter: {serverCount}</button>
         </form>
       </div>
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<form action={updateServerCounter.bind(null, 1)}>
<button>Server Counter: {getServerCounter()}</button>
export async function Root(props: { url: URL }) {
const serverCount = await getServerCounter();
return (
<div id="root">
{/* ... */}
<div className="card">
<form action={updateServerCounter.bind(null, 1)}>
<button>Server Counter: {serverCount}</button>
</form>
</div>
πŸ€– Prompt for AI Agents
In `@examples/vite-rsc/README.md` around lines 276 - 277, The JSX calls
getServerCounter() synchronously which returns a Promise; make the Root
component async, await getServerCounter() during render (e.g., const serverCount
= await getServerCounter()), and replace {getServerCounter()} with the awaited
value (e.g., {serverCount}); keep the existing updateServerCounter.bind(null, 1)
form action unchanged and ensure the component signature is updated to async so
the awaited value is rendered instead of a Promise.

Comment on lines +74 to +80
```tsx [src/entry-client.tsx]
import { hydrate } from "solid-js/web";
import "./styles.css";
import { App } from "./app.jsx";

hydrate(() => <App />, document.querySelector("#app")!);
```
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | πŸ”΄ Critical

Fix file extension inconsistency in import statement.

Line 77 imports from "./app.jsx" but the actual file shown in the code tree is app.tsx. This mismatch will cause module resolution errors at runtime.

πŸ› Proposed fix
 import { hydrate } from "solid-js/web";
 import "./styles.css";
-import { App } from "./app.jsx";
+import { App } from "./app.tsx";

 hydrate(() => <App />, document.querySelector("#app")!);
πŸ€– Prompt for AI Agents
In `@examples/vite-ssr-solid/README.md` around lines 74 - 80, The import in
entry-client.tsx references the wrong extension ("./app.jsx") causing module
resolution errors; update the import in the hydrate call to point to the actual
file ("./app.tsx") so hydrate(() => <App />, document.querySelector("#app")!)
imports the App component from the correct module.

Comment on lines +82 to +120
```tsx [src/entry-server.tsx]
import { renderToStringAsync, HydrationScript } from "solid-js/web";
import { App } from "./app.jsx";

import clientAssets from "./entry-client?assets=client";
import serverAssets from "./entry-server?assets=ssr";

export default {
async fetch(req: Request): Promise<Response> {
const appHTML = await renderToStringAsync(() => <App />);
const rootHTML = await renderToStringAsync(() => <Root appHTML={appHTML} />);
return new Response(rootHTML, {
headers: { "Content-Type": "text/html" },
});
},
};

function Root(props: { appHTML?: string }) {
const assets = clientAssets.merge(serverAssets);
return (
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
{assets.css.map((attr: any) => (
<link key={attr.href} rel="stylesheet" {...attr} />
))}
{assets.js.map((attr: any) => (
<link key={attr.href} type="modulepreload" {...attr} />
))}
</head>
<body>
<div id="app" innerHTML={props.appHTML || ""} />
<HydrationScript />
<script type="module" src={assets.entry} />
</body>
</html>
);
}
```
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | πŸ”΄ Critical

Fix file extension inconsistency in import statement.

Line 84 imports from "./app.jsx" but the actual file shown in the code tree is app.tsx. This mismatch will cause module resolution errors at runtime.

πŸ› Proposed fix
 import { renderToStringAsync, HydrationScript } from "solid-js/web";
-import { App } from "./app.jsx";
+import { App } from "./app.tsx";

 import clientAssets from "./entry-client?assets=client";
πŸ€– Prompt for AI Agents
In `@examples/vite-ssr-solid/README.md` around lines 82 - 120, The import in
entry-server.tsx uses the wrong file extension ("./app.jsx") which does not
match the actual module (app.tsx); update the import statement in the module
that defines fetch and calls renderToStringAsync(() => <App />) to import
"./app.tsx" so the App symbol resolves correctly at runtime and module
resolution errors are avoided.

Comment on lines +217 to +256
```tsx [entry-server.tsx]
import { renderToStringAsync, HydrationScript } from "solid-js/web";
import { App } from "./app.jsx";

import clientAssets from "./entry-client?assets=client";
import serverAssets from "./entry-server?assets=ssr";

export default {
async fetch(req: Request): Promise<Response> {
const appHTML = await renderToStringAsync(() => <App />);
const rootHTML = await renderToStringAsync(() => <Root appHTML={appHTML} />);
return new Response(rootHTML, {
headers: { "Content-Type": "text/html" },
});
},
};

function Root(props: { appHTML?: string }) {
const assets = clientAssets.merge(serverAssets);
return (
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
{assets.css.map((attr: any) => (
<link key={attr.href} rel="stylesheet" {...attr} />
))}
{assets.js.map((attr: any) => (
<link key={attr.href} type="modulepreload" {...attr} />
))}
</head>
<body>
<div id="app" innerHTML={props.appHTML || ""} />
<HydrationScript />
<script type="module" src={assets.entry} />
</body>
</html>
);
}
```

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | πŸ”΄ Critical

Fix file extension inconsistency in import statement.

Line 219 imports from "./app.jsx" but the actual file shown in the code tree is app.tsx. This mismatch will cause module resolution errors at runtime.

πŸ› Proposed fix
 import { renderToStringAsync, HydrationScript } from "solid-js/web";
-import { App } from "./app.jsx";
+import { App } from "./app.tsx";

 import clientAssets from "./entry-client?assets=client";
πŸ€– Prompt for AI Agents
In `@examples/vite-ssr-solid/README.md` around lines 217 - 256, The import path
for the App component is using the wrong file extension; update the import
statement in entry-server.tsx from "import { App } from \"./app.jsx\";" to
reference the actual file "app.tsx" so module resolution succeeds; verify the
App symbol and subsequent renderToStringAsync(() => <App />) usage still resolve
and adjust any other import extensions (e.g., entry-client/entry-server asset
queries) if they mismatch actual filenames.

Comment on lines +267 to +273
```tsx [entry-client.tsx]
import { hydrate } from "solid-js/web";
import "./styles.css";
import { App } from "./app.jsx";

hydrate(() => <App />, document.querySelector("#app")!);
```
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | πŸ”΄ Critical

Fix file extension inconsistency in import statement.

Line 270 imports from "./app.jsx" but the actual file shown in the code tree is app.tsx. This mismatch will cause module resolution errors at runtime.

πŸ› Proposed fix
 import { hydrate } from "solid-js/web";
 import "./styles.css";
-import { App } from "./app.jsx";
+import { App } from "./app.tsx";

 hydrate(() => <App />, document.querySelector("#app")!);
πŸ€– Prompt for AI Agents
In `@examples/vite-ssr-solid/README.md` around lines 267 - 273, The import in
entry-client.tsx references the wrong extension ("./app.jsx") causing module
resolution failures; update the import to match the actual module filename by
changing the import source to "./app.tsx" in the entry-client.tsx file (the
hydrate call and document selector remain unchanged) so the module loader
imports the correct App component.

Comment on lines +22 to +31
"devDependencies": {
"@vitejs/plugin-vue": "^6.0.3",
"nitro": "latest",
"unhead": "^2.1.2",
"vite": "beta",
"vite-plugin-devtools-json": "^1.0.0",
"vue": "^3.5.27",
"vue-router": "^4.6.4"
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor

🧩 Analysis chain

🌐 Web query:

latest stable version of Vite 2025

πŸ’‘ Result:

As of 2025, the latest stable Vite release is Vite 7.3.0 (tagged β€œLatest” on the official GitHub Releases page, published Dec 15, 2025). [1]

Vite 8 was still in beta in 2025 (e.g., 8.0.0-beta.x), so it’s not the latest stable. [2]

Sources: [1] [2]


🏁 Script executed:

# Check if there's context in the example about why beta Vite is used
cat -n examples/vite-ssr-vue-router/README.md | head -40

Repository: nitrojs/nitro

Length of output: 1104


🏁 Script executed:

# Check if there's any explanation in the README about why beta Vite is used
cat -n examples/vite-ssr-vue-router/README.md | grep -A 5 -B 5 -i "beta\|vite.*version" | head -50

Repository: nitrojs/nitro

Length of output: 362


🏁 Script executed:

# Also check if there's a comment or note about Vite beta requirements elsewhere in the file
wc -l examples/vite-ssr-vue-router/README.md

Repository: nitrojs/nitro

Length of output: 99


🏁 Script executed:

# Search for mentions of "vite" throughout the README to understand context
rg -i "vite.*beta|beta.*vite|vite.*version" examples/vite-ssr-vue-router/README.md -A 3 -B 3

Repository: nitrojs/nitro

Length of output: 251


🏁 Script executed:

# Check the full content around the package.json section and see if there's explanation
sed -n '1,100p' examples/vite-ssr-vue-router/README.md

Repository: nitrojs/nitro

Length of output: 2236


🏁 Script executed:

# Check git history or comments in the file about why beta Vite was chosen
git log --oneline examples/vite-ssr-vue-router/README.md | head -10

Repository: nitrojs/nitro

Length of output: 92


🏁 Script executed:

# Search the entire example directory for any comments or docs explaining beta Vite
grep -r "vite.*beta\|beta.*vite" examples/vite-ssr-vue-router/ --include="*.md" --include="*.ts" --include="*.js" -B 3 -A 3

Repository: nitrojs/nitro

Length of output: 524


Use a stable Vite version instead of beta.

The example specifies "vite": "beta" (line 26), which contradicts the guideline that examples should reflect best practices. Vite 7.3.0 is the latest stable release and should be used instead. Beta versions are unstable and subject to breaking changes, leading to inconsistent behavior for users copying this template.

πŸ€– Prompt for AI Agents
In `@examples/vite-ssr-vue-router/README.md` around lines 22 - 31, The
devDependencies entry uses an unstable Vite version ("vite": "beta"); update the
package devDependencies by replacing the "vite": "beta" entry with the stable
release (e.g., "vite": "7.3.0" or a caret-pinned "^7.3.0") so the README example
reflects best practices and stable behavior; look for the "devDependencies"
block and the "vite": "beta" key in the shown diff to make the change.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

πŸ€– Fix all issues with AI agents
In `@examples/vite-ssr-tss-react/README.md`:
- Line 28: The .gitignore entry "/public/build# Sentry Config File" is malformed
because the comment is appended to the path pattern; change it so the path and
comment are separatedβ€”for example replace "/public/build# Sentry Config File"
with either "/public/build # Sentry Config File" or split into two lines:
"/public/build" and a separate "# Sentry Config File" comment; update the
README.md entry accordingly.
- Line 63: Replace the pre-release specifier for Vite with a stable version:
locate the dependency entry named "vite" (the "vite": "beta" line shown) in the
example's package.json/README snippet and update its version to a stable pinned
range such as "^7.3.0" so the example uses the latest stable Vite release
instead of a beta pre-release.
🧹 Nitpick comments (3)
examples/vite-ssr-vue-router/README.md (1)

156-173: Consider adding type safety for asset loading.

The asset loading logic uses any types for the metadata functions and attributes. While functional, defining interfaces for these would demonstrate better TypeScript practices.

πŸ’‘ Example type definitions
// Add before the handler function
interface AssetMeta {
  css: Array<{ href: string }>;
  js: Array<{ href: string }>;
}

interface RouteAssets {
  default: AssetMeta;
}

// Then update the type casts:
// Line 161: .map((fn) => (fn as () => Promise<RouteAssets>)()...
// Lines 169-170: ...assets.css.map((attrs) => ({...

Based on learnings, examples should reflect best practices, and strong typing is a TypeScript best practice.

examples/vite-ssr-tss-react/README.md (2)

252-252: Replace @ts-ignore with @ts-expect-error for better type safety.

When intentionally writing code that produces a type error (like linking to a non-existent route for 404 demonstration), use @ts-expect-error instead of @ts-ignore. This provides better safety because @ts-expect-error will fail if the error is fixed or if no error exists, while @ts-ignore suppresses all errors indiscriminately.

♻️ Proposed fix
           <Link
-            // `@ts-ignore`
+            // `@ts-expect-error` - Intentionally non-existent route for 404 demo
             to="/this-route-does-not-exist"
             activeProps={{ className: "font-bold" }}
           >

As per coding guidelines, examples should reflect best practices.


491-493: Consider adding API route implementation example.

The documentation mentions API routes and the generated route tree (line 143) references src/routes/api/test, but no implementation is provided. Including a complete example would better demonstrate the full capabilities of TanStack Start with Nitro.

Would you like me to suggest a sample API route implementation to include in this example?

/build/
/api/
/server/build
/public/build# Sentry Config File
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor

Fix malformed .gitignore entry.

The comment is directly appended to the path pattern without proper separation. This will cause the entire line (including # Sentry Config File) to be treated as a single path pattern rather than a path with a comment.

πŸ“ Proposed fix
-/public/build# Sentry Config File
+/public/build
+# Sentry Config File
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/public/build# Sentry Config File
/public/build
# Sentry Config File
πŸ€– Prompt for AI Agents
In `@examples/vite-ssr-tss-react/README.md` at line 28, The .gitignore entry
"/public/build# Sentry Config File" is malformed because the comment is appended
to the path pattern; change it so the path and comment are separatedβ€”for example
replace "/public/build# Sentry Config File" with either "/public/build # Sentry
Config File" or split into two lines: "/public/build" and a separate "# Sentry
Config File" comment; update the README.md entry accordingly.

"@vitejs/plugin-react": "^5.1.2",
"tailwindcss": "^4.1.18",
"typescript": "^5.9.3",
"vite": "beta",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor

🧩 Analysis chain

🏁 Script executed:

fd "package.json" examples/vite-ssr-tss-react/ --type f

Repository: nitrojs/nitro

Length of output: 97


🏁 Script executed:

cat -n examples/vite-ssr-tss-react/package.json

Repository: nitrojs/nitro

Length of output: 998


🏁 Script executed:

web_search
What does "beta" npm tag mean for Vite and what is the current latest stable version of Vite in 2025?

Repository: nitrojs/nitro

Length of output: 150


🌐 Web query:

What does "beta" npm tag mean for Vite and what is the current latest stable version of Vite?

πŸ’‘ Result:

What the beta npm tag means (for Vite)

On npm, dist-tags (like latest, beta, next) are just human-readable aliases that point to specific published versions. They’re used like npm i vite@beta. npm itself gives no special meaning to beta beyond β€œa tag name”; by convention it’s used for pre-releases/unstable builds, while latest is typically used for stable releases. Also, npm i vite (no tag) installs whatever is under latest. [1]

For Vite specifically, the Vite 8 pre-release line is published under beta versions like 8.0.0-beta.x (and commonly exposed via a beta dist-tag). [2]

Current latest stable Vite version

As of January 31, 2026, the latest stable Vite release is v7.3.0 (released December 15, 2025). [3]

Sources: npm dist-tags docs [1], Vite 8 beta announcement [2], Vite GitHub releases [3].


🏁 Script executed:

cat -n examples/vite-ssr-tss-react/README.md | head -100

Repository: nitrojs/nitro

Length of output: 2822


Use stable Vite version in example.

The example uses "vite": "beta" which installs pre-release versions. All other dependencies are stable and pinned. Update to "vite": "^7.3.0" (current latest stable) to match best practices and demonstrate a production-ready setup.

πŸ€– Prompt for AI Agents
In `@examples/vite-ssr-tss-react/README.md` at line 63, Replace the pre-release
specifier for Vite with a stable version: locate the dependency entry named
"vite" (the "vite": "beta" line shown) in the example's package.json/README
snippet and update its version to a stable pinned range such as "^7.3.0" so the
example uses the latest stable Vite release instead of a beta pre-release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants