Conversation
|
@RihanArfan is attempting to deploy a commit to the Nitro Team on Vercel. A member of the Team first needs to authorize it. |
📝 WalkthroughWalkthroughThis 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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
commit: |
There was a problem hiding this comment.
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(orbash) 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 configurationexamples/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 configurationexamples/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 stylesexamples/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 pointexamples/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.tsextensions from import paths.The import statements include
.tsfile extensions (lines 27-28), which is non-standard for TypeScript. TypeScript module resolution automatically locates.tsfiles without explicit extensions. Update the imports to use extensionless paths:import { sum } from "~server/utils/math"andimport { 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; usetext/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
hrefandhtmlTemplatewithout 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: Normalizeroute.pathbefore 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 andexampleName.🛠️ 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 missingrepoto avoid broken embeds.
propsare optional, buturlalways returns a string; ifsrcandrepoare both absent you’ll build a.../undefined/...URL and the loading state will never render. Consider returningundefinedwhen 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 usesvite.config.mjsinstead 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(orbashif 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
textas 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 indevDependenciesto minimize runtime surface.Based on learnings: Avoid introducing new dependencies unless strictly necessary—add them to `devDependencies` unless required in runtime logic.💡 Suggested fix
- "dependencies": { - "zod": "^4.3.6" - }, "devDependencies": { "shaders": "^2.2.43", - "undocs": "^0.4.15" + "undocs": "^0.4.15", + "zod": "^4.3.6" }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.githubandappConfig.docs.branch. Apply the same pattern to the StackBlitz URL and Sandbox component for consistency across forks and non-mainbranches.♻️ 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" />
| ```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") | ||
| } | ||
| ); |
There was a problem hiding this comment.
🛠️ 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 -->
| export default defineErrorHandler((error, _event) => { | ||
| return new Response(`Custom Error Handler: ${error.message}`, { | ||
| status: 500, | ||
| headers: { "Content-Type": "text/plain" }, | ||
| }); | ||
| }); |
There was a problem hiding this comment.
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.
| 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.
| ```ts [server/middleware/auth.ts] | ||
| import { defineMiddleware } from "nitro/h3"; | ||
|
|
||
| export default defineMiddleware((event) => { | ||
| event.context.auth = { | ||
| name: "User " + Math.round(Math.random() * 100) | ||
| }; | ||
| }); | ||
| ``` |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for defineMiddleware in the codebase
rg "defineMiddleware" -i --type ts --type js -C 3Repository: 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 -30Repository: 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/nullRepository: 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 usesdefineHandlerfromnitro/h3)—middleware should generally not return a value. (v3.nitro.build) - Nitro (stable docs): same idea under
server/middleware/, typically usingdefineEventHandler. (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:
- 1: https://v3.nitro.build/docs/routing?utm_source=openai
- 2: https://nitro.build/guide/routing?utm_source=openai
- 3: https://h3.dev/guide/basics/middleware?utm_source=openai
- 4: https://vinxi.vercel.app/api/server/response.html?utm_source=openai
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.
There was a problem hiding this comment.
defineMiddleware is a valid export
There was a problem hiding this comment.
🧩 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...
| 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" } } | ||
| ); | ||
| } |
There was a problem hiding this comment.
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, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''');
+}
+
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.
| ```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" }, | ||
| }); | ||
| }, | ||
| }; | ||
| ``` |
There was a problem hiding this comment.
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.
| ```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).
|
undocs merged to main we can try with nightly. |
There was a problem hiding this comment.
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(orplain) 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.tsxexamples/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.tsexamples/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.tsexamples/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.mjsexamples/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.tsexamples/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: Addrel="noopener noreferrer"to external links withtarget="_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
| 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', |
There was a problem hiding this comment.
🧩 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 -20Repository: nitrojs/nitro
Length of output: 206
🏁 Script executed:
cat -n ./docs/.docs/pages/examples/[...slug].vueRepository: nitrojs/nitro
Length of output: 4173
🏁 Script executed:
find . -name "nuxt.config.*" -type f | head -5Repository: nitrojs/nitro
Length of output: 39
🏁 Script executed:
fd -t f "nuxt" docs/ | head -20Repository: nitrojs/nitro
Length of output: 39
🏁 Script executed:
fd -t f -e config "nuxt" . | head -20Repository: nitrojs/nitro
Length of output: 39
🏁 Script executed:
ls -la docs/.docs/ | head -30Repository: nitrojs/nitro
Length of output: 510
🏁 Script executed:
cat docs/.docs/content.config.tsRepository: 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 2Repository: nitrojs/nitro
Length of output: 39
🏁 Script executed:
rg "trailing" docs/ -iRepository: nitrojs/nitro
Length of output: 715
🏁 Script executed:
rg "useAsyncData" docs/.docs/pages/ -A 3 -B 1Repository: 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.tsRepository: 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`.
| // 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' }) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "[...slug].md.get.ts" -o -name "*.md.get.ts" | head -20Repository: nitrojs/nitro
Length of output: 116
🏁 Script executed:
git ls-files | grep -E "(routes|examples)" | head -30Repository: 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 -60Repository: 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 2Repository: 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 2Repository: 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 -20Repository: 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 10Repository: 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 jsonRepository: 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 2Repository: 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 1Repository: 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 -20Repository: 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 -100Repository: nitrojs/nitro
Length of output: 39
🏁 Script executed:
# Check root package.json for dependencies
cat package.json | grep -A 100 "dependencies" | head -50Repository: nitrojs/nitro
Length of output: 1599
🏁 Script executed:
# Look for any tests or examples related to this specific route
fd "slug" docs/ --type fRepository: 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 -5Repository: nitrojs/nitro
Length of output: 191
🏁 Script executed:
# Check if there are any TypeScript type definitions in the project
fd "*.d.ts" docs/ | head -10Repository: 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 2Repository: 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 1Repository: 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 5Repository: 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 -5Repository: 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 -20Repository: 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 -50Repository: 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/nullRepository: 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 tsRepository: nitrojs/nitro
Length of output: 39
🏁 Script executed:
# Check if Nuxt Content is listed as a dependency anywhere
rg "@nuxt/content" --type jsonRepository: 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 -5Repository: 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 2Repository: nitrojs/nitro
Length of output: 39
🏁 Script executed:
# Search for how unshift is used in the codebase
rg "unshift" --type ts -B 2 -A 2Repository: 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 -5Repository: 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.jsonRepository: 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 -100Repository: 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 -50Repository: 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 tsRepository: nitrojs/nitro
Length of output: 105
🏁 Script executed:
cat -n docs/.docs/content.config.tsRepository: 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 -10Repository: 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 -5Repository: 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 tsRepository: 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 -20Repository: 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.
| // 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.
examples/database/README.md
Outdated
| <!-- 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 --> |
There was a problem hiding this comment.
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.
examples/database/README.md
Outdated
| <!-- 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 --> |
There was a problem hiding this comment.
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:
- Renaming this to
reset.tsorseed.tsto clarify it's a destructive reset operation - 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.
| export default () => ( | ||
| <html> | ||
| <h1>Nitro + mongo-jsx works!</h1> | ||
| </html> |
There was a problem hiding this comment.
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.
| 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"); |
There was a problem hiding this comment.
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.
| 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).
| ``` | ||
| ├── api/ | ||
| │ └── highlight.ts | ||
| ├── index.html | ||
| ├── nitro.config.ts | ||
| ├── package.json | ||
| ├── README.md | ||
| ├── styles.css | ||
| ├── tsconfig.json | ||
| └── vite.config.ts | ||
| ``` |
There was a problem hiding this comment.
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.
| ``` | |
| ├── 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"; |
There was a problem hiding this comment.
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.
| 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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
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@latestwith nightly builds may impact reproducibility, but is likely intentional for development.The
undocs-nightly@latestdependency 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 thedevDependenciesclarifying 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.
There was a problem hiding this comment.
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
counterimplies 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.Based on learnings: "Applies to examples/**/* : Add examples in `examples/` for new integrations and ensure they reflect best practices".✏️ 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.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, andlatestcan 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: Addrel="noreferrer"for externaltarget="_blank"links.This avoids reverse‑tabnabbing and is a best‑practice for examples.
As per coding guidelines: “Add examples inexamples/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">
| ```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, | ||
| }; | ||
| }); | ||
| ``` |
There was a problem hiding this comment.
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.
| ```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".
| ```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" }, | ||
| }); | ||
| }; |
There was a problem hiding this comment.
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",
});🤖 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.
| <script server> | ||
| const hl = (code) => | ||
| serverFetch("/api/highlight", { | ||
| method: "POST", | ||
| body: code, | ||
| }); | ||
| </script> | ||
| <pre><code>{{{ hl(`console.log("💚 Simple is beautiful!");`) }}}</code></pre> | ||
| </div> |
There was a problem hiding this comment.
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.
| <form action={updateServerCounter.bind(null, 1)}> | ||
| <button>Server Counter: {getServerCounter()}</button> |
There was a problem hiding this comment.
🛠️ 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 -20Repository: nitrojs/nitro
Length of output: 441
🏁 Script executed:
# Also search for getServerCounter definition
rg "getServerCounter" --type ts --type tsx --type js --type jsx -A 5Repository: 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
awaitasync work during render only in a Server Component. (e.g.,fetch, DB/ORM, filesystem). In frameworks like Next.js App Router, make the Server Componentasyncandawaitinside 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 20Repository: 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 -50Repository: nitrojs/nitro
Length of output: 1553
🏁 Script executed:
# Also check if there's source code files
rg "function getServerCounter|const getServerCounter" -A 5Repository: 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.
| <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.
| ```tsx [src/entry-client.tsx] | ||
| import { hydrate } from "solid-js/web"; | ||
| import "./styles.css"; | ||
| import { App } from "./app.jsx"; | ||
|
|
||
| hydrate(() => <App />, document.querySelector("#app")!); | ||
| ``` |
There was a problem hiding this comment.
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.
| ```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> | ||
| ); | ||
| } | ||
| ``` |
There was a problem hiding this comment.
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.
examples/vite-ssr-solid/README.md
Outdated
| ```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> | ||
| ); | ||
| } | ||
| ``` | ||
|
|
There was a problem hiding this comment.
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.
examples/vite-ssr-solid/README.md
Outdated
| ```tsx [entry-client.tsx] | ||
| import { hydrate } from "solid-js/web"; | ||
| import "./styles.css"; | ||
| import { App } from "./app.jsx"; | ||
|
|
||
| hydrate(() => <App />, document.querySelector("#app")!); | ||
| ``` |
There was a problem hiding this comment.
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.
| "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" | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 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 -40Repository: 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 -50Repository: 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.mdRepository: 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 3Repository: 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.mdRepository: 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 -10Repository: 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 3Repository: 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.
There was a problem hiding this comment.
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
anytypes 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-ignorewith@ts-expect-errorfor 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-errorinstead of@ts-ignore. This provides better safety because@ts-expect-errorwill fail if the error is fixed or if no error exists, while@ts-ignoresuppresses 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 |
There was a problem hiding this comment.
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.
| /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", |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
fd "package.json" examples/vite-ssr-tss-react/ --type fRepository: nitrojs/nitro
Length of output: 97
🏁 Script executed:
cat -n examples/vite-ssr-tss-react/package.jsonRepository: 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 -100Repository: 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.
There was a problem hiding this comment.
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.tsfile 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.tscode 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
#appexists. 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
.envwhich encourages committing secrets, conflicting with the warning text and best practices for examples. Suggest keeping.envignored 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-keyAs 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.jsonimports, andtsconfig.jsonpaths (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 standardutf-8charset value.
utf8is non‑standard; HTML expectsutf-8.As per coding guidelines: "examples/**/*: Add examples in `examples/` for new integrations and ensure they reflect best practices".✅ Suggested tweak
- { charSet: "utf8" }, + { charSet: "utf-8" },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.,
HTTPErrorat 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") } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's locate and read the file
find . -path "*examples/cached-handler/GUIDE.md" -type fRepository: 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=mdRepository: 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 -50Repository: 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 -30Repository: nitrojs/nitro
Length of output: 1338
🏁 Script executed:
# Read the GUIDE.md file to see the actual content
cat -n examples/cached-handler/GUIDE.mdRepository: 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 5Repository: 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 20Repository: 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=jsRepository: nitrojs/nitro
Length of output: 166
🏁 Script executed:
# Check what HTTPEvent type is
rg -n "type HTTPEvent\|interface HTTPEvent" src/types/ -B 2 -A 10Repository: 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.tsRepository: 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 3Repository: nitrojs/nitro
Length of output: 2008
🏁 Script executed:
# Search for how shouldBypassCache is documented or typed
rg -n "shouldBypassCache" docs/ -B 2 -A 5Repository: 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=tsRepository: 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 20Repository: 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 2Repository: nitrojs/nitro
Length of output: 39
🏁 Script executed:
# Look at h3 exports more broadly
rg -n "export" src/runtime/h3.ts | head -30Repository: nitrojs/nitro
Length of output: 114
🏁 Script executed:
# Check the actual h3.ts file
cat src/runtime/h3.ts | head -80Repository: 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.tsRepository: 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.tsRepository: nitrojs/nitro
Length of output: 201
🏁 Script executed:
# Check h3 package and its exports
rg -n "getQuery\|parseQuery" --type=ts | head -20Repository: nitrojs/nitro
Length of output: 39
🏁 Script executed:
# Look at the actual code usage in test fixture
cat test/fixture/server/routes/file.tsRepository: nitrojs/nitro
Length of output: 391
🏁 Script executed:
# Check imports in the example file and related files
rg -n "import" examples/cached-handler/ --type=tsRepository: 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 2Repository: nitrojs/nitro
Length of output: 212
🏁 Script executed:
# Look for URL parsing utilities in nitro
rg -n "parseURL\|useQuery\|getQuery" src/ --type=ts | head -20Repository: 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.tsRepository: 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 1Repository: nitrojs/nitro
Length of output: 39
🏁 Script executed:
# Check the type of H3Event to see what properties are available
fd "h3" src/types/ --type=fRepository: 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 -40Repository: nitrojs/nitro
Length of output: 39
🏁 Script executed:
# Verify the event object structure in H3Event
cat src/types/h3.ts | head -150Repository: 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 2Repository: 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}")
EOFRepository: 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.tsRepository: 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.tsRepository: 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 10Repository: 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 1Repository: nitrojs/nitro
Length of output: 212
🏁 Script executed:
# Check HTTPEvent definition from h3 types
rg -n "HTTPEvent" src/types/ -A 5Repository: 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=tsRepository: 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 2Repository: 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 1Repository: 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 2Repository: 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 -20Repository: 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=tsRepository: 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")
EOFRepository: 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=tsRepository: nitrojs/nitro
Length of output: 242
🏁 Script executed:
# Check the complete CachedEventHandlerOptions to understand the function signature better
cat src/types/runtime/cache.tsRepository: 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 1Repository: 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 5Repository: 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")
EOFRepository: 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 -20Repository: 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=tsRepository: 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")
EOFRepository: 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.
| ```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" }, | ||
| }); | ||
| }); | ||
| ``` |
There was a problem hiding this comment.
🧩 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 usesdefineNitroErrorHandler((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 2Repository: 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 -5Repository: 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.
| ```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!", | ||
| }; | ||
| }, | ||
| }); | ||
| ``` |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Suggest restructuring: present migrations before request handlers.
The guide currently presents examples in an order that contradicts best practices:
- First example (lines 5-27): shows schema changes in a request handler
- 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:
- First: Present the migration task example (current lines 31-57)
- 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.
| 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)`; |
There was a problem hiding this comment.
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.
| ```ts [server/routes/index.ts] | ||
| import { sum } from "~server/utils/math.ts"; | ||
|
|
||
| import { rand } from "#server/utils/math.ts"; |
There was a problem hiding this comment.
🧩 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 -20Repository: 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 -100Repository: nitrojs/nitro
Length of output: 84
🏁 Script executed:
# Check the example file itself
cat examples/import-alias/GUIDE.mdRepository: 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 -10Repository: nitrojs/nitro
Length of output: 123
🏁 Script executed:
# Check the import-alias example configuration
cat examples/import-alias/tsconfig.jsonRepository: 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 -20Repository: 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 -30Repository: 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.tsRepository: nitrojs/nitro
Length of output: 295
🏁 Script executed:
# View the actual math.ts file
cat examples/import-alias/server/utils/math.tsRepository: 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 -5Repository: 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
.tsto 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.jsonthat 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.pathsdirectly. Nuxt recommends extending./.nuxt/tsconfig.jsonand adding aliases vianuxt.configso they’re reflected in the generated tsconfig, and specifically warns against overwriting key options likemoduleResolution. [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.tsand 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 | |||
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
🧩 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 -5Repository: 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.jsonRepository: 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/*"] |
There was a problem hiding this comment.
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.
| "@/*": ["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.
| <script server> | ||
| // Server-side Rendering | ||
| const { result } = await serverFetch("/trpc/get").then(r => r.json()) | ||
| echo(result?.data?.value) | ||
| </script> |
There was a problem hiding this comment.
🧩 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.mdRepository: 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:
- 1: https://nitro.build/guide/fetch?utm_source=openai
- 2: https://nuxt.com/docs/3.x/guide/concepts/server-engine?utm_source=openai
🌐 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:
- 1: https://nitro.build/guide/routing?utm_source=openai
- 2: https://nitro.build/guide/routing?utm_source=openai
- 3: https://v3.nitro.build/docs/renderer?utm_source=openai
🌐 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 (viadefineEventHandlerinserver/routes/*orserver/api/*) or renderers defined innitro.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.
| ```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" | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ 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.
❓ Type of change
📚 Description
Adds examples section to the docs
📝 Checklist