Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
6 changes: 1 addition & 5 deletions app/components/StarterCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@ const props = defineProps({
})

const template = computed(() => {
return props.starter.repo === 'nuxt/starter'
? (props.starter.branch === 'v4')
? ''
: `-- -t ${props.starter.branch}`
: `-- -t "${props.starter.repo}#${props.starter.branch}"`
return props.starter.default ? '' : `-- -t ${props.starter.slug}`
})

const command = computed(() => {
Expand Down
3 changes: 2 additions & 1 deletion nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ export default defineNuxtConfig({
routeRules: {
'/themes': { redirect: 'https://nuxt.com/templates' },
'/templates': { redirect: 'https://nuxt.com/templates' },
'/data/starters.json': { isr: 3600 },
},
compatibilityDate: '2025-07-28',
nitro: {
prerender: {
routes: ['/', '/data/starters.json'],
routes: ['/'],
},
},
vite: {
Expand Down
72 changes: 24 additions & 48 deletions server/routes/data/starters.json.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,24 @@
export default defineEventHandler((): Starter[] => [
{
name: 'Nuxt 4',
slug: 'v4',
description: 'Minimal starter with a single app.vue.',
image: 'data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' width=\'34\' height=\'34\' viewBox=\'0 0 34 34\' fill=\'none\'%3E%3Cpath d=\'M19.0778 28.3333H31.6956C32.0993 28.3364 32.4968 28.2333 32.8482 28.0344C33.1996 27.8355 33.4926 27.5478 33.6978 27.2C33.8967 26.8554 34.0015 26.4645 34.0015 26.0667C34.0015 25.6688 33.8967 25.2779 33.6978 24.9333L25.1978 10.3511C24.9987 10.0063 24.7124 9.72003 24.3675 9.52108C24.0226 9.32213 23.6315 9.21751 23.2333 9.21777C22.8296 9.2147 22.4321 9.31778 22.0807 9.51669C21.7293 9.71559 21.4363 10.0033 21.2311 10.3511L19.0778 14.0911L14.8467 6.79999C14.6445 6.44959 14.3521 6.1597 14 5.96041C13.648 5.76112 13.249 5.65969 12.8445 5.66666C12.4471 5.67016 12.0574 5.77635 11.7132 5.97494C11.369 6.17352 11.0819 6.45774 10.88 6.79999L0.302233 24.9333C0.153235 25.1913 0.056538 25.476 0.0176701 25.7714C-0.0211977 26.0667 -0.00147386 26.3668 0.075714 26.6545C0.152902 26.9422 0.28604 27.2118 0.467516 27.4481C0.648992 27.6843 0.875247 27.8824 1.13334 28.0311C1.47334 28.22 1.8889 28.3333 2.30446 28.3333H10.2378C13.3733 28.3333 15.6778 26.9733 17.2645 24.2911L21.1556 17.6422L23.2333 14.0911L29.4667 24.7822H21.1556L19.0778 28.3333ZM10.0867 24.7822H4.57112L12.8445 10.54L17 17.6422L14.2045 22.4022C13.1467 24.14 11.9756 24.7822 10.0867 24.7822Z\' fill=\'%2300DC82\'/%3E%3C/svg%3E',
repo: 'nuxt/starter',
branch: 'v4',
docs: 'https://nuxt.com/docs/4.x/getting-started/installation',
},
{
name: 'Nuxt 3',
deprecated: true,
slug: 'v3',
description: 'Minimal starter with a single app.vue.',
image: 'data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' width=\'34\' height=\'34\' viewBox=\'0 0 34 34\' fill=\'none\'%3E%3Cpath d=\'M19.0778 28.3333H31.6956C32.0993 28.3364 32.4968 28.2333 32.8482 28.0344C33.1996 27.8355 33.4926 27.5478 33.6978 27.2C33.8967 26.8554 34.0015 26.4645 34.0015 26.0667C34.0015 25.6688 33.8967 25.2779 33.6978 24.9333L25.1978 10.3511C24.9987 10.0063 24.7124 9.72003 24.3675 9.52108C24.0226 9.32213 23.6315 9.21751 23.2333 9.21777C22.8296 9.2147 22.4321 9.31778 22.0807 9.51669C21.7293 9.71559 21.4363 10.0033 21.2311 10.3511L19.0778 14.0911L14.8467 6.79999C14.6445 6.44959 14.3521 6.1597 14 5.96041C13.648 5.76112 13.249 5.65969 12.8445 5.66666C12.4471 5.67016 12.0574 5.77635 11.7132 5.97494C11.369 6.17352 11.0819 6.45774 10.88 6.79999L0.302233 24.9333C0.153235 25.1913 0.056538 25.476 0.0176701 25.7714C-0.0211977 26.0667 -0.00147386 26.3668 0.075714 26.6545C0.152902 26.9422 0.28604 27.2118 0.467516 27.4481C0.648992 27.6843 0.875247 27.8824 1.13334 28.0311C1.47334 28.22 1.8889 28.3333 2.30446 28.3333H10.2378C13.3733 28.3333 15.6778 26.9733 17.2645 24.2911L21.1556 17.6422L23.2333 14.0911L29.4667 24.7822H21.1556L19.0778 28.3333ZM10.0867 24.7822H4.57112L12.8445 10.54L17 17.6422L14.2045 22.4022C13.1467 24.14 11.9756 24.7822 10.0867 24.7822Z\' fill=\'%2300DC82\'/%3E%3C/svg%3E',
repo: 'nuxt/starter',
branch: 'v3',
docs: 'https://nuxt.com/docs/3.x/getting-started/installation',
},
{
name: 'UI',
slug: 'ui',
description: 'Starter with Nuxt UI.',
image: 'data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' width=\'32\' height=\'32\' viewBox=\'0 0 20 20\'%3E%3Cpath fill=\'%2300DC82\' fill-rule=\'evenodd\' d=\'M10 2.5c-1.31 0-2.526.386-3.546 1.051a.75.75 0 0 1-.82-1.256A8 8 0 0 1 18 9a22.47 22.47 0 0 1-1.228 7.351a.75.75 0 1 1-1.417-.49A20.97 20.97 0 0 0 16.5 9A6.5 6.5 0 0 0 10 2.5ZM4.333 4.416a.75.75 0 0 1 .218 1.038A6.466 6.466 0 0 0 3.5 9a7.966 7.966 0 0 1-1.293 4.362a.75.75 0 0 1-1.257-.819A6.466 6.466 0 0 0 2 9c0-1.61.476-3.11 1.295-4.365a.75.75 0 0 1 1.038-.219ZM10 6.12a3 3 0 0 0-3.001 3.041a11.455 11.455 0 0 1-2.697 7.24a.75.75 0 0 1-1.148-.965A9.957 9.957 0 0 0 5.5 9c0-.028.002-.055.004-.082a4.5 4.5 0 0 1 8.996.084v.148l-.005.297a.75.75 0 1 1-1.5-.034c.003-.11.004-.219.005-.328a3 3 0 0 0-3-2.965Zm0 2.13a.75.75 0 0 1 .75.75c0 3.51-1.187 6.745-3.181 9.323a.75.75 0 1 1-1.186-.918A13.687 13.687 0 0 0 9.25 9a.75.75 0 0 1 .75-.75Zm3.529 3.698a.75.75 0 0 1 .584.885a18.883 18.883 0 0 1-2.257 5.84a.75.75 0 1 1-1.29-.764a17.386 17.386 0 0 0 2.078-5.377a.75.75 0 0 1 .885-.584Z\' clip-rule=\'evenodd\'/%3E%3C/svg%3E',
repo: 'nuxt/starter',
branch: 'ui',
docs: 'https://ui.nuxt.com',
},
{
name: 'Content',
slug: 'content',
description: 'Starter for a content-driven website.',
image: 'data:image/svg+xml,%3Csvg width=\'34\' height=\'34\' fill=\'none\' xmlns=\'http://www.w3.org/2000/svg\'%3E%3Cpath d=\'M12 14h10a1.667 1.667 0 0 0 1.667-1.667v-10A1.667 1.667 0 0 0 22 .667H12a1.667 1.667 0 0 0-1.667 1.666v10A1.667 1.667 0 0 0 12 14Zm1.667-10h6.666v6.667h-6.666V4ZM32 10.667h-3.333a1.667 1.667 0 0 0 0 3.333H32a1.667 1.667 0 1 0 0-3.333Zm-3.333-3.334H32A1.667 1.667 0 1 0 32 4h-3.333a1.667 1.667 0 0 0 0 3.333ZM2 7.333h3.333a1.667 1.667 0 1 0 0-3.333H2a1.667 1.667 0 1 0 0 3.333ZM2 14h3.333a1.667 1.667 0 1 0 0-3.333H2A1.667 1.667 0 0 0 2 14Zm30 3.333H2a1.667 1.667 0 1 0 0 3.334h30a1.667 1.667 0 1 0 0-3.334ZM18.667 24H2a1.667 1.667 0 1 0 0 3.333h16.667a1.667 1.667 0 0 0 0-3.333Z\' fill=\'%2300BD6F\'/%3E%3C/svg%3E',
repo: 'nuxt/starter',
branch: 'content',
docs: 'https://content.nuxt.com',
},
{
name: 'Module',
slug: 'module',
description: 'Starter to create your first Nuxt module.',
image: 'data:image/svg+xml,%3Csvg width=\'34\' height=\'34\' fill=\'none\' xmlns=\'http://www.w3.org/2000/svg\'%3E%3Cpath d=\'M29.117 9.283V9.15l-.1-.25a1.186 1.186 0 0 0-.117-.15 1.571 1.571 0 0 0-.15-.2l-.15-.117-.267-.133-12.5-7.717a1.667 1.667 0 0 0-1.766 0L1.667 8.3l-.15.133-.15.117c-.056.063-.106.13-.15.2a1.183 1.183 0 0 0-.117.15l-.1.25v.133c-.016.144-.016.29 0 .434v14.566a1.667 1.667 0 0 0 .783 1.417l12.5 7.717a.784.784 0 0 0 .25.1h.134c.282.09.584.09.866 0h.134a.784.784 0 0 0 .25-.1L28.333 25.7a1.667 1.667 0 0 0 .784-1.417V9.717c.016-.144.016-.29 0-.434ZM13.333 29.017 4.167 23.35V12.717l9.166 5.65v10.65ZM15 15.483 5.667 9.717 15 3.967l9.333 5.75L15 15.483Zm10.833 7.867-9.166 5.667v-10.65l9.166-5.65V23.35Z\' fill=\'%2300BD6F\'/%3E%3C/svg%3E',
repo: 'nuxt/starter',
branch: 'module',
docs: 'https://nuxt.com/docs/4.x/guide/going-further/modules',
},
])
export default defineEventHandler(async (event) => {

Check failure on line 1 in server/routes/data/starters.json.ts

View workflow job for this annotation

GitHub Actions / lint

'event' is defined but never used. Allowed unused args must match /^_/u
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
const templates: Starter[] = []

const files = await $fetch('https://api.github.com/repos/nuxt/nuxt-starter/contents/templates')

await Promise.all(files.map(async (file) => {

Check failure on line 6 in server/routes/data/starters.json.ts

View workflow job for this annotation

GitHub Actions / test

Parameter 'file' implicitly has an 'any' type.

Check failure on line 6 in server/routes/data/starters.json.ts

View workflow job for this annotation

GitHub Actions / test

'files' is of type 'unknown'.

Check failure on line 6 in server/routes/data/starters.json.ts

View workflow job for this annotation

GitHub Actions / test

Parameter 'file' implicitly has an 'any' type.

Check failure on line 6 in server/routes/data/starters.json.ts

View workflow job for this annotation

GitHub Actions / test

'files' is of type 'unknown'.
if (!file.download_url || file.type !== 'file' || !file.name.endsWith('.json')) {

Check failure on line 7 in server/routes/data/starters.json.ts

View workflow job for this annotation

GitHub Actions / lint

Expected indentation of 4 spaces but found 6
return

Check failure on line 8 in server/routes/data/starters.json.ts

View workflow job for this annotation

GitHub Actions / lint

Expected indentation of 6 spaces but found 8
}

Check failure on line 9 in server/routes/data/starters.json.ts

View workflow job for this annotation

GitHub Actions / lint

Expected indentation of 4 spaces but found 6
const templateName = file.name.replace('.json', '')

Check failure on line 10 in server/routes/data/starters.json.ts

View workflow job for this annotation

GitHub Actions / lint

Expected indentation of 4 spaces but found 6
const template = await $fetch(file.download_url, {

Check failure on line 11 in server/routes/data/starters.json.ts

View workflow job for this annotation

GitHub Actions / lint

Expected indentation of 4 spaces but found 6
responseType: 'json',

Check failure on line 12 in server/routes/data/starters.json.ts

View workflow job for this annotation

GitHub Actions / lint

Expected indentation of 6 spaces but found 8
}) as Starter

Check failure on line 13 in server/routes/data/starters.json.ts

View workflow job for this annotation

GitHub Actions / lint

Expected indentation of 4 spaces but found 6
if (!template.deprecated) {

Check failure on line 14 in server/routes/data/starters.json.ts

View workflow job for this annotation

GitHub Actions / lint

Expected indentation of 4 spaces but found 6
templates.push({ slug: templateName, ...template, tar: `https://codeload.github.com/${template.repo}/tar.gz/refs/heads/${template.branch}` })

Check failure on line 15 in server/routes/data/starters.json.ts

View workflow job for this annotation

GitHub Actions / test

'slug' is specified more than once, so this usage will be overwritten.

Check failure on line 15 in server/routes/data/starters.json.ts

View workflow job for this annotation

GitHub Actions / test

'slug' is specified more than once, so this usage will be overwritten.

Check failure on line 15 in server/routes/data/starters.json.ts

View workflow job for this annotation

GitHub Actions / lint

Expected indentation of 6 spaces but found 8
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
}
}))
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

return templates.sort((a, b) => {
if (a.default && !b.default) return -1
if (!a.default && b.default) return 1
return 0
})
})
Comment on lines +1 to +24
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

No error handling for external GitHub API calls.

Both $fetch calls (directory listing on Line 4, individual template files on Line 11) can fail due to rate-limiting (unauthenticated GitHub API allows 60 req/hr), network errors, or malformed responses. A single failure inside Promise.all will reject the entire response with an unhandled error.

Consider:

  • Adding a try/catch around each inner fetch so one bad template doesn't take down the whole endpoint.
  • Using Promise.allSettled instead of Promise.all.
  • Authenticating the GitHub API request (e.g., via a server token) to raise the rate limit, especially given ISR revalidation every hour.
🧰 Tools
πŸͺ› GitHub Check: test

[failure] 15-15:
'slug' is specified more than once, so this usage will be overwritten.


[failure] 6-6:
Parameter 'file' implicitly has an 'any' type.


[failure] 6-6:
'files' is of type 'unknown'.


[failure] 15-15:
'slug' is specified more than once, so this usage will be overwritten.


[failure] 6-6:
Parameter 'file' implicitly has an 'any' type.


[failure] 6-6:
'files' is of type 'unknown'.

πŸ€– Prompt for AI Agents
In `@server/routes/data/starters.json.ts` around lines 1 - 24, Wrap the external
GitHub calls inside robust error handling: replace the Promise.all over files
with Promise.allSettled and for each entry in the map around the inner $fetch
(the call that produces template in the defineEventHandler) add a try/catch so
one failed template is skipped and logged (push only successful, non-deprecated
templates into the templates array); also wrap the initial $fetch that retrieves
files in its own try/catch and return an empty templates array or a safe
fallback on failure; finally, when calling $fetch to GitHub include an
Authorization header using a server-side GITHUB_TOKEN env var to increase rate
limits.

11 changes: 6 additions & 5 deletions shared/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
export interface Starter {
name: string
slug: string
default?: boolean
description: string
image?: string
repo: string
branch: string
dir?: string
demo?: string
docs?: string
tar: string
repo: string,
branch: string,
defaultDir?: string
url?: string
deprecated?: boolean
}
Loading