Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions docs/.docs/components/Sandbox.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<script setup lang="ts">
const props = defineProps<{
src?: string
repo?: string
branch?: string
dir?: string
file?: string
}>()

const colorMode = useColorMode()

const url = computed(() => {
if (props.src) {
return props.src
}
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()}`
})
</script>

<template>
<div class="w-full min-h-[500px] mx-auto overflow-hidden rounded-md mt-4 border border-default">
<iframe
v-if="url"
:src="url"
title="StackBlitz Sandbox"
sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"
class="w-full h-full min-h-[600px] overflow-hidden bg-gray-100 dark:bg-gray-800"
/>
<div v-else class="flex items-center justify-center h-[600px] text-muted">
Loading Sandbox...
</div>
</div>
</template>
20 changes: 20 additions & 0 deletions docs/.docs/content.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { defineContentConfig, defineCollection, z } from '@nuxt/content'
import { resolve } from 'pathe'

export default defineContentConfig({
collections: {
examples: defineCollection({
type: 'page',
source: {
cwd: resolve(__dirname, '../../examples'),
include: '**/README.md',
prefix: '/examples',
exclude: ['**/.**/**', '**/node_modules/**', '**/dist/**', '**/.docs/**'],
},
schema: z.object({
category: z.string().optional(),
icon: z.string().optional(),
}),
}),
},
})
85 changes: 85 additions & 0 deletions docs/.docs/layouts/examples.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<script setup lang="ts">
import type { ContentNavigationItem } from '@nuxt/content'
import { categoryOrder } from '~/utils/examples'

// Fetch all examples and group by category
const { data: examples } = await useAsyncData('examples-nav', () =>
queryCollection('examples')
.select('title', 'description', 'category', 'path')
.all(),
)

// Group examples by category
const groupedExamples = computed(() => {
if (!examples.value) return []

const groups: Record<string, ContentNavigationItem[]> = {}

for (const example of examples.value) {
const category = example.category || 'Other'
if (!groups[category]) {
groups[category] = []
}
groups[category].push({
title: example.title,
path: example.path.replace(/\/readme$/i, ''),
})
}

// Convert to navigation items with children, sorted by categoryOrder
const sortedEntries = Object.entries(groups).sort(([a], [b]) => {
const aIndex = categoryOrder.indexOf(a.toLowerCase())
const bIndex = categoryOrder.indexOf(b.toLowerCase())
if (aIndex === -1 && bIndex === -1) return a.localeCompare(b)
if (aIndex === -1) return 1
if (bIndex === -1) return -1
return aIndex - bIndex
})

return sortedEntries.map(([category, items]) => ({
title: category.split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' '),
path: '',
children: items,
}))
})

// Flat list for navigation (no groups)
const flatExamples = computed(() => {
if (!examples.value) return []
return examples.value.map((example) => ({
title: example.title,
path: example.path.replace(/\/readme$/i, ''),
}))
})
</script>

<template>
<UContainer>
<UPage :ui="{ left: 'lg:col-span-2 pr-2 border-r border-default' }">
<template #left>
<UPageAside>
<UPageAnchors
:links="[
{ label: 'Docs', icon: 'i-lucide-book-open', to: '/docs' },
{ label: 'Deploy', icon: 'ri:upload-cloud-2-line', to: '/deploy' },
{ label: 'Config', icon: 'ri:settings-3-line', to: '/config' },
{ label: 'Examples', icon: 'i-lucide-folder-code', to: '/examples', active: true },
]"
/>
<USeparator type="dashed" class="py-6" />
<UContentNavigation
v-if="groupedExamples.length"
:navigation="groupedExamples"
:collapsible="false"
/>
<UContentNavigation
v-else-if="flatExamples.length"
:navigation="flatExamples"
:collapsible="false"
/>
</UPageAside>
</template>
<slot />
</UPage>
</UContainer>
</template>
122 changes: 122 additions & 0 deletions docs/.docs/pages/examples/[...slug].vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<script setup lang="ts">
import { joinURL } from 'ufo'
import { kebabCase } from 'scule'

definePageMeta({
layout: 'examples',
})

const appConfig = useAppConfig()
const route = useRoute()

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',
Comment on lines +12 to +53
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟑 Minor

🧩 Analysis chain

🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 206


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 4173


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 510


🏁 Script executed:

cat docs/.docs/content.config.ts

Repository: nitrojs/nitro

Length of output: 582


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

rg "trailing" docs/ -i

Repository: nitrojs/nitro

Length of output: 715


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 1571


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

cat docs/.docs/content.config.ts

Repository: nitrojs/nitro

Length of output: 582


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

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

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

},
],
})
</script>

<template>
<UPage v-if="page">
<UPageHeader
:title="page.title"
:description="page.description"
:ui="{
wrapper: 'flex-row items-center flex-wrap justify-between',
}"
>
<template #headline>
<UBreadcrumb :items="breadcrumb" />
</template>
<template #links>
<UButton
icon="i-simple-icons-stackblitz"
label="Open in Playground"
color="neutral"
variant="soft"
size="sm"
:to="`https://stackblitz.com/fork/github/${appConfig.docs.github}/tree/${appConfig.docs.branch || 'main'}/examples/${exampleName}`"
target="_blank"
/>

<UButton
icon="i-simple-icons-github"
label="Source"
color="neutral"
variant="soft"
size="sm"
:to="`https://github.com/${appConfig.docs.github}/tree/${appConfig.docs.branch || 'main'}/examples/${exampleName}`"
target="_blank"
/>

<PageHeaderLinks />
</template>
</UPageHeader>

<template v-if="page.body?.toc?.links?.length" #right>
<UContentToc title="On this page" :links="page.body?.toc?.links || []" highlight />
</template>

<UPageBody prose class="break-words">
<ContentRenderer v-if="page.body" :value="page" />

<div class="space-y-6">
<USeparator type="dashed" />
<div class="mb-4">
<UPageLinks
class="inline-block"
:links="[
{
icon: 'i-lucide-pencil',
label: 'Edit this page',
to: `https://github.com/${appConfig.docs.github}/edit/${appConfig.docs.branch || 'main'}/examples/${exampleName}/README.md`,
target: '_blank',
},
]"
/>
</div>
<UContentSurround v-if="surround?.length" class="mb-4" :surround="surround" />
</div>
</UPageBody>
</UPage>
</template>
96 changes: 96 additions & 0 deletions docs/.docs/pages/examples/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<script setup lang="ts">
import { categoryOrder, categoryIcons } from '~/utils/examples'

definePageMeta({
layout: 'examples',
})

const appConfig = useAppConfig()

// Fetch all examples
const { data: examples } = await useAsyncData('examples-list', () =>
queryCollection('examples')
.select('title', 'description', 'category', 'path', 'icon')
.all(),
)

// Group examples by category
const groupedExamples = computed(() => {
if (!examples.value) return {}

const groups: Record<string, typeof examples.value> = {}

for (const example of examples.value) {
const category = example.category || 'Other'
if (!groups[category]) {
groups[category] = []
}
groups[category].push(example)
}

// Sort groups by categoryOrder
const sortedEntries = Object.entries(groups).sort(([a], [b]) => {
const aIndex = categoryOrder.indexOf(a.toLowerCase())
const bIndex = categoryOrder.indexOf(b.toLowerCase())
if (aIndex === -1 && bIndex === -1) return a.localeCompare(b)
if (aIndex === -1) return 1
if (bIndex === -1) return -1
return aIndex - bIndex
})

return Object.fromEntries(sortedEntries)
})

usePageSEO({
title: `Examples - ${appConfig.site.name}`,
ogTitle: 'Examples',
description: 'Explore Nitro examples to learn how to build full-stack applications',
})
</script>

<template>
<UPage>
<UPageHeader
title="Examples"
description="Explore Nitro examples to learn how to build full-stack applications with different frameworks and features."
>
<template #headline>
<UBreadcrumb :items="[{ label: 'Examples', icon: 'i-lucide-code' }]" />
</template>
</UPageHeader>

<UPageBody>
<UAlert
color="warning"
variant="subtle"
icon="i-lucide-triangle-alert"
title="Work in Progress"
description="Nitro v3 Alpha docs and examples are a work in progress β€” expect updates, rough edges, and occasional inaccuracies."
class="mb-8"
/>

<div v-for="(categoryExamples, category) in groupedExamples" :key="category" class="mb-12">
<h2 class="text-xl font-semibold mb-4 flex items-center gap-2">
<UIcon :name="categoryIcons[String(category).toLowerCase()] || categoryIcons.other" class="size-5" />
{{ String(category).charAt(0).toUpperCase() + String(category).slice(1) }}
</h2>

<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<UPageCard
v-for="example in categoryExamples"
:key="example.path"
:to="example.path.replace(/\/readme$/i, '')"
:title="example.title"
:description="example.description"
:icon="example.icon"
/>
</div>
</div>

<div v-if="!examples?.length" class="text-center py-12">
<UIcon name="i-lucide-book-dashed" class="size-12 text-muted mx-auto mb-4" />
<p class="text-muted">No examples</p>
</div>
</UPageBody>
</UPage>
</template>
28 changes: 28 additions & 0 deletions docs/.docs/server/routes/raw/examples/[...slug].md.get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { queryCollection } from '@nuxt/content/server'
import { stringify } from 'minimark/stringify'
import { withLeadingSlash } from 'ufo'

export default eventHandler(async (event) => {
const slug = getRouterParams(event)['slug.md']
if (!slug?.endsWith('.md')) {
throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
}

// Convert /raw/examples/hello-world.md -> /examples/hello-world/readme
const exampleName = slug.replace('.md', '')
const path = withLeadingSlash(`examples/${exampleName}/readme`)

const page = await queryCollection(event, 'examples').path(path).first()
if (!page) {
throw createError({ statusCode: 404, statusMessage: 'Example not found', fatal: true })
}

// 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' })
Comment on lines +20 to +27
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟑 Minor

🧩 Analysis chain

🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 116


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 1149


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 1374


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 1457


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 114


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 292


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 1599


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 191


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 342


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 1021


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 274


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 1723


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 99


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 1183


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 1123


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 1178


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 39


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 4504


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 76


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 205


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 4698


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 2223


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 105


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 715


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 74


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 742


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 532


🏁 Script executed:

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

Repository: nitrojs/nitro

Length of output: 84


Add guards for optional page properties before injection.

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

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

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

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

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

})
Loading
Loading