Skip to content

Comments

chore: refactor starters fetching to use dynamic source from starter repo#492

Open
maximepvrt wants to merge 4 commits intonuxt:mainfrom
maximepvrt:update-templates
Open

chore: refactor starters fetching to use dynamic source from starter repo#492
maximepvrt wants to merge 4 commits intonuxt:mainfrom
maximepvrt:update-templates

Conversation

@maximepvrt
Copy link

@maximepvrt maximepvrt commented Feb 8, 2026

❓ Type of change

  • 📖 Documentation (updates to the documentation or readme)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • 👌 Enhancement (improving an existing functionality)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

📚 Description

I would like to propose replacing the static starters.json file in this repository with a dynamic version fetched directly from the nuxt/starter repository, similar to how the Nuxt CLI operates.

The current static approach leads to synchronization issues. I noticed this when @benjamincanac updated the UI templates:

  • Running npm create nuxt@latest -- -t ui correctly uses the updated template.
  • However, clicking on CodeSandbox or StackBlitz on nuxt.new still opens an outdated repository because the local starters.json was not updated.

To facilitate this transition, I have already modified the repository definitions within nuxt/starter and add properties to this repo with this PR : nuxt/starter#1774.

Questions & Discussion Points

1. Template Recursion & Subdirectories
I noticed that subdirectories in the templates branch (e.g., ui and ui-vue) are not currently integrated into the CLI because there is no recursion logic.

  • What is the intended purpose of these subdirectories if they aren't accessible via the CLI?
  • Should we implement recursive fetching to support these?

Currently, @benjamincanac uses the following command for UI templates in the README: npm create nuxt@latest -- -t github:nuxt-ui-templates/editor

2. Centralizing the Source of Truth
To avoid maintaining starter lists in two places, should we move the starters.json definition to the nuxt.com repository? This would allow both the CLI and the nuxt.new to pull from a single source.

3. UX Consolidation
I am also questioning the placement of the starter cards. Would it make more sense to migrate/repatriate the nuxt.new starter cards to the top of the nuxt.com/templates page for a more unified user experience?

@maximepvrt maximepvrt requested a review from danielroe as a code owner February 8, 2026 17:39
@codesandbox
Copy link

codesandbox bot commented Feb 8, 2026

Review or Edit in CodeSandbox

Open the branch in Web EditorVS CodeInsiders

Open Preview

@vercel
Copy link

vercel bot commented Feb 8, 2026

@maximepvrt is attempting to deploy a commit to the Nuxt Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Feb 8, 2026

📝 Walkthrough

Walkthrough

The PR converts starters data from a static, synchronous export to an async server route that fetches and builds Starter objects from GitHub contents at runtime. The Starter type was changed: added default?, tar, defaultDir?, and url?; removed dir?, demo?, and docs?. StarterCard.vue simplifies template computation to return '' when starter.default is truthy or -- -t ${starter.slug} otherwise. nuxt.config.ts adds an ISR rule for /data/starters.json and removes that path from prerender routes. Two GET routes (c/[slug].get.ts, s/[slug].get.ts) omit the former dir segment when constructing redirect targets.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed The PR description clearly explains the motivation (syncing with nuxt/starter), the technical changes (dynamic fetching instead of static JSON), and provides context about related updates in the nuxt/starter repository.
Title check ✅ Passed The PR title accurately describes the main change: refactoring starters fetching from a static source to dynamic fetching from the nuxt/starter repo. It is specific, concise, and directly reflects the core objective of the changeset.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

❤️ Share

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@server/routes/data/starters.json.ts`:
- Line 1: The handler exported from defineEventHandler declares an unused
parameter `event` and uses untyped fetch results causing implicit any for
`files` and `file`; rename the parameter to `_event` (or remove it), call $fetch
with a type parameter to declare the expected response shape (e.g.,
$fetch<YourResponseType>(...)) so `files` is strongly typed, and update the .map
callback to use a typed `file` parameter (e.g., (file: FileType) => ...)
matching that response type; adjust any related type aliases/interfaces used by
the handler to match the fetched data structure.
- Line 15: The current templates.push call sets slug: templateName before
spreading ...template which allows a slug from the fetched template object to
overwrite the filename-derived templateName; update the object literal in the
templates.push expression so that the spread comes first and then slug:
templateName appears after (e.g., change to { ...template, slug: templateName,
tar: ... }) to ensure the filename-derived slug wins; locate the
templates.push(...) expression and reorder the properties accordingly
(referencing templateName, template, and the Starter-shaped fetched JSON).
- Around line 6-17: Reformat the callback passed to Promise.all(files.map(...))
to use the project's 2-space indentation (instead of the current 6/8-space),
adjusting the entire block beginning with "async (file) => {" through the
closing "}))" so each nested line (checks for file.download_url/type/name, const
templateName, const template fetch, and the templates.push) is indented by 2
spaces per level; verify the async callback body, the if blocks, and the object
literal for templates.push maintain consistent 2-space indentation so the linter
stops failing.
🧹 Nitpick comments (2)
shared/types/index.ts (1)

8-9: Nit: Inconsistent separators in the interface.

Lines 8–9 use trailing commas while every other property uses no separator. Consider removing them for consistency.

✏️ Suggested fix
-  repo: string,
-  branch: string,
+  repo: string
+  branch: string
server/routes/data/starters.json.ts (1)

4-4: Unauthenticated GitHub API calls are subject to strict rate limits.

The GitHub Contents API allows only 60 requests/hour for unauthenticated callers. This handler makes 1 + N requests (directory listing + one per template file) on every cache miss. With ISR at 3600s that's likely fine under normal conditions, but any cache purge, deployment, or cold-start burst could exhaust the quota quickly.

Consider either:

  • Using a GITHUB_TOKEN via a server-side env variable to raise the limit to 5,000 req/hour.
  • Fetching the entire directory tree in a single call (e.g., Git Trees API with ?recursive=1) to reduce request count.

export default defineEventHandler(async () => {
const templates: Starter[] = []

const files = await $fetch('https://api.github.com/repos/maximepvrt/nuxt-starter/contents/templates?ref=update-template')
Copy link
Author

Choose a reason for hiding this comment

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

use https://api.github.com/repos/nuxt/starter/contents/templates?ref=templates after merge nuxt/starter#1774

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@server/routes/data/starters.json.ts`:
- Around line 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.
- Line 4: The $fetch call that assigns files is pointing at a hardcoded fork and
feature branch; update the URL passed to $fetch (the string literal) to the
canonical upstream repository and branch (e.g., replace
'https://api.github.com/repos/maximepvrt/nuxt-starter/contents/templates?ref=update-template'
with the correct upstream repo and branch such as
'https://api.github.com/repos/nuxt/starter/contents/templates?ref=main'), or
better yet make the repo and ref configurable via an environment variable and
use that in the $fetch call so the files variable always targets the canonical
repo/branch in production.
🧹 Nitpick comments (1)
server/routes/data/starters.json.ts (1)

11-13: Unsafe as Starter cast on unvalidated external data.

The fetched JSON is cast to Starter without any runtime validation. If the remote JSON schema changes or a file is malformed, the code will silently produce incorrect Starter objects (e.g., missing repo or branch would yield a broken tar URL on Line 15).

Consider adding minimal runtime validation (e.g., check that template.repo and template.branch are truthy strings) before pushing.

Comment on lines 1 to 24
export default defineEventHandler(async () => {
const templates: Starter[] = []

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

await Promise.all(files.map(async (file) => {
if (!file.download_url || file.type !== 'file' || !file.name.endsWith('.json')) {
return
}
const templateName = file.name.replace('.json', '')
const template = await $fetch(file.download_url, {
responseType: 'json',
}) as Starter
if (!template.deprecated) {
templates.push({ slug: templateName, ...template, tar: `https://codeload.github.com/${template.repo}/tar.gz/refs/heads/${template.branch}` })
}
}))

return templates.sort((a, b) => {
if (a.default && !b.default) return -1
if (!a.default && b.default) return 1
return 0
})
})
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

export default defineEventHandler(async () => {
const templates: Starter[] = []

const files = await $fetch('https://api.github.com/repos/maximepvrt/nuxt-starter/contents/templates?ref=update-template')
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Hardcoded fork URL — must point to the canonical repo before merging.

This fetches from maximepvrt/nuxt-starter on a feature branch. Before merging, update to the target upstream repo and branch (e.g., nuxt/starter).

🤖 Prompt for AI Agents
In `@server/routes/data/starters.json.ts` at line 4, The $fetch call that assigns
files is pointing at a hardcoded fork and feature branch; update the URL passed
to $fetch (the string literal) to the canonical upstream repository and branch
(e.g., replace
'https://api.github.com/repos/maximepvrt/nuxt-starter/contents/templates?ref=update-template'
with the correct upstream repo and branch such as
'https://api.github.com/repos/nuxt/starter/contents/templates?ref=main'), or
better yet make the repo and ref configurable via an environment variable and
use that in the $fetch call so the files variable always targets the canonical
repo/branch in production.

@maximepvrt maximepvrt changed the title chore: refactor starters fetching to use dynamic source from starters repo chore: refactor starters fetching to use dynamic source from starter repo Feb 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant