Skip to content

docs: add examples#3984

Merged
pi0 merged 16 commits intonitrojs:mainfrom
RihanArfan:docs/examples
Feb 5, 2026
Merged

docs: add examples#3984
pi0 merged 16 commits intonitrojs:mainfrom
RihanArfan:docs/examples

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

This PR establishes a comprehensive examples documentation and browsing system for Nitro by adding 30+ categorized example projects with README and GUIDE documentation, creating a Nuxt Content-based UI for browsing examples with category-based navigation, and introducing an automd code-tree generator for embedding directory structures in documentation.

Changes

Cohort / File(s) Summary
Documentation Framework
docs/.docs/content.config.ts, docs/.docs/layouts/examples.vue, docs/.docs/pages/examples/index.vue, docs/.docs/pages/examples/[...slug].vue
Nuxt Content configuration, layout, and pages for example browsing with category-based grouping, navigation structure, and detail view rendering.
Example Documentation
examples/*/README.md, examples/*/GUIDE.md
25+ example projects documented with README files covering api-routes, auto-imports, cached-handler, custom-error-handler, database, elysia, express, fastify, hello-world, hono, import-alias, middleware, mono-jsx, nano-jsx, plugins, renderer, runtime-config, server-fetch, shiki, virtual-routes, vite-nitro-plugin, vite-rsc, vite-ssr-*, vite-trpc, and websocket.
Utilities and Server Routes
docs/.docs/utils/examples.ts, docs/.docs/server/routes/raw/examples/[...slug].md.get.ts
Category ordering, icon mappings, and raw markdown endpoint for fetching example content with title/description injection.
Documentation Generation
automd.config.ts
New ui-code-tree generator supporting directory traversal, .gitignore-based filtering, language detection, and configurable depth/expansion options.
Configuration and Metadata
docs/4.examples/0.index.md, docs/package.json, docs/pnpm-workspace.yaml
Examples landing page, dependency additions (automd, zod), and quote formatting updates.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed Title follows conventional commits format with 'docs:' prefix and clearly describes the main change of adding an examples section.
Description check ✅ Passed Description is related to the changeset, documenting that examples section has been added to the docs with supporting implementation files.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 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.

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".

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.

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: 16

🤖 Fix all issues with AI agents
In `@examples/cached-handler/GUIDE.md`:
- Line 17: Replace the brittle string match in the shouldBypassCache option with
proper query parsing: use H3's getQuery(req) to obtain the parsed query object
and check the skipCache parameter (e.g., query.skipCache === "true" or
Boolean(query.skipCache)) instead of req.url.includes("skipCache=true"); update
the shouldBypassCache implementation that references req to call getQuery(req)
and evaluate the skipCache key.

In `@examples/custom-error-handler/GUIDE.md`:
- Around line 7-16: The example always returns status 500; change the handler
inside defineErrorHandler to derive the status from the thrown error (e.g.,
check error.status and error.statusCode) and fall back to 500, then use that
status in the Response returned from the export default
defineErrorHandler((error, _event) => { ... }); so the handler respects
HTTPError status values.

In `@examples/database/GUIDE.md`:
- Around line 5-57: Reorder the examples so the migration task (the defineTask
example that uses useDatabase and db.sql to DROP/CREATE users) appears before
the request handler example; then modify the request handler (defineHandler
using useDatabase and db.sql) to remove schema-changing statements (DROP TABLE /
CREATE TABLE) and instead just query the already-migrated schema (e.g.,
INSERT/SELECT by userId), keeping the explanatory text that migrations should
run via tasks and that request handlers should assume the schema exists.
- Around line 13-14: Remove the schema operations that run on each
request—specifically the db.sql calls that DROP TABLE IF EXISTS users and CREATE
TABLE IF NOT EXISTS users—so the request handler only performs queries; instead,
either assume the users table exists and add a one-line note before the example
stating the table should be created via migrations, or, if keeping it as a demo,
replace the destructive operations with a prominent warning that table creation
is for demo only and must not be used in production request handlers.

In `@examples/import-alias/GUIDE.md`:
- Around line 5-8: Update the alias imports to remove the .ts extensions: locate
the import statements referencing "~server/utils/math.ts" and
"#server/utils/math.ts" (in the server/routes/index.ts import lines) and change
them to extensionless imports ("~server/utils/math" and "#server/utils/math") so
TypeScript/Nitro module resolution remains correct and the imports survive TS→JS
transpilation.

In `@examples/renderer/GUIDE.md`:
- Around line 5-25: The example renderer directly interpolates user-controlled
values (url.pathname and apiRes) into HTML, creating XSS risk; fix by adding an
HTML-escaping helper (e.g., function escapeHtml(unsafe: string): string) and use
it when rendering values inside the template string in the default exported
renderer function so you call escapeHtml(url.pathname) and escapeHtml(apiRes)
before interpolation; ensure the helper escapes &, <, >, ", and ' to their HTML
entities and keep the Response creation and headers unchanged.

In `@examples/vite-nitro-plugin/README.md`:
- Around line 14-27: The example package.json uses unstable tags ("vite": "beta"
and "nitro": "latest") which should be replaced with pinned, reproducible
versions; update the devDependencies entries in package.json to specific stable
versions or fixed ranges (e.g., exact patch versions or a caret range you trust)
instead of "beta"/"latest", then update the README snippet to match and
regenerate the lockfile (run npm/yarn/pnpm install) so the example reflects a
reproducible configuration.

In `@examples/vite-rsc/GUIDE.md`:
- Around line 129-137: The external anchor tags that open in new tabs (the <a>
elements wrapping viteLogo, reactLogo and nitroLogo using target="_blank") must
include rel="noreferrer noopener" to prevent exposing window.opener; update each
<a href=... target="_blank"> in the GUIDE examples to add rel="noreferrer
noopener" (e.g., the anchors around viteLogo, reactLogo, nitroLogo) so all
external links follow best practices.

In `@examples/vite-rsc/README.md`:
- Around line 376-425: The example can throw because setPayload is only assigned
inside BrowserRoot's useEffect but may be called earlier by
fetchRscPayload()/setServerCallback/HMR; initialize setPayload to a safe no-op
(e.g., let setPayload: (v: RscPayload) => void = () => {} ) or add a guard
before invoking it in fetchRscPayload and the server callback; update the
top-level declaration of setPayload and ensure fetchRscPayload and the
setServerCallback handler call the safe function or check for existence before
calling so renders never call an uninitialized function.

In `@examples/vite-ssr-preact/GUIDE.md`:
- Line 55: The import in GUIDE.md references ./app.jsx while the component is
defined as src/app.tsx; update the import to the correct module extension or
rename the component so they match—specifically change the import of App (the
`import { App } ...` statement) to point to ./app.tsx (or rename src/app.tsx to
app.jsx) so module resolution succeeds.

In `@examples/vite-ssr-react/README.md`:
- Line 30: Replace the unstable dependency entry "vite": "beta" in package.json
with a stable version constraint (for example "vite": "^7.3.1") so the example
uses the current stable Vite release; update the package.json dependency line
where "vite" is defined to the chosen stable version string and run npm/yarn
install to refresh lockfile if present.

In `@examples/vite-ssr-tsr-react/README.md`:
- Line 2: The frontmatter "category: server side rendering" conflicts with the
README description "Client-side routing with TanStack Router"; update either the
frontmatter or the description so they match – e.g., change the frontmatter to
"category: client side rendering" (or "SPA/CSR") if the example demonstrates
client-side routing with TanStack Router, or rewrite the description to
emphasize SSR features if the example is actually SSR-focused; locate and edit
the README.md lines containing the exact text "category: server side rendering"
and the description "Client-side routing with TanStack Router" to keep them
consistent.
- Line 60: The tsconfig path mapping for the alias "@/*" is incorrect: it
currently maps "@/*" to "sec/*" — update the mapping so "@/*" maps to "src/*"
instead (replace "sec/*" with "src/*") to restore correct module resolution for
the "@/*" imports; verify the change in the tsconfig/paths section where the
string "@/*": ["sec/*"] appears.
- Line 48: The examples package.json pins Vite to a pre-release ("vite":
"beta"); update the dependency in the examples/vite-ssr-tsr-react package.json
by replacing the "vite": "beta" entry with a stable version constraint (for
example "vite": "^7.3.1" or the current latest stable) so the example uses a
supported production release instead of a beta.

In `@examples/vite-trpc/GUIDE.md`:
- Around line 122-126: Remove the unsupported <script server> block and the
non-existent echo() call; instead implement the fetch logic in a Nitro server
route or renderer (use defineEventHandler in a file under server/routes or
server/api) and have that handler call serverFetch (or perform the same fetch)
and return the data or rendered HTML; then consume that route from the client
(or have the renderer return HTML) rather than trying to output inside a
template with echo()—look for serverFetch and echo usages in the snippet and
move their logic into the new handler.

In `@examples/vite-trpc/README.md`:
- Around line 109-124: Update the example package.json devDependencies to avoid
non-reproducible tags: replace the "nitro": "latest" and "vite": "beta" entries
with pinned or semver-ranged versions (e.g., use specific versions or caret
ranges) so installs are reproducible; modify the devDependencies object in the
package.json (the entries for "nitro" and "vite") to concrete version strings
per project guidelines.
🧹 Nitpick comments (13)
examples/mono-jsx/GUIDE.md (1)

4-7: Fix typo in example heading text ("mongo-jsx" → "mono-jsx").

The example name should match the integration name used elsewhere in the doc.

examples/hono/README.md (2)

52-57: Consider whether vite.config.ts is necessary for this minimal example.

The vite.config.ts file is included in the code tree but not explained in the documentation. If this is an optional configuration or only needed for specific use cases (e.g., development with Vite dev server), consider either:

  • Adding a brief explanation of when/why it's needed
  • Removing it to keep the example minimal, if it's not essential for the basic Hono integration

67-77: Code duplication between code-tree and GUIDE sections.

The server.ts code appears identically in both the code-tree section (lines 34-44) and here in the GUIDE.md inclusion (lines 67-77). While this may be intentional for documentation flow (showing the complete file tree first, then explaining specific files), it creates a maintenance burden if the code needs updating.

Consider whether the GUIDE section could reference the code-tree instead of duplicating the code block, or confirm this duplication is the intended documentation pattern across all examples.

examples/vite-ssr-preact/GUIDE.md (1)

107-107: Consider defensive coding for element selection.

The non-null assertion assumes #app exists. While valid in this SSR setup, adding a runtime check would make this example more robust and educational for users learning best practices.

🛡️ Suggested defensive approach
 function main() {
-  hydrate(<App />, document.querySelector("#app")!);
+  const container = document.querySelector("#app");
+  if (!container) {
+    throw new Error("Failed to find `#app` container");
+  }
+  hydrate(<App />, container);
 }
examples/runtime-config/README.md (1)

14-22: Avoid un-ignoring .env; prefer .env.example.

The snippet un-ignores .env which encourages committing secrets, conflicting with the warning text and best practices for examples. Suggest keeping .env ignored and providing .env.example.

♻️ Proposed doc fix
```text [.env]
-# NEVER COMMIT SENSITIVE DATA. THIS IS ONLY FOR DEMO PURPOSES.
-NITRO_API_KEY=secret-api-key
+# Copy to .env and never commit secrets.
+NITRO_API_KEY=secret-api-key
-# THIS IS ONLY FOR DEMO. DO NOT COMMIT SENSITIVE DATA IN REAL PROJECTS
-!.env
+.env
+.env.*
+!.env.example
+NITRO_API_KEY=secret-api-key

As per coding guidelines: "Add examples in examples/ for new integrations and ensure they reflect best practices".

examples/websocket/README.md (2)

233-233: Consider exact string matching for ping/pong.

The current implementation uses message.text().includes("ping") which matches any message containing "ping" as a substring. Messages like "I'm pinging you" or "stopping" would also trigger a pong response.

For a clearer ping/pong mechanism, consider exact matching:

💬 Proposed refinement for exact ping detection
   message(peer, message) {
-    if (message.text().includes("ping")) {
+    if (message.text().trim() === "ping") {
       peer.send({ user: "server", message: "pong" });
     } else {

Also applies to: 272-272


228-230: Document the subscription timing behavior.

The peer subscribes to the "chat" channel (line 230) after publishing the join notification (line 229). This means the joining peer won't receive their own "X joined!" message—only other connected peers will see it.

If this is intentional UX design, consider adding a comment to clarify. If not, consider subscribing before publishing:

📝 Optional reordering to receive own join message
   open(peer) {
+    peer.subscribe("chat");
     peer.send({ user: "server", message: `Welcome ${peer}!` });
     peer.publish("chat", { user: "server", message: `${peer} joined!` });
-    peer.subscribe("chat");
   },

Also applies to: 266-269

examples/nano-jsx/GUIDE.md (1)

12-12: Consider using standard HTTP header naming.

The phrase "content type header" could be more precise as "Content-Type header" to match standard HTTP header naming conventions.

📝 Suggested terminology improvement
-Nitro auto-detects `server.tsx` and uses it as the server entry. Use `renderSSR` from nano-jsx to convert JSX into an HTML string. The `html` helper from H3 sets the correct content type header.
+Nitro auto-detects `server.tsx` and uses it as the server entry. Use `renderSSR` from nano-jsx to convert JSX into an HTML string. The `html` helper from H3 sets the correct Content-Type header.
examples/import-alias/GUIDE.md (1)

19-21: Configuration section is incomplete — add concrete snippets.

Right now the section ends after the lead‑in sentence, so readers can’t apply the guidance. Please add minimal examples for nitro.config.ts, package.json imports, and tsconfig.json paths (and Vite if relevant) to reflect best practices.

🧩 Example patch (fill in exact values used in this repo)
 ## Configuration
 
-Aliases can be configured in `package.json` imports field or `nitro.config.ts`.
+Aliases can be configured in `package.json` `imports` or `nitro.config.ts`.
+
+```ts [nitro.config.ts]
+export default defineNitroConfig({
+  alias: {
+    "~server": "./server",
+    "#server": "./server"
+  }
+});
+```
+
+```json [package.json]
+{
+  "imports": {
+    "~server/*": "./server/*",
+    "#server/*": "./server/*"
+  }
+}
+```
+
+```json [tsconfig.json]
+{
+  "compilerOptions": {
+    "paths": {
+      "~server/*": ["./server/*"],
+      "#server/*": ["./server/*"]
+    }
+  }
+}
+```
examples/vite-ssr-tss-react/GUIDE.md (1)

89-91: Use the standard utf-8 charset value.

utf8 is non‑standard; HTML expects utf-8.

✅ Suggested tweak
-      { charSet: "utf8" },
+      { charSet: "utf-8" },
As per coding guidelines: "examples/**/*: Add examples in `examples/` for new integrations and ensure they reflect best practices".
examples/vite-nitro-plugin/GUIDE.md (1)

1-1: Optional: Consider alternative wording.

The text is clear as written. If you prefer a slightly more formal tone, you could use "provides" instead of "gives" per the style checker suggestion, but the current wording is perfectly acceptable.

examples/custom-error-handler/GUIDE.md (2)

10-15: Consider respecting the error's status code.

The error handler always returns status 500, but the thrown error (e.g., HTTPError at line 28) may have a different status code. Consider extracting and using the error's status when available to provide more accurate HTTP responses.

📊 Proposed enhancement to respect error status
 export default defineErrorHandler((error, _event) => {
+  const status = error.statusCode || error.status || 500;
   return new Response(`Custom Error Handler: ${error.message}`, {
-    status: 500,
+    status,
     headers: { "Content-Type": "text/plain" },
   });
 });

18-18: Optionally demonstrate event parameter usage.

The guide mentions using the event object to customize responses per route, but the error handler example at line 10 doesn't demonstrate this. Consider adding a brief example showing how to access request details from the event.

📝 Example of using the event parameter

Add this example after line 16 to demonstrate event usage:

// Example: Customize response based on request path
export default defineErrorHandler((error, event) => {
  const path = event.path || event.node.req.url;
  const isAPI = path.startsWith('/api');
  
  return new Response(
    isAPI 
      ? JSON.stringify({ error: error.message })
      : `Custom Error Handler: ${error.message}`,
    {
      status: error.statusCode || 500,
      headers: { 
        "Content-Type": isAPI ? "application/json" : "text/plain" 
      },
    }
  );
});

<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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's locate and read the file
find . -path "*examples/cached-handler/GUIDE.md" -type f

Repository: nitrojs/nitro

Length of output: 91


🏁 Script executed:

# Also check if the file exists and read its contents
ls -la examples/cached-handler/ 2>/dev/null || echo "Directory not found, searching for similar files"
fd "GUIDE.md" examples/

Repository: nitrojs/nitro

Length of output: 1547


🏁 Script executed:

# Search for shouldBypassCache in the codebase
rg -n "shouldBypassCache" --type=ts --type=js --type=md

Repository: nitrojs/nitro

Length of output: 1560


🏁 Script executed:

# Search for defineCachedHandler to understand the API
rg -n "defineCachedHandler" --type=ts --type=js -A 10 | head -50

Repository: nitrojs/nitro

Length of output: 3178


🏁 Script executed:

# Search for getQuery usage patterns in examples
rg -n "getQuery" --type=ts --type=js -B 2 -A 2 | head -30

Repository: nitrojs/nitro

Length of output: 1338


🏁 Script executed:

# Read the GUIDE.md file to see the actual content
cat -n examples/cached-handler/GUIDE.md

Repository: nitrojs/nitro

Length of output: 1246


🏁 Script executed:

# Check the cache.ts implementation to understand what args are passed to shouldBypassCache
rg -n "shouldBypassCache\?" src/runtime/internal/cache.ts -B 5 -A 5

Repository: nitrojs/nitro

Length of output: 483


🏁 Script executed:

# Look at the type definition to understand ArgsT
rg -n "CachedEventHandlerOptions" src/types/runtime/cache.ts -A 20

Repository: nitrojs/nitro

Length of output: 282


🏁 Script executed:

# Check if req property exists or if it's just event parameter
rg -n "req\." examples/cached-handler/ --type=ts --type=js

Repository: nitrojs/nitro

Length of output: 166


🏁 Script executed:

# Check what HTTPEvent type is
rg -n "type HTTPEvent\|interface HTTPEvent" src/types/ -B 2 -A 10

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Look at the actual implementation of defineCachedHandler to see what it passes
cat -n src/runtime/internal/cache.ts | sed -n '183,210p'

Repository: nitrojs/nitro

Length of output: 1150


🏁 Script executed:

# Check the actual server.ts in the example to see the full context
cat -n examples/cached-handler/server.ts

Repository: nitrojs/nitro

Length of output: 575


🏁 Script executed:

# Check what properties are available on the event parameter
rg -n "req\." src/runtime/internal/cache.ts -B 3 -A 3

Repository: nitrojs/nitro

Length of output: 2008


🏁 Script executed:

# Search for how shouldBypassCache is documented or typed
rg -n "shouldBypassCache" docs/ -B 2 -A 5

Repository: nitrojs/nitro

Length of output: 899


🏁 Script executed:

# Check if getQuery is a public export from nitro/h3
rg -n "export.*getQuery" src/runtime/ --type=ts

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Verify the type signature of H3Event and confirm it has req property
rg -n "interface H3Event\|type H3Event" src/types/ -A 20

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Check examples in the documentation to see how query parameters are typically handled
rg -n "getQuery" examples/ --type=ts -B 2 -A 2

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Look at h3 exports more broadly
rg -n "export" src/runtime/h3.ts | head -30

Repository: nitrojs/nitro

Length of output: 114


🏁 Script executed:

# Check the actual h3.ts file
cat src/runtime/h3.ts | head -80

Repository: nitrojs/nitro

Length of output: 106


🏁 Script executed:

# Search for getQuery in src/
find src -name "*.ts" -type f -exec grep -l "getQuery" {} \;

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Look at how event.url and event.req.url relate
rg -n "event\.url\|event\.req\.url" src/runtime/internal/cache.ts

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Check what the req object is by looking at Node.js web standards or h3 imports
rg -n "import.*from.*h3" src/runtime/internal/cache.ts

Repository: nitrojs/nitro

Length of output: 201


🏁 Script executed:

# Check h3 package and its exports
rg -n "getQuery\|parseQuery" --type=ts | head -20

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Look at the actual code usage in test fixture
cat test/fixture/server/routes/file.ts

Repository: nitrojs/nitro

Length of output: 391


🏁 Script executed:

# Check imports in the example file and related files
rg -n "import" examples/cached-handler/ --type=ts

Repository: nitrojs/nitro

Length of output: 447


🏁 Script executed:

# Look at how query parameters are accessed using event.url
rg -n "event\.url\." src/runtime/internal/cache.ts -B 2 -A 2

Repository: nitrojs/nitro

Length of output: 212


🏁 Script executed:

# Look for URL parsing utilities in nitro
rg -n "parseURL\|useQuery\|getQuery" src/ --type=ts | head -20

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Check if event.req.url is actually used elsewhere or if it should be event.url
rg -n "\.req\.url\|\.url\.search\|\.url\.pathname" src/runtime/internal/cache.ts

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Search for how query parameter checking is done in other examples
rg -n "skipCache\|query\|getQuery" examples/ --type=ts -B 1 -A 1

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Check the type of H3Event to see what properties are available
fd "h3" src/types/ --type=f

Repository: nitrojs/nitro

Length of output: 72


🏁 Script executed:

# Look for documentation on proper query parameter access
cat docs/1.docs/7.cache.md | grep -A 10 -B 10 "shouldBypassCache"

Repository: nitrojs/nitro

Length of output: 1307


🏁 Script executed:

# Verify getQuery is exported from nitro/h3 by checking the package
grep -r "getQuery" src/types/h3.ts || grep -r "getQuery" src/ --type=ts | grep "export"

Repository: nitrojs/nitro

Length of output: 177


🏁 Script executed:

# Check h3 package.json to see if getQuery is available
fd package.json src/runtime | xargs grep -l "h3" || echo "Check h3 dependency"

Repository: nitrojs/nitro

Length of output: 76


🏁 Script executed:

# Look at how other examples handle query parameters
rg -n "query\|getQuery" examples/ --type=ts --type=md -B 1 -A 1 | head -40

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Verify the event object structure in H3Event
cat src/types/h3.ts | head -150

Repository: nitrojs/nitro

Length of output: 896


🏁 Script executed:

# Check if getQuery is actually exported in the h3 package that nitro uses
rg -n "getQuery\|export" src/types/h3.ts -A 2 -B 2

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Verify the behavior by checking if string.includes() would really cause false positives
# Example: if someone has ?param=skipCache=true, would it match?
python3 << 'EOF'
url_examples = [
    "http://example.com/?skipCache=true",
    "http://example.com/?foo=skipCache=true",
    "http://example.com/?skipCacheme=true",
    "http://example.com/?param=value&skipCache=true",
]

for url in url_examples:
    result = "skipCache=true" in url
    print(f"{url} -> {result}")
EOF

Repository: nitrojs/nitro

Length of output: 247


🏁 Script executed:

# Verify what the actual query parameter check should return
# by looking at how getQuery works in the test fixture context
cat test/fixture/server/routes/file.ts

Repository: nitrojs/nitro

Length of output: 391


🏁 Script executed:

# Check if H3Event is re-exported or if it has both req and url properties
rg -n "from.*h3\|export.*H3Event" src/types/h3.ts

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Look at what the actual issue is - check if the destructuring is correct
cat src/runtime/internal/cache.ts | sed -n '147,151p'

Repository: nitrojs/nitro

Length of output: 218


🏁 Script executed:

# Verify the function signature by looking at the CacheOptions generic type
rg -n "CacheOptions.*ResponseCacheEntry" src/types/runtime/cache.ts -B 5 -A 10

Repository: nitrojs/nitro

Length of output: 360


🏁 Script executed:

# Check if event parameter should be destructured or used directly
rg -n "shouldBypassCache.*event\|shouldBypassCache.*{" examples/ -B 1 -A 1

Repository: nitrojs/nitro

Length of output: 212


🏁 Script executed:

# Check HTTPEvent definition from h3 types
rg -n "HTTPEvent" src/types/ -A 5

Repository: nitrojs/nitro

Length of output: 3059


🏁 Script executed:

# Verify if getQuery can work with HTTPEvent or if it's H3Event specific
rg -n "getQuery.*event\|getQuery.*=" examples/cached-handler/ test/fixture/ --type=ts

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Look for URL parsing utilities in Nitro
rg -n "parseURL\|new URL" src/runtime/internal/cache.ts -B 2 -A 2

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Check if the destructuring ({ req }) is correct by looking at other examples
rg -n "shouldBypassCache.*req\|shouldBypassCache.*event" src/runtime/internal/ -B 1 -A 1

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Check if HTTPEvent and H3Event are the same or related
rg -n "HTTPEvent.*H3Event\|H3Event.*HTTPEvent" src/types/ -B 2 -A 2

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Verify what properties are available on HTTPEvent by checking how event is used in cache.ts
rg -n "event\.(url|req)" src/runtime/internal/cache.ts | head -20

Repository: nitrojs/nitro

Length of output: 730


🏁 Script executed:

# Check if there's a standard way to parse query parameters from HTTPEvent
rg -n "URLSearchParams\|new URL" src/runtime/ --type=ts

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Verify the false positive issue more thoroughly
python3 << 'EOF'
# Test cases for the current implementation
test_cases = [
    ("?skipCache=true", True, "Valid: single parameter"),
    ("?foo=skipCache=true", True, "FALSE POSITIVE: parameter value contains skipCache=true"),
    ("?skipCacheme=true", False, "Not a false positive"),
    ("?cache=skip&skipCache=true", True, "Valid: with multiple params"),
    ("?skipCache=false", True, "FALSE POSITIVE: would match even though value is false"),
]

print("Current implementation: req.url.includes('skipCache=true')\n")
for url_query, matches, desc in test_cases:
    full_url = f"http://example.com{url_query}"
    result = "skipCache=true" in full_url
    status = "✓" if result == matches else "✗ BUG"
    print(f"{status} {desc}")
    print(f"   URL: {full_url}")
    print(f"   Match: {result}\n")
EOF

Repository: nitrojs/nitro

Length of output: 624


🏁 Script executed:

# Verify if the destructuring ({ req }) actually works or if it's a type error
# by checking if there's any type casting or if req is somehow available from destructuring
rg -n "function.*shouldBypassCache\|shouldBypassCache.*{.*req" src/ examples/ --type=ts

Repository: nitrojs/nitro

Length of output: 242


🏁 Script executed:

# Check the complete CachedEventHandlerOptions to understand the function signature better
cat src/types/runtime/cache.ts

Repository: nitrojs/nitro

Length of output: 1177


🏁 Script executed:

# Verify if the destructuring syntax is valid - check if there are any similar patterns
rg -n "shouldBypassCache.*req" src/ examples/ -B 1 -A 1

Repository: nitrojs/nitro

Length of output: 830


🏁 Script executed:

# Check the actual runtime to see if there's some wrapper or if this is a bug
rg -n "shouldBypassCache" src/runtime/internal/cache.ts -B 5 -A 5

Repository: nitrojs/nitro

Length of output: 915


🏁 Script executed:

# Test if the destructuring would even work by checking if event.req exists
# and if destructuring would work properly
python3 << 'EOF'
# Simulate the HTTPEvent structure based on observations
class Request:
    def __init__(self):
        self.url = "http://example.com?skipCache=true"
        self.method = "GET"

class HTTPEvent:
    def __init__(self):
        self.req = Request()
        self.url_obj = type('obj', (object,), {'pathname': '/', 'search': '?skipCache=true'})()

event = HTTPEvent()

# Test 1: Direct parameter access
try:
    # This is what should happen: shouldBypassCache: (event) => ...
    result1 = event.req.url.includes("skipCache=true") if hasattr(event.req.url, 'includes') else "skipCache=true" in event.req.url
    print(f"Direct access event.req.url: Works? {result1}")
except Exception as e:
    print(f"Direct access failed: {e}")

# Test 2: Destructuring (what the example shows)
try:
    # This is what the example shows: shouldBypassCache: ({ req }) => ...
    def test_destructure({req}):
        return "skipCache=true" in req.url
    print("Destructuring syntax: Valid in Python? No, Python doesn't support this syntax")
except:
    print("Destructuring: This is TypeScript/JavaScript syntax, not valid Python")

# In TypeScript, { req } would destructure the first parameter
# So the function signature implies the first param should be an object with a 'req' property
# But HTTPEvent IS an object with a 'req' property, so it should work
print("\nIn TypeScript: { req } destructuring from HTTPEvent parameter SHOULD work")
print("It would extract event.req and bind it to the 'req' variable")
EOF

Repository: nitrojs/nitro

Length of output: 170


🏁 Script executed:

# Final check: Compare with the default implementation in cache.ts
cat src/runtime/internal/cache.ts | sed -n '192,196p'

Repository: nitrojs/nitro

Length of output: 236


🏁 Script executed:

# Check if getQuery is re-exported from nitro or if it's only from h3
rg -n "export.*getQuery\|from.*h3" src/runtime/ --type=ts | head -20

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Look at how to properly parse query params given we have event.url.search
# Check if there's a parseURL or query parsing utility in nitro
rg -n "parseURL\|searchParams\|URLSearchParams" src/runtime/ --type=ts

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

# Verify proper query parameter parsing approach
python3 << 'EOF'
from urllib.parse import urlparse, parse_qs

# Test proper vs improper parsing
test_urls = [
    "http://example.com?skipCache=true",
    "http://example.com?foo=skipCache=true", 
    "http://example.com?skipCache=false",
    "http://example.com?cache=skip&skipCache=true"
]

print("Using proper query parsing (URLSearchParams/parse_qs):\n")
for url in test_urls:
    parsed = urlparse(url)
    params = parse_qs(parsed.query)
    has_skip = "skipCache" in params and params["skipCache"][0] == "true"
    print(f"URL: {url}")
    print(f"  skipCache=true? {has_skip}\n")
EOF

Repository: nitrojs/nitro

Length of output: 382


Use proper query parameter parsing instead of string matching.

The current implementation uses req.url.includes("skipCache=true") which is brittle and produces false positives. For example, ?foo=skipCache=true would incorrectly trigger cache bypass. As per the coding guidelines, examples should reflect best practices for query parameter handling.

Use H3's getQuery() utility to properly parse query parameters:

Suggested fix
-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) => getQuery(event).skipCache === "true" }
 );
🤖 Prompt for AI Agents
In `@examples/cached-handler/GUIDE.md` at line 17, Replace the brittle string
match in the shouldBypassCache option with proper query parsing: use H3's
getQuery(req) to obtain the parsed query object and check the skipCache
parameter (e.g., query.skipCache === "true" or Boolean(query.skipCache)) instead
of req.url.includes("skipCache=true"); update the shouldBypassCache
implementation that references req to call getQuery(req) and evaluate the
skipCache key.

Comment on lines +7 to +16
```ts [error.ts]
import { defineErrorHandler } from "nitro";

export default defineErrorHandler((error, _event) => {
return new Response(`Custom Error Handler: ${error.message}`, {
status: 500,
headers: { "Content-Type": "text/plain" },
});
});
```
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:

Nitro defineErrorHandler API documentation

💡 Result:

Nitro doesn’t expose a defineErrorHandler() helper. The runtime error-handler API is:

  • Config option: errorHandler → path to a custom error handler module. The handler receives (error: H3Error, event: H3Event) and is expected to send the response itself. Example uses defineNitroErrorHandler((error, event) => { ... }). [1]
// nitro.config.ts
export default defineNitroConfig({
  errorHandler: "~/error",
});

// error.ts
export default defineNitroErrorHandler((error, event) => {
  setResponseHeader(event, "Content-Type", "text/plain");
  return send(event, "[custom error handler] " + error.stack);
});

If your goal is capturing/reporting errors (rather than replacing the error response), Nitro also supports error capturing and an error hook via Nitro plugins. [2]


🏁 Script executed:

#!/bin/bash
# Search for defineErrorHandler in the repository
rg "defineErrorHandler" -t ts -t js -t md -C 2

Repository: nitrojs/nitro

Length of output: 2567


🏁 Script executed:

#!/bin/bash
# Check for nitro package type definitions or exports
fd -e "d.ts" -e "ts" | xargs grep -l "defineErrorHandler" 2>/dev/null | head -5

Repository: nitrojs/nitro

Length of output: 138


The defineErrorHandler API is correct and properly imported from "nitro". The import is valid and the example follows the established patterns in the Nitro codebase.

However, the hardcoded status 500 doesn't respect the error's actual status code. If error is an HTTPError with a specific status property, that should be used instead.

Suggested improvement:
import { defineErrorHandler } from "nitro";

export default defineErrorHandler((error, _event) => {
  const status = error.statusCode || 500;
  return new Response(`Custom Error Handler: ${error.message}`, {
    status,
    headers: { "Content-Type": "text/plain" },
  });
});
🤖 Prompt for AI Agents
In `@examples/custom-error-handler/GUIDE.md` around lines 7 - 16, The example
always returns status 500; change the handler inside defineErrorHandler to
derive the status from the thrown error (e.g., check error.status and
error.statusCode) and fall back to 500, then use that status in the Response
returned from the export default defineErrorHandler((error, _event) => { ... });
so the handler respects HTTPError status values.

Comment on lines +5 to +57
```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,
};
});
```

Retrieve the database instance using `useDatabase()`. The database can be queried using `db.sql`, and variables like `${userId}` are automatically escaped to prevent SQL injection.

## Running Migrations with Tasks

Nitro tasks let you run operations outside of request handlers. For database migrations, create a task file in `tasks/` and run it via the CLI. This keeps schema changes separate from your application code.

```ts [tasks/db/migrate.ts]
import { defineTask } from "nitro/task";
import { useDatabase } from "nitro/database";

export default defineTask({
meta: {
description: "Run database migrations",
},
async run() {
const db = useDatabase();

console.log("Running database migrations...");

// Create users table
await db.sql`DROP TABLE IF EXISTS users`;
await db.sql`CREATE TABLE IF NOT EXISTS users ("id" TEXT PRIMARY KEY, "firstName" TEXT, "lastName" TEXT, "email" TEXT)`;

return {
result: "Database migrations complete!",
};
},
});
```
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

Suggest restructuring: present migrations before request handlers.

The guide currently presents examples in an order that contradicts best practices:

  1. First example (lines 5-27): shows schema changes in a request handler
  2. Second example (lines 31-57): correctly shows schema changes in migrations with the note "keeps schema changes separate from your application code"

This progression teaches the anti-pattern before the best practice.

📋 Recommended structure

Consider reordering to:

  1. First: Present the migration task example (current lines 31-57)
  2. Then: Show the request handler that queries the migrated schema (modified lines 5-27 without DROP/CREATE)

This progression teaches the proper pattern first and shows how request handlers build on a properly migrated schema. It also aligns with the coding guideline that examples should "reflect best practices."

🤖 Prompt for AI Agents
In `@examples/database/GUIDE.md` around lines 5 - 57, Reorder the examples so the
migration task (the defineTask example that uses useDatabase and db.sql to
DROP/CREATE users) appears before the request handler example; then modify the
request handler (defineHandler using useDatabase and db.sql) to remove
schema-changing statements (DROP TABLE / CREATE TABLE) and instead just query
the already-migrated schema (e.g., INSERT/SELECT by userId), keeping the
explanatory text that migrations should run via tasks and that request handlers
should assume the schema exists.

Comment on lines +13 to +14
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)`;
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: Remove schema operations from request handler.

The example drops and recreates the users table on every HTTP request, which would:

  • Destroy all data with each request
  • Create race conditions under concurrent load
  • Violate the best practice stated later: "keeps schema changes separate from your application code"

This directly contradicts the coding guideline requirement that examples "reflect best practices."

🔧 Recommended fix: separate schema and query operations

Option 1 (recommended): Remove schema operations and assume the table exists from a migration:

 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', '')`;

Then add a note before the example:

This example assumes the `users` table has already been created via a migration (see the Migrations section below).

Option 2: If this is meant as a self-contained demo, add a prominent warning:

> **Note:** This example includes table creation for demonstration purposes only. In production applications, use migrations (shown below) to manage schema changes, not request handlers.
🤖 Prompt for AI Agents
In `@examples/database/GUIDE.md` around lines 13 - 14, Remove the schema
operations that run on each request—specifically the db.sql calls that DROP
TABLE IF EXISTS users and CREATE TABLE IF NOT EXISTS users—so the request
handler only performs queries; instead, either assume the users table exists and
add a one-line note before the example stating the table should be created via
migrations, or, if keeping it as a demo, replace the destructive operations with
a prominent warning that table creation is for demo only and must not be used in
production request handlers.

Comment on lines +5 to +8
```ts [server/routes/index.ts]
import { sum } from "~server/utils/math.ts";

import { rand } from "#server/utils/math.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

🧩 Analysis chain

🏁 Script executed:

# Search for import alias configuration
find . -type f \( -name "tsconfig.json" -o -name "tsconfig*.json" -o -name "vite.config.*" -o -name "nuxt.config.*" \) | head -20

Repository: nitrojs/nitro

Length of output: 784


🏁 Script executed:

# Search for import alias examples in the codebase
rg "from ['\"]~.*(?:\.ts)?['\"]|from ['\"]#.*(?:\.ts)?['\"]" --type ts --type tsx -A 2 -B 2 | head -100

Repository: nitrojs/nitro

Length of output: 84


🏁 Script executed:

# Check the example file itself
cat examples/import-alias/GUIDE.md

Repository: nitrojs/nitro

Length of output: 768


🏁 Script executed:

# Look for other import alias related documentation
find . -type f -name "*.md" | xargs grep -l "import.*alias\|alias.*import" 2>/dev/null | head -10

Repository: nitrojs/nitro

Length of output: 123


🏁 Script executed:

# Check the import-alias example configuration
cat examples/import-alias/tsconfig.json

Repository: nitrojs/nitro

Length of output: 173


🏁 Script executed:

# Check nitro or vite config for import-alias
cat examples/import-alias/vite.config.ts 2>/dev/null || echo "No vite config"
cat examples/import-alias/nitro.config.ts 2>/dev/null || echo "No nitro config"

Repository: nitrojs/nitro

Length of output: 323


🏁 Script executed:

# Check package.json for imports field
cat examples/import-alias/package.json 2>/dev/null || echo "No package.json"

Repository: nitrojs/nitro

Length of output: 298


🏁 Script executed:

# Look at actual files in the example to see their names
find examples/import-alias -type f -name "*.ts" -o -name "*.js" | head -20

Repository: nitrojs/nitro

Length of output: 219


🏁 Script executed:

# Check other examples to see what import patterns they use (with or without .ts)
rg "from ['\"][~#]" examples/ -A 0 | head -30

Repository: nitrojs/nitro

Length of output: 954


🏁 Script executed:

# View the actual routes file to confirm the import pattern
cat examples/import-alias/server/routes/index.ts

Repository: nitrojs/nitro

Length of output: 295


🏁 Script executed:

# View the actual math.ts file
cat examples/import-alias/server/utils/math.ts

Repository: nitrojs/nitro

Length of output: 246


🏁 Script executed:

# Check if there are any docs or comments about import conventions in Nitro
find . -type f -name "*.md" -path "*/docs/*" | xargs grep -l "import\|alias" 2>/dev/null | head -5

Repository: nitrojs/nitro

Length of output: 203


🌐 Web query:

Nitro framework TypeScript import alias extension best practices .ts

💡 Result:

Nitro + TypeScript import aliases & file extensions (.ts) — best practices

  • Don’t add .ts to your imports (neither relative imports nor alias imports). You generally want specifiers that survive build output changes (TS → JS). This is the most common recommendation around TS path aliases as well. [2]

  • Use Nitro’s generated TypeScript config instead of hand-rolling resolution settings: create/keep a root tsconfig.json that extends Nitro’s generated config:

    { "extends": "./.nitro/types/tsconfig.json" }

    This ensures TS understands Nitro’s auto-imports/routes/types without you fighting module resolution. [1]

  • If you’re in Nuxt (Nitro under the hood): don’t override compilerOptions.paths directly. Nuxt recommends extending ./.nuxt/tsconfig.json and adding aliases via nuxt.config so they’re reflected in the generated tsconfig, and specifically warns against overwriting key options like moduleResolution. [3]

  • Be careful if you’re publishing a library consumed by Node ESM: TypeScript’s docs note that with certain setups (especially bundler-style builds), extensionless imports can leak into .d.ts and become invalid for Node ESM consumers that require explicit extensions. If you’re building a library (not just an app/server bundle), prefer settings/outputs that keep declarations Node-compatible. [4]

Rule of thumb: in a Nitro app/server, keep imports extensionless and let Nitro/Nuxt generate the tsconfig + aliases; avoid .ts in specifiers altogether. [1][3]

Sources: Nitro TypeScript guide [1], Nuxt tsconfig/alias guidance [3], TS alias/extensions advice [2], TypeScript module resolution guidance and declaration-file caveats [4].


Remove .ts extensions from alias imports to follow Nitro best practices.

Alias imports should be extensionless. Use ~server/utils/math instead of ~server/utils/math.ts — TypeScript module resolution handles this automatically, and extensionless imports are more portable as they survive transpilation from TS to JS.

Correct import pattern
import { sum } from "~server/utils/math";

import { rand } from "#server/utils/math";
🤖 Prompt for AI Agents
In `@examples/import-alias/GUIDE.md` around lines 5 - 8, Update the alias imports
to remove the .ts extensions: locate the import statements referencing
"~server/utils/math.ts" and "#server/utils/math.ts" (in the
server/routes/index.ts import lines) and change them to extensionless imports
("~server/utils/math" and "#server/utils/math") so TypeScript/Nitro module
resolution remains correct and the imports survive TS→JS transpilation.

@@ -0,0 +1,457 @@
---
category: server side rendering
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

Inconsistent category and description.

The frontmatter declares category: server side rendering (line 2), but the description states "Client-side routing with TanStack Router" (line 8). While TanStack Router provides client-side navigation, this example appears to focus on the SPA/CSR pattern rather than SSR. Please align the category and description to accurately reflect the example's purpose.

📝 Suggested fix

If this is primarily a client-side routing example:

 ---
-category: server side rendering
+category: routing
 icon: i-simple-icons-tanstack
 ---

Or if the description should emphasize SSR capabilities:

-> Client-side routing with TanStack Router in Nitro using Vite.
+> Server-side rendering with TanStack Router in Nitro using Vite.

Also applies to: 8-8

🤖 Prompt for AI Agents
In `@examples/vite-ssr-tsr-react/README.md` at line 2, The frontmatter "category:
server side rendering" conflicts with the README description "Client-side
routing with TanStack Router"; update either the frontmatter or the description
so they match – e.g., change the frontmatter to "category: client side
rendering" (or "SPA/CSR") if the example demonstrates client-side routing with
TanStack Router, or rewrite the description to emphasize SSR features if the
example is actually SSR-focused; locate and edit the README.md lines containing
the exact text "category: server side rendering" and the description
"Client-side routing with TanStack Router" to keep them consistent.

"nitro": "latest",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find and read the package.json in the mentioned directory
find . -path "*/examples/vite-ssr-tsr-react/package.json" -type f 2>/dev/null | head -5

Repository: nitrojs/nitro

Length of output: 99


🏁 Script executed:

#!/bin/bash
# Check latest Vite versions from npm registry
curl -s https://registry.npmjs.org/vite | jq -r '.["dist-tags"]' 2>/dev/null || echo "Failed to fetch from npm"

Repository: nitrojs/nitro

Length of output: 160


🏁 Script executed:

#!/bin/bash
# Also get the current version info if available
curl -s https://registry.npmjs.org/vite | jq -r '.version' 2>/dev/null || echo "Unable to fetch version"

Repository: nitrojs/nitro

Length of output: 61


🏁 Script executed:

cat ./examples/vite-ssr-tsr-react/package.json

Repository: nitrojs/nitro

Length of output: 545


Update Vite to a stable version in this example.

The package.json specifies "vite": "beta" (resolving to 8.0.0-beta.12), which is a pre-release version. Per the coding guidelines, examples should reflect best practices. Beta versions are unsuitable for example code as they lack stability guarantees and may diverge from production recommendations. Use the latest stable version 7.3.1 or a stable version constraint (e.g., ^7.3.1).

🤖 Prompt for AI Agents
In `@examples/vite-ssr-tsr-react/README.md` at line 48, The examples package.json
pins Vite to a pre-release ("vite": "beta"); update the dependency in the
examples/vite-ssr-tsr-react package.json by replacing the "vite": "beta" entry
with a stable version constraint (for example "vite": "^7.3.1" or the current
latest stable) so the example uses a supported production release instead of a
beta.

"baseUrl": ".",
"jsx": "react-jsx",
"paths": {
"@/*": ["sec/*"]
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 typo in tsconfig path mapping.

The path mapping contains a typo: "@/*": ["sec/*"] should be "@/*": ["src/*"]. This will cause module resolution to fail.

🔧 Proposed fix
     "paths": {
-      "@/*": ["sec/*"]
+      "@/*": ["src/*"]
     }
📝 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
"@/*": ["sec/*"]
"paths": {
"@/*": ["src/*"]
}
🤖 Prompt for AI Agents
In `@examples/vite-ssr-tsr-react/README.md` at line 60, The tsconfig path mapping
for the alias "@/*" is incorrect: it currently maps "@/*" to "sec/*" — update
the mapping so "@/*" maps to "src/*" instead (replace "sec/*" with "src/*") to
restore correct module resolution for the "@/*" imports; verify the change in
the tsconfig/paths section where the string "@/*": ["sec/*"] appears.

Comment on lines +122 to +126
<script server>
// Server-side Rendering
const { result } = await serverFetch("/trpc/get").then(r => r.json())
echo(result?.data?.value)
</script>
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for Nitro API usage and documentation

echo "=== Searching for serverFetch in codebase ==="
rg -n "serverFetch" --type md --type ts --type js -C 2

echo -e "\n=== Searching for echo function ==="
rg -n "echo\(" --type md --type ts --type js -C 2

echo -e "\n=== Searching for <script server> tag ==="
rg -n "script server" --type md --type html -C 2

echo -e "\n=== Checking GUIDE.md file structure ==="
wc -l examples/vite-trpc/GUIDE.md

Repository: nitrojs/nitro

Length of output: 15191


🌐 Web query:

Nitro serverFetch API documentation official

💡 Result:

Nitro’s official documentation for its server-side fetch API is the built-in $fetch helper (often what people mean when they say “Nitro serverFetch”):

  • Nitro docs (official): “Fetch” / $fetch (nitro.build)
  • Nuxt docs (Nitro “Direct API Calls” via $fetch) (nuxt.com)

Citations:


🌐 Web query:

Nitro echo function server-side rendering

💡 Result:

In Nitro (the server engine behind Nuxt), an “echo function” is just a normal server route that returns back parts of the incoming request (method, path, query, body). You create it with defineEventHandler in server/api/*. (nitro.build)

1) Echo endpoint (API route)

Create server/api/echo.post.ts:

import { defineEventHandler, getQuery, readBody } from "h3";

export default defineEventHandler(async (event) => {
  const query = getQuery(event);
  const body = await readBody(event).catch(() => undefined);

  return {
    path: event.path,
    method: event.method,
    query,
    body,
  };
});

This is standard H3/Nitro request handling, and it will respond at /api/echo (POST). (nitro.build)

2) Server-side rendering (SSR) in Nitro

Nitro does SSR via a renderer: a special catch-all handler that returns HTML for unmatched routes (i.e., your “app pages”). Configure renderer.handler in nitro.config.ts and implement renderer.ts. (v3.nitro.build)

If you tell me whether you mean Nuxt SSR (most common) or standalone Nitro SSR, I can tailor the exact folder layout + config you’re using.

Citations:


🌐 Web query:

Nitro "<script server>" tag island components

💡 Result:

Nitro (the UnJS server engine Nuxt uses) does not natively support a Vue SFC tag like <script server> for “island components”. In Nuxt, “islands” are implemented as server-only components rendered via <NuxtIsland> (no client JS downloaded), and are typically discovered from ~/components/islands/ (or by using server-only component conventions, depending on your Nuxt version/config). [1]

If what you’re looking for is SFC-embedded server code (a “server block”) alongside a page/component, that’s currently an experimental, third‑party approach: @hebilicious/server-block-nuxt adds a <server> block (explicitly not <script server> due to SFC behavior issues) and generates Nitro API handlers from it. [2]

Relevant docs/pages

  • Nuxt islands API: <NuxtIsland> (how Nuxt does islands) [1]
  • Experimental <server> SFC block module: @hebilicious/server-block-nuxt [2]
  • Background/read-through on Nuxt Islands (tutorial/article) [3]

Sources: [1] [2] [3]


Remove echo() function and <script server> tag—these are not supported Nitro APIs.

The example at lines 122–126 uses two unsupported features:

  • <script server>: Nitro does not natively support this tag syntax. Server-side code in Nitro should use standard API routes (via defineEventHandler in server/routes/* or server/api/*) or renderers defined in nitro.config.ts.
  • echo(): This is not a built-in Nitro function. There is no way to directly output values within an HTML template like this.

While serverFetch is a valid Nitro API, the overall approach contradicts best practices. Replace this pattern with a proper Nitro route handler or renderer that fetches the data and returns HTML, rather than relying on unsupported template directives.

🤖 Prompt for AI Agents
In `@examples/vite-trpc/GUIDE.md` around lines 122 - 126, Remove the unsupported
<script server> block and the non-existent echo() call; instead implement the
fetch logic in a Nitro server route or renderer (use defineEventHandler in a
file under server/routes or server/api) and have that handler call serverFetch
(or perform the same fetch) and return the data or rendered HTML; then consume
that route from the client (or have the renderer return HTML) rather than trying
to output inside a template with echo()—look for serverFetch and echo usages in
the snippet and move their logic into the new handler.

Comment on lines +109 to +124
```json [package.json]
{
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"devDependencies": {
"@trpc/client": "^11.8.1",
"@trpc/server": "^11.8.1",
"nitro": "latest",
"vite": "beta",
"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.

🛠️ Refactor suggestion | 🟠 Major

Use pinned versions instead of "latest" and "beta" for reproducibility.

The package.json specifies "nitro": "latest" and "vite": "beta", which prevents reproducible builds and may introduce instability. As per coding guidelines, examples should reflect best practices.

Consider using specific version numbers or semantic version ranges:

"nitro": "^2.9.0",
"vite": "^6.0.0"

As per coding guidelines, examples in examples/ should reflect best practices. Using pinned or semantic versions ensures users can reliably reproduce the example.

🤖 Prompt for AI Agents
In `@examples/vite-trpc/README.md` around lines 109 - 124, Update the example
package.json devDependencies to avoid non-reproducible tags: replace the
"nitro": "latest" and "vite": "beta" entries with pinned or semver-ranged
versions (e.g., use specific versions or caret ranges) so installs are
reproducible; modify the devDependencies object in the package.json (the entries
for "nitro" and "vite") to concrete version strings per project guidelines.

Copy link
Member

@pi0 pi0 left a comment

Choose a reason for hiding this comment

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

🚀

@pi0 pi0 merged commit 8d92160 into nitrojs:main Feb 5, 2026
7 of 8 checks passed
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