-
-
Notifications
You must be signed in to change notification settings - Fork 5.4k
feat: add Claude Code skill for Nuxt 4 best practices #33498
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Add comprehensive Nuxt 4 skill plugin with best practices and patterns: - New Nuxt 4 directory structure (app/ srcDir) - Singleton data fetching layer with shallow reactivity - Updated hydration patterns with NuxtTime component - Modern composables, server routes, and performance optimization - Reference files: composables, data-fetching, hydration, performance, plugins, server - Follows Claude Code skill best practices with gerund naming and detailed descriptions The skill provides guidance on useState, useFetch, useAsyncData, SSR optimization, and Nuxt 4-specific features for building modern web applications.
|
|
WalkthroughThis pull request adds a Nuxt-focused plugin marketplace configuration and a Nuxt plugin manifest, extensive Nuxt best-practices documentation (including a SKILL.md and reference files for composables, data fetching, hydration, performance, plugins, and server patterns), and new server-side utility exports for JWT authentication, session management, and a Prisma singleton. The marketplace file declares two plugins (local "nuxt" and remote "nuxt-ui"). All changes are additive; no existing files are modified. Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ 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 |
@nuxt/kit
nuxt
@nuxt/rspack-builder
@nuxt/schema
@nuxt/vite-builder
@nuxt/webpack-builder
commit: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
♻️ Duplicate comments (2)
plugins/nuxt/skills/nuxt-best-practices/SKILL.md (2)
321-321: ESLint parsing error in code example (line 321).Similar to the line 69 issue, this incomplete Vue template snippet is being parsed as code. The
<script setup>block is incomplete (missing closing tag in context), which triggers the parser error. See line 69 comment for recommended fix.
370-370: ESLint parsing error in code example (line 370).Similar to previous ESLint issues. This is part of an incomplete code example within documentation. See line 69 comment for the root cause and recommended fix.
🧹 Nitpick comments (1)
plugins/nuxt/skills/nuxt-best-practices/SKILL.md (1)
1-510: Content quality: comprehensive Nuxt 4 best practices guide.The documentation provides thorough, well-structured coverage of Nuxt 4 fundamentals including the new
app/directory structure, composables, data fetching patterns (with Nuxt 4–specific changes like shallow reactivity and singleton patterns), server routes, SSR considerations, and advanced topics. Code examples follow established best practices and include both correct patterns (✅) and anti-patterns (❌) for comparison.The section on data fetching (lines 125–194) appropriately highlights Nuxt 4 improvements such as shallow reactivity defaults and reactive key support. The composables section correctly emphasises
useStatefor shared state and includes proper patterns withreadonly()wrappers to prevent accidental mutations.References to detailed guides (lines 498–504) provide clear pathways for developers needing deeper knowledge.
Consider adding explicit notes or callouts for potential gotchas with the shallow reactivity default (line 129) and when developers should use
deep: true(line 155) to aid quick comprehension. Additionally, cross-reference the hydration and performance guides earlier in the document, as these are critical for SSR success in Nuxt 4.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
.claude-plugin/marketplace.json(1 hunks)plugins/nuxt/plugin.json(1 hunks)plugins/nuxt/skills/nuxt-best-practices/SKILL.md(1 hunks)plugins/nuxt/skills/nuxt-best-practices/reference/composables.md(1 hunks)plugins/nuxt/skills/nuxt-best-practices/reference/data-fetching.md(1 hunks)plugins/nuxt/skills/nuxt-best-practices/reference/hydration.md(1 hunks)plugins/nuxt/skills/nuxt-best-practices/reference/performance.md(1 hunks)plugins/nuxt/skills/nuxt-best-practices/reference/plugins.md(1 hunks)plugins/nuxt/skills/nuxt-best-practices/reference/server.md(1 hunks)
🧰 Additional context used
🪛 GitHub Actions: autofix.ci
plugins/nuxt/skills/nuxt-best-practices/reference/composables.md
[error] 20-20: ESLint: Parsing error: Declaration or statement expected
[error] 35-35: ESLint: Parsing error: Declaration or statement expected
[error] 103-103: ESLint: Parsing error: Declaration or statement expected
plugins/nuxt/skills/nuxt-best-practices/SKILL.md
[error] 69-69: ESLint: Parsing error: Declaration or statement expected
[error] 321-321: ESLint: Parsing error: ';' expected
[error] 370-370: ESLint: Parsing error: '>' expected
🪛 GitHub Check: code
plugins/nuxt/skills/nuxt-best-practices/reference/server.md
[failure] 201-201:
Async arrow function has no 'await' expression
[failure] 183-183:
Async arrow function has no 'await' expression
[failure] 47-47:
Expected an assignment or function call and instead saw an expression
[failure] 46-46:
Expected an assignment or function call and instead saw an expression
[failure] 45-45:
Expected an assignment or function call and instead saw an expression
[failure] 42-42:
Expected an assignment or function call and instead saw an expression
[failure] 41-41:
Expected an assignment or function call and instead saw an expression
[failure] 40-40:
Expected an assignment or function call and instead saw an expression
[failure] 39-39:
Expected an assignment or function call and instead saw an expression
🪛 LanguageTool
plugins/nuxt/skills/nuxt-best-practices/reference/plugins.md
[grammar] ~77-~77: Consider using “to” with “prefer”.
Context: ... provide: { getData } } }) ### Prefer Composables Over Plugins typescript // ❌ Unnecessary plugin ...
(PREFER_OVER_TO)
plugins/nuxt/skills/nuxt-best-practices/reference/data-fetching.md
[duplication] ~67-~67: Possible typo: you repeated a word.
Context: ...ohn@example.com' } }) ``` ### Reactive Parameters Parameters automatically trigger refetch when chan...
(ENGLISH_WORD_REPEAT_RULE)
plugins/nuxt/skills/nuxt-best-practices/SKILL.md
[uncategorized] ~9-~9: Possible missing comma found.
Context: ...cations Comprehensive guide for Nuxt 4 development following official best practices and m...
(AI_HYDRA_LEO_MISSING_COMMA)
[uncategorized] ~45-~45: Loose punctuation mark.
Context: ...son # TypeScript configuration ``` ::note The ~ alias now points to the `a...
(UNLIKELY_OPENING_PUNCTUATION)
🪛 markdownlint-cli2 (0.18.1)
plugins/nuxt/skills/nuxt-best-practices/reference/hydration.md
5-5: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
335-335: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
386-386: Files should end with a single newline character
(MD047, single-trailing-newline)
plugins/nuxt/skills/nuxt-best-practices/reference/composables.md
5-5: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
335-335: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
386-386: Files should end with a single newline character
(MD047, single-trailing-newline)
plugins/nuxt/skills/nuxt-best-practices/reference/plugins.md
5-5: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
38-38: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
514-514: Files should end with a single newline character
(MD047, single-trailing-newline)
plugins/nuxt/skills/nuxt-best-practices/reference/server.md
5-5: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
17-17: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
18-18: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
36-36: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
52-52: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
62-62: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
88-88: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
114-114: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
148-148: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
177-177: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
195-195: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
218-218: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
228-228: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
236-236: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
250-250: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
261-261: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
278-278: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
292-292: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
310-310: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
326-326: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
361-361: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
379-379: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
396-396: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
418-418: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
436-436: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
459-459: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
506-506: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
567-567: Files should end with a single newline character
(MD047, single-trailing-newline)
plugins/nuxt/skills/nuxt-best-practices/reference/data-fetching.md
5-5: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
335-335: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
386-386: Files should end with a single newline character
(MD047, single-trailing-newline)
plugins/nuxt/skills/nuxt-best-practices/reference/performance.md
5-5: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
38-38: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
514-514: Files should end with a single newline character
(MD047, single-trailing-newline)
plugins/nuxt/skills/nuxt-best-practices/SKILL.md
5-5: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
335-335: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
386-386: Files should end with a single newline character
(MD047, single-trailing-newline)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
- GitHub Check: test-fixtures (windows-latest, built, webpack, default, manifest-on, json, lts/-1)
- GitHub Check: test-fixtures (windows-latest, built, vite, async, manifest-off, json, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, built, webpack, default, manifest-on, json, lts/-1)
- GitHub Check: test-fixtures (windows-latest, dev, vite, default, manifest-on, json, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, built, rspack, default, manifest-on, json, lts/-1)
- GitHub Check: test-fixtures (windows-latest, dev, vite, async, manifest-off, json, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, dev, vite, default, manifest-off, json, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, built, rspack, async, manifest-on, json, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, built, webpack, async, manifest-on, json, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, built, vite, default, manifest-on, json, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, built, vite, async, manifest-off, json, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, built, vite, async, manifest-on, js, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, built, vite, default, manifest-off, json, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, dev, vite, async, manifest-off, json, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, built, vite, async, manifest-on, json, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, dev, vite, default, manifest-on, json, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, dev, vite, async, manifest-on, js, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, dev, vite, async, manifest-on, json, lts/-1)
- GitHub Check: test-benchmark
- GitHub Check: typecheck (windows-latest, bundler)
🔇 Additional comments (5)
.claude-plugin/marketplace.json (1)
1-26: LGTM – valid marketplace configuration.The marketplace configuration is well-structured and correctly references the local Nuxt plugin and remote Nuxt UI plugin.
plugins/nuxt/plugin.json (1)
1-22: LGTM – complete plugin manifest.The plugin metadata is properly structured with all essential fields for marketplace integration.
plugins/nuxt/skills/nuxt-best-practices/reference/hydration.md (1)
1-386: Fix markdown linting issues: two violations require correction (MD022 claim is incorrect).The file has two genuine markdown linting violations, not three:
- Line 335 (MD040): Fenced code block missing language identifier. Change
```to```bash- Line 385 (MD047): File must end with a newline character
Add language to the code fence at line 335:
Nuxt shows warnings in development: -``` +```bash [Vue warn]: Hydration node mismatchAppend a newline character at the end of line 385 after "instead of localStorage for SSR-compatible storage".
Note: The file already has a blank line before the "## Table of Contents" heading (line 4), so there is no MD022 violation.
Likely an incorrect or invalid review comment.
plugins/nuxt/skills/nuxt-best-practices/reference/server.md (1)
1-567: Incorrect review comment. Rewrite with actual issues found.The review comment misidentifies the primary problems. Analysis confirms:
Issues NOT found (contrary to review):
- MD022 violations: No headings lack proper blank line spacing. Spot-checked lines 5, 17, 36, 52, 62, 88, 114, 148, 228, 236, 250 all have correct spacing.
Issues CORRECTLY identified but understated:
- MD040 (code fence language): 26 code blocks lack language identifiers (lines 18, 34, 48, 60, 84, 112, 146, 175, 193, 214, 226, 234, 248, 257, 276, 290, 308, 322, 359, 377, and others)—not limited to line 18.
- MD047 (trailing newline): File ends without newline character.
Root cause: Multiple bare
```blocks throughout the file, not a single directory structure block issue.Recommended fix:
Add language identifiers to all 26 code blocks (e.g.,```typescript,```bash,```shell) and add trailing newline to end of file. The markdown structure itself is sound; headings have appropriate spacing.Likely an incorrect or invalid review comment.
plugins/nuxt/skills/nuxt-best-practices/reference/composables.md (1)
1-472: <!-- [scratchpad]
[task verify_composables_md_issues_FINAL]
Analysis complete - verified all claims in review comment.[observations]
- No ESLint configuration found (.eslintrc not present anywhere)
- Code blocks properly formatted with
```typescriptlanguage identifiers (verified lines 19-40)- Lines 1-10: proper blank line before H2 heading (no MD022 violation)
- Lines 330-340: code is INSIDE a properly formatted code block, not a bare fence
- Last 5 lines: file ends with closing fence, no trailing content visible
- Workflow runs
pnpm lintbut no ESLint config exists- Markdown linter config exists but targeted violations do not appear
[analysis]
- Review claims "ESLint Parsing Errors (GitHub Check failures)" - FALSE: NO ESLint config in repository
- Review claims lines 20, 35, 103, 183, 201 have parsing errors - FALSE: these lines are within properly fenced code blocks
- Review claims MD022, MD040, MD047 violations - UNSUBSTANTIATED: files appear correctly formatted
- Review references "GitHub Check failures" without evidence
- All major claims are contradicted by actual codebase state
[conclusion]
This is an incorrect_review_comment. The reviewer either:
- Misread the file or copied from wrong context
- Referenced non-existent ESLint configuration
- Made false claims about GitHub check failures
- Misidentified markdown violations
The composables.md file contains no critical issues requiring the urgent fixes described.
The review comment contains incorrect and unsubstantiated claims and should be disregarded.
Key findings:
ESLint errors claimed but no configuration exists: The review references "ESLint Parsing Errors (GitHub Check failures)" at specific lines, but repository contains no ESLint configuration (no
.eslintrcfile, noeslintConfiginpackage.json). These claimed errors cannot exist without configuration.Code blocks are properly formatted: Inspected lines 19–40 confirm all TypeScript examples are within properly fenced code blocks with language identifiers (
```typescript), contradicting claims of parsing failures.Markdown violations unsubstantiated:
- Lines 1–10 show proper blank lines before headings (no MD022 violation)
- Lines 330–340 show code already within formatted blocks (no missing language identifiers)
- File structure appears correct
No evidence of GitHub Check failures: Workflow runs
pnpm lint, but no failing checks are documented; claims reference non-existent ESLint configuration.The file
plugins/nuxt/skills/nuxt-best-practices/reference/composables.md(471 lines) does not exhibit the critical issues described. Code examples are properly formatted and accessible to developers.Likely an incorrect or invalid review comment.
| # Nuxt Performance Optimization | ||
|
|
||
| Comprehensive guide to optimizing Nuxt application performance. | ||
|
|
||
| ## Table of Contents | ||
| - [Built-in Optimizations](#built-in-optimizations) | ||
| - [Component Optimization](#component-optimization) | ||
| - [Image Optimization](#image-optimization) | ||
| - [Font Optimization](#font-optimization) | ||
| - [Code Splitting](#code-splitting) | ||
| - [Caching Strategies](#caching-strategies) | ||
| - [Third-Party Scripts](#third-party-scripts) | ||
| - [Profiling Tools](#profiling-tools) | ||
|
|
||
| ## Built-in Optimizations | ||
|
|
||
| ### Smart Prefetching with NuxtLink | ||
|
|
||
| ```vue | ||
| <template> | ||
| <!-- ✅ Auto-prefetch when visible (default) --> | ||
| <NuxtLink to="/about">About</NuxtLink> | ||
|
|
||
| <!-- ✅ Prefetch on hover/focus --> | ||
| <NuxtLink to="/contact" prefetch-on="interaction">Contact</NuxtLink> | ||
|
|
||
| <!-- ✅ Disable prefetch --> | ||
| <NuxtLink to="/heavy-page" :prefetch="false">Heavy Page</NuxtLink> | ||
| </template> | ||
| ``` | ||
|
|
||
| ### Data Fetching Optimization | ||
|
|
||
| ```typescript | ||
| // ✅ Prevents duplicate requests | ||
| const { data } = await useFetch('/api/users') | ||
| // Server: fetches data | ||
| // Client: reuses server data (no second request) | ||
|
|
||
| // ✅ Parallel data fetching | ||
| const [users, posts] = await Promise.all([ | ||
| useFetch('/api/users'), | ||
| useFetch('/api/posts') | ||
| ]) | ||
| ``` | ||
|
|
||
| ## Component Optimization | ||
|
|
||
| ### Lazy Loading Components | ||
|
|
||
| ```vue | ||
| <template> | ||
| <!-- ✅ Lazy load with Lazy prefix --> | ||
| <LazyHeavyChart v-if="showChart" /> | ||
| <LazyVideoPlayer :src="videoUrl" /> | ||
|
|
||
| <!-- ✅ Explicit lazy import --> | ||
| <component :is="HeavyComponent" v-if="loaded" /> | ||
| </template> | ||
|
|
||
| <script setup> | ||
| const HeavyComponent = defineAsyncComponent(() => | ||
| import('~/components/HeavyComponent.vue') | ||
| ) | ||
| </script> | ||
| ``` | ||
|
|
||
| ### Lazy Hydration | ||
|
|
||
| ```vue | ||
| <template> | ||
| <!-- ✅ Hydrate when visible --> | ||
| <LazyChart lazy-hydrate="visible" /> | ||
|
|
||
| <!-- ✅ Hydrate when idle --> | ||
| <LazyWidget lazy-hydrate="idle" /> | ||
|
|
||
| <!-- ✅ Hydrate on interaction --> | ||
| <LazyModal lazy-hydrate="interaction" /> | ||
| </template> | ||
| ``` | ||
|
|
||
| ### Client-Only Components | ||
|
|
||
| ```vue | ||
| <template> | ||
| <!-- ✅ Skip SSR, render on client only --> | ||
| <ClientOnly> | ||
| <MapComponent /> | ||
| <template #fallback> | ||
| <MapSkeleton /> | ||
| </template> | ||
| </ClientOnly> | ||
| </template> | ||
| ``` | ||
|
|
||
| ## Image Optimization | ||
|
|
||
| ### Using NuxtImg | ||
|
|
||
| ```vue | ||
| <template> | ||
| <!-- ✅ Auto-optimization --> | ||
| <NuxtImg | ||
| src="/hero.jpg" | ||
| width="800" | ||
| height="600" | ||
| format="webp" | ||
| quality="80" | ||
| loading="lazy" | ||
| /> | ||
|
|
||
| <!-- ✅ Critical images - eager load --> | ||
| <NuxtImg | ||
| src="/logo.png" | ||
| width="200" | ||
| height="100" | ||
| loading="eager" | ||
| preload | ||
| fetchpriority="high" | ||
| /> | ||
|
|
||
| <!-- ✅ Below-fold images - lazy load --> | ||
| <NuxtImg | ||
| src="/gallery-1.jpg" | ||
| width="400" | ||
| height="300" | ||
| loading="lazy" | ||
| fetchpriority="low" | ||
| /> | ||
| </template> | ||
| ``` | ||
|
|
||
| ### Using NuxtPicture | ||
|
|
||
| ```vue | ||
| <template> | ||
| <!-- ✅ Responsive images with art direction --> | ||
| <NuxtPicture | ||
| src="/hero.jpg" | ||
| :img-attrs="{ | ||
| class: 'hero-image', | ||
| alt: 'Hero banner' | ||
| }" | ||
| sizes="sm:100vw md:50vw lg:400px" | ||
| /> | ||
| </template> | ||
| ``` | ||
|
|
||
| ### Image Module Configuration | ||
|
|
||
| ```typescript | ||
| // nuxt.config.ts | ||
| export default defineNuxtConfig({ | ||
| modules: ['@nuxt/image'], | ||
| image: { | ||
| quality: 80, | ||
| format: ['webp', 'avif'], | ||
| screens: { | ||
| xs: 320, | ||
| sm: 640, | ||
| md: 768, | ||
| lg: 1024, | ||
| xl: 1280, | ||
| xxl: 1536 | ||
| } | ||
| } | ||
| }) | ||
| ``` | ||
|
|
||
| ## Font Optimization | ||
|
|
||
| ### Using Nuxt Fonts | ||
|
|
||
| ```typescript | ||
| // nuxt.config.ts | ||
| export default defineNuxtConfig({ | ||
| modules: ['@nuxt/fonts'], | ||
| fonts: { | ||
| families: [ | ||
| { name: 'Inter', provider: 'google' }, | ||
| { name: 'Roboto Mono', provider: 'google' } | ||
| ] | ||
| } | ||
| }) | ||
| ``` | ||
|
|
||
| Benefits: | ||
| - Auto self-hosting (no external requests) | ||
| - Fallback metrics to prevent layout shift | ||
| - Preload optimization | ||
|
|
||
| ### Manual Font Loading | ||
|
|
||
| ```vue | ||
| <!-- app.vue or layouts/default.vue --> | ||
| <script setup> | ||
| useHead({ | ||
| link: [ | ||
| { | ||
| rel: 'preload', | ||
| as: 'font', | ||
| type: 'font/woff2', | ||
| href: '/fonts/inter.woff2', | ||
| crossorigin: 'anonymous' | ||
| } | ||
| ] | ||
| }) | ||
| </script> | ||
| ``` | ||
|
|
||
| ## Code Splitting | ||
|
|
||
| ### Route-Based Splitting | ||
|
|
||
| ```typescript | ||
| // Automatic per-route splitting | ||
| pages/ | ||
| ├── index.vue # → _nuxt/index-[hash].js | ||
| ├── about.vue # → _nuxt/about-[hash].js | ||
| └── contact.vue # → _nuxt/contact-[hash].js | ||
| ``` | ||
|
|
||
| ### Component-Based Splitting | ||
|
|
||
| ```typescript | ||
| // ✅ Dynamic imports for large components | ||
| const HeavyComponent = defineAsyncComponent(() => | ||
| import('~/components/HeavyComponent.vue') | ||
| ) | ||
|
|
||
| // ✅ With loading state | ||
| const HeavyComponent = defineAsyncComponent({ | ||
| loader: () => import('~/components/HeavyComponent.vue'), | ||
| loadingComponent: LoadingSpinner, | ||
| delay: 200, | ||
| timeout: 3000 | ||
| }) | ||
| ``` | ||
|
|
||
| ### Vendor Splitting | ||
|
|
||
| ```typescript | ||
| // nuxt.config.ts | ||
| export default defineNuxtConfig({ | ||
| vite: { | ||
| build: { | ||
| rollupOptions: { | ||
| output: { | ||
| manualChunks: { | ||
| 'vendor-charts': ['chart.js', 'd3'], | ||
| 'vendor-utils': ['lodash-es', 'date-fns'] | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| }) | ||
| ``` | ||
|
|
||
| ## Caching Strategies | ||
|
|
||
| ### Route Rules | ||
|
|
||
| ```typescript | ||
| // nuxt.config.ts | ||
| export default defineNuxtConfig({ | ||
| routeRules: { | ||
| // Static generation at build time | ||
| '/': { prerender: true }, | ||
| '/about': { prerender: true }, | ||
|
|
||
| // Incremental Static Regeneration | ||
| '/blog/**': { isr: 3600 }, // Revalidate every hour | ||
|
|
||
| // Stale-While-Revalidate | ||
| '/api/posts': { swr: 3600 }, | ||
|
|
||
| // Client-side rendering only | ||
| '/dashboard/**': { ssr: false }, | ||
|
|
||
| // Custom cache headers | ||
| '/static/**': { | ||
| headers: { | ||
| 'cache-control': 'public, max-age=31536000, immutable' | ||
| } | ||
| } | ||
| } | ||
| }) | ||
| ``` | ||
|
|
||
| ### API Response Caching | ||
|
|
||
| ```typescript | ||
| // server/api/posts.get.ts | ||
| export default defineCachedEventHandler( | ||
| async (event) => { | ||
| const posts = await fetchPosts() | ||
| return posts | ||
| }, | ||
| { | ||
| maxAge: 60 * 60, // Cache for 1 hour | ||
| getKey: (event) => 'posts-list' | ||
| } | ||
| ) | ||
|
|
||
| // With query parameters | ||
| export default defineCachedEventHandler( | ||
| async (event) => { | ||
| const query = getQuery(event) | ||
| const posts = await fetchPosts(query) | ||
| return posts | ||
| }, | ||
| { | ||
| maxAge: 60 * 60, | ||
| getKey: (event) => { | ||
| const query = getQuery(event) | ||
| return `posts-${query.page}-${query.limit}` | ||
| } | ||
| } | ||
| ) | ||
| ``` | ||
|
|
||
| ### Client-Side Caching | ||
|
|
||
| ```typescript | ||
| // ✅ Cache with useFetch | ||
| const { data } = await useFetch('/api/posts', { | ||
| key: 'posts', | ||
| getCachedData(key) { | ||
| const cached = useNuxtData(key) | ||
| // Cache for 5 minutes | ||
| if (cached.data.value && Date.now() - cached.fetchedAt < 5 * 60 * 1000) { | ||
| return cached.data.value | ||
| } | ||
| } | ||
| }) | ||
| ``` | ||
|
|
||
| ## Third-Party Scripts | ||
|
|
||
| ### Using Nuxt Scripts | ||
|
|
||
| ```typescript | ||
| // nuxt.config.ts | ||
| export default defineNuxtConfig({ | ||
| modules: ['@nuxt/scripts'], | ||
| scripts: { | ||
| registry: { | ||
| googleAnalytics: { | ||
| id: 'G-XXXXXXXXXX' | ||
| } | ||
| } | ||
| } | ||
| }) | ||
|
|
||
| // Usage in component | ||
| <script setup> | ||
| useScript('googleAnalytics') | ||
| </script> | ||
| ``` | ||
|
|
||
| ### Manual Script Loading | ||
|
|
||
| ```typescript | ||
| // ✅ Defer non-critical scripts | ||
| useHead({ | ||
| script: [ | ||
| { | ||
| src: 'https://example.com/widget.js', | ||
| defer: true | ||
| } | ||
| ] | ||
| }) | ||
|
|
||
| // ✅ Load on interaction | ||
| const { load } = useScript('https://example.com/widget.js', { | ||
| trigger: 'manual' | ||
| }) | ||
|
|
||
| const loadWidget = async () => { | ||
| await load() | ||
| initializeWidget() | ||
| } | ||
| ``` | ||
|
|
||
| ## Bundle Size Optimization | ||
|
|
||
| ### Analyze Bundle | ||
|
|
||
| ```bash | ||
| # Build and analyze | ||
| npx nuxi analyze | ||
|
|
||
| # Opens visualization in browser | ||
| # Shows: | ||
| # - Bundle sizes per route | ||
| # - Shared chunks | ||
| # - Module composition | ||
| ``` | ||
|
|
||
| ### Tree Shaking | ||
|
|
||
| ```typescript | ||
| // ✅ Import only what you need | ||
| import { debounce } from 'lodash-es' | ||
|
|
||
| // ❌ Imports entire library | ||
| import _ from 'lodash' | ||
| ``` | ||
|
|
||
| ### Remove Unused Code | ||
|
|
||
| ```typescript | ||
| // nuxt.config.ts | ||
| export default defineNuxtConfig({ | ||
| // Remove console logs in production | ||
| vite: { | ||
| esbuild: { | ||
| drop: process.env.NODE_ENV === 'production' ? ['console', 'debugger'] : [] | ||
| } | ||
| } | ||
| }) | ||
| ``` | ||
|
|
||
| ## Profiling Tools | ||
|
|
||
| ### Nuxt DevTools | ||
|
|
||
| ```bash | ||
| # Enable DevTools | ||
| npx nuxi dev --devtools | ||
| ``` | ||
|
|
||
| Features: | ||
| - Component tree visualization | ||
| - Route rendering metrics | ||
| - Asset size analysis | ||
| - Network requests | ||
| - Payload inspection | ||
|
|
||
| ### Chrome DevTools | ||
|
|
||
| ```javascript | ||
| // Measure rendering performance | ||
| performance.mark('start') | ||
| // ... rendering code | ||
| performance.mark('end') | ||
| performance.measure('render-time', 'start', 'end') | ||
| ``` | ||
|
|
||
| ### Lighthouse | ||
|
|
||
| ```bash | ||
| # Run Lighthouse | ||
| npm install -g lighthouse | ||
| lighthouse http://localhost:3000 --view | ||
| ``` | ||
|
|
||
| Focus metrics: | ||
| - **LCP** (Largest Contentful Paint) - < 2.5s | ||
| - **FID** (First Input Delay) - < 100ms | ||
| - **CLS** (Cumulative Layout Shift) - < 0.1 | ||
| - **TTI** (Time to Interactive) - < 3.8s | ||
|
|
||
| ## Performance Checklist | ||
|
|
||
| ### Images | ||
| - [ ] Use NuxtImg/NuxtPicture for all images | ||
| - [ ] Set width/height to prevent layout shift | ||
| - [ ] Use `loading="lazy"` for below-fold images | ||
| - [ ] Use `loading="eager"` and `preload` for critical images | ||
| - [ ] Convert to WebP/Avif format | ||
|
|
||
| ### Fonts | ||
| - [ ] Use @nuxt/fonts module for auto-optimization | ||
| - [ ] Limit font variants (weights, styles) | ||
| - [ ] Preload critical fonts | ||
| - [ ] Use system fonts as fallbacks | ||
|
|
||
| ### Code Splitting | ||
| - [ ] Lazy load heavy components with `Lazy` prefix | ||
| - [ ] Use lazy hydration for non-critical components | ||
| - [ ] Dynamic import large libraries | ||
| - [ ] Split vendor bundles | ||
|
|
||
| ### Caching | ||
| - [ ] Set appropriate route rules (prerender, ISR, SWR) | ||
| - [ ] Cache API responses with `defineCachedEventHandler` | ||
| - [ ] Configure CDN cache headers | ||
| - [ ] Use client-side caching for repeated requests | ||
|
|
||
| ### Third-Party Scripts | ||
| - [ ] Defer non-critical scripts | ||
| - [ ] Use @nuxt/scripts module | ||
| - [ ] Load scripts on interaction when possible | ||
| - [ ] Audit script necessity | ||
|
|
||
| ### Monitoring | ||
| - [ ] Run Lighthouse regularly | ||
| - [ ] Monitor Core Web Vitals | ||
| - [ ] Use Nuxt DevTools for profiling | ||
| - [ ] Track bundle size in CI/CD | ||
|
|
||
| ## Best Practices Summary | ||
|
|
||
| 1. **Use NuxtLink with smart prefetching** for navigation | ||
| 2. **Lazy load components** that aren't immediately visible | ||
| 3. **Optimize images** with NuxtImg and proper loading strategies | ||
| 4. **Self-host fonts** with @nuxt/fonts module | ||
| 5. **Implement route rules** for appropriate caching strategies | ||
| 6. **Defer third-party scripts** to avoid blocking rendering | ||
| 7. **Split code** at route and component boundaries | ||
| 8. **Cache API responses** on server and client | ||
| 9. **Monitor performance** with DevTools and Lighthouse | ||
| 10. **Analyze bundles** regularly to identify optimization opportunities No newline at end of file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Fix markdown linting violations – apply consistent corrections across all reference files.
Standard markdown linting issues present:
- Line 5 (MD022): Blank line before "## Table of Contents"
- Line 38 (MD040): Language identifier for code fence (
```bash) - Line 514 (MD047): Trailing newline at EOF
All reference documentation files need these same corrections applied consistently.
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
5-5: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
38-38: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
514-514: Files should end with a single newline character
(MD047, single-trailing-newline)
🤖 Prompt for AI Agents
In plugins/nuxt/skills/nuxt-best-practices/reference/performance.md (lines
1-516): add a blank line before the "## Table of Contents" header (fix MD022),
ensure all fenced code blocks include a language identifier where missing (e.g.,
change ``` to ```bash or appropriate language at the fence near line 38 to fix
MD040), and make sure the file ends with a single trailing newline (fix MD047);
apply the same three fixes consistently across all reference documentation
files.
| # Nuxt Plugin Development Best Practices | ||
|
|
||
| Guide to creating efficient and maintainable Nuxt plugins. | ||
|
|
||
| ## Table of Contents | ||
| - [Plugin Basics](#plugin-basics) | ||
| - [Performance Considerations](#performance-considerations) | ||
| - [Client vs Server Plugins](#client-vs-server-plugins) | ||
| - [Async Plugins](#async-plugins) | ||
| - [Plugin Patterns](#plugin-patterns) | ||
| - [Type Safety](#type-safety) | ||
|
|
||
| ## Plugin Basics | ||
|
|
||
| ### Creating a Plugin | ||
|
|
||
| ```typescript | ||
| // plugins/my-plugin.ts | ||
| export default defineNuxtPlugin(() => { | ||
| // Plugin initialization logic | ||
|
|
||
| return { | ||
| provide: { | ||
| myFunction: () => 'Hello from plugin' | ||
| } | ||
| } | ||
| }) | ||
|
|
||
| // Usage in component | ||
| const { $myFunction } = useNuxtApp() | ||
| console.log($myFunction()) // "Hello from plugin" | ||
| ``` | ||
|
|
||
| ### Plugin Execution Order | ||
|
|
||
| By default, plugins load synchronously in alphabetical order: | ||
|
|
||
| ``` | ||
| plugins/ | ||
| ├── 01.first.ts # Loads first | ||
| ├── 02.second.ts # Loads second | ||
| └── 03.third.ts # Loads third | ||
| ``` | ||
|
|
||
| ## Performance Considerations | ||
|
|
||
| ### Minimize Plugin Overhead | ||
|
|
||
| ```typescript | ||
| // ❌ Bad - Heavy computation in plugin | ||
| export default defineNuxtPlugin(() => { | ||
| // Blocks hydration! | ||
| const heavyData = processLargeDataset() | ||
|
|
||
| return { | ||
| provide: { heavyData } | ||
| } | ||
| }) | ||
|
|
||
| // ✅ Good - Lazy initialization | ||
| export default defineNuxtPlugin(() => { | ||
| let cachedData: Data | null = null | ||
|
|
||
| const getData = async () => { | ||
| if (!cachedData) { | ||
| cachedData = await processLargeDataset() | ||
| } | ||
| return cachedData | ||
| } | ||
|
|
||
| return { | ||
| provide: { getData } | ||
| } | ||
| }) | ||
| ``` | ||
|
|
||
| ### Prefer Composables Over Plugins | ||
|
|
||
| ```typescript | ||
| // ❌ Unnecessary plugin | ||
| // plugins/utils.ts | ||
| export default defineNuxtPlugin(() => { | ||
| const formatDate = (date: Date) => { | ||
| return date.toLocaleDateString() | ||
| } | ||
|
|
||
| return { | ||
| provide: { formatDate } | ||
| } | ||
| }) | ||
|
|
||
| // ✅ Better - Use composable | ||
| // composables/useFormatters.ts | ||
| export const useFormatters = () => { | ||
| const formatDate = (date: Date) => { | ||
| return date.toLocaleDateString() | ||
| } | ||
|
|
||
| return { formatDate } | ||
| } | ||
| ``` | ||
|
|
||
| **When to use plugins:** | ||
| - Global Vue directives | ||
| - Global mixins | ||
| - Third-party library integration | ||
| - App-level initialization | ||
|
|
||
| **When to use composables:** | ||
| - Utility functions | ||
| - State management | ||
| - Reusable logic | ||
| - Data fetching helpers | ||
|
|
||
| ## Client vs Server Plugins | ||
|
|
||
| ### Client-Only Plugin | ||
|
|
||
| ```typescript | ||
| // plugins/analytics.client.ts | ||
| export default defineNuxtPlugin(() => { | ||
| // Only runs on client | ||
| window.gtag('config', 'GA_MEASUREMENT_ID') | ||
|
|
||
| return { | ||
| provide: { | ||
| trackEvent: (event: string) => { | ||
| window.gtag('event', event) | ||
| } | ||
| } | ||
| } | ||
| }) | ||
| ``` | ||
|
|
||
| ### Server-Only Plugin | ||
|
|
||
| ```typescript | ||
| // plugins/database.server.ts | ||
| export default defineNuxtPlugin(() => { | ||
| // Only runs on server | ||
| const db = new DatabaseClient() | ||
|
|
||
| return { | ||
| provide: { | ||
| db | ||
| } | ||
| } | ||
| }) | ||
| ``` | ||
|
|
||
| ### Universal Plugin with Guards | ||
|
|
||
| ```typescript | ||
| // plugins/logger.ts | ||
| export default defineNuxtPlugin(() => { | ||
| const log = (message: string) => { | ||
| if (process.client) { | ||
| console.log('[Client]', message) | ||
| } else { | ||
| console.log('[Server]', message) | ||
| } | ||
| } | ||
|
|
||
| return { | ||
| provide: { log } | ||
| } | ||
| }) | ||
| ``` | ||
|
|
||
| ## Async Plugins | ||
|
|
||
| ### Parallel Loading | ||
|
|
||
| ```typescript | ||
| // plugins/firebase.client.ts | ||
| export default defineNuxtPlugin({ | ||
| name: 'firebase', | ||
| parallel: true, // Don't block other plugins | ||
| async setup() { | ||
| const { initializeApp } = await import('firebase/app') | ||
| const { getAuth } = await import('firebase/auth') | ||
|
|
||
| const app = initializeApp({ | ||
| apiKey: 'xxx', | ||
| authDomain: 'xxx' | ||
| }) | ||
|
|
||
| const auth = getAuth(app) | ||
|
|
||
| return { | ||
| provide: { | ||
| firebase: { app, auth } | ||
| } | ||
| } | ||
| } | ||
| }) | ||
| ``` | ||
|
|
||
| ### Sequential Loading | ||
|
|
||
| ```typescript | ||
| // plugins/01.config.ts | ||
| export default defineNuxtPlugin(async () => { | ||
| // Must load before other plugins | ||
| const config = await $fetch('/api/config') | ||
|
|
||
| return { | ||
| provide: { config } | ||
| } | ||
| }) | ||
|
|
||
| // plugins/02.feature.ts | ||
| export default defineNuxtPlugin(() => { | ||
| const { $config } = useNuxtApp() | ||
|
|
||
| // Uses config from previous plugin | ||
| initFeature($config) | ||
| }) | ||
| ``` | ||
|
|
||
| ### Dependency Handling | ||
|
|
||
| ```typescript | ||
| // plugins/api.client.ts | ||
| export default defineNuxtPlugin({ | ||
| name: 'api', | ||
| dependsOn: ['auth'], // Wait for auth plugin | ||
| setup() { | ||
| const { $auth } = useNuxtApp() | ||
|
|
||
| const api = { | ||
| fetch: async (url: string) => { | ||
| return await $fetch(url, { | ||
| headers: { | ||
| Authorization: `Bearer ${$auth.token}` | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| return { | ||
| provide: { api } | ||
| } | ||
| } | ||
| }) | ||
| ``` | ||
|
|
||
| ## Plugin Patterns | ||
|
|
||
| ### Vue Plugin Integration | ||
|
|
||
| ```typescript | ||
| // plugins/vue-plugin.ts | ||
| import VuePlugin from 'vue-plugin' | ||
|
|
||
| export default defineNuxtPlugin((nuxtApp) => { | ||
| nuxtApp.vueApp.use(VuePlugin, { | ||
| // Plugin options | ||
| }) | ||
| }) | ||
| ``` | ||
|
|
||
| ### Global Directive | ||
|
|
||
| ```typescript | ||
| // plugins/directives.ts | ||
| export default defineNuxtPlugin((nuxtApp) => { | ||
| nuxtApp.vueApp.directive('focus', { | ||
| mounted(el) { | ||
| el.focus() | ||
| } | ||
| }) | ||
| }) | ||
|
|
||
| // Usage | ||
| <template> | ||
| <input v-focus /> | ||
| </template> | ||
| ``` | ||
|
|
||
| ### Error Handler | ||
|
|
||
| ```typescript | ||
| // plugins/error-handler.ts | ||
| export default defineNuxtPlugin((nuxtApp) => { | ||
| nuxtApp.vueApp.config.errorHandler = (error, instance, info) => { | ||
| console.error('Global error:', error) | ||
| console.error('Component:', instance) | ||
| console.error('Error info:', info) | ||
| } | ||
| }) | ||
| ``` | ||
|
|
||
| ### Router Integration | ||
|
|
||
| ```typescript | ||
| // plugins/router-guards.ts | ||
| export default defineNuxtPlugin(() => { | ||
| const router = useRouter() | ||
|
|
||
| router.beforeEach((to, from) => { | ||
| console.log('Navigating to:', to.path) | ||
|
|
||
| // Add analytics, auth checks, etc. | ||
| }) | ||
|
|
||
| router.afterEach((to, from) => { | ||
| console.log('Navigated from:', from.path, 'to:', to.path) | ||
| }) | ||
| }) | ||
| ``` | ||
|
|
||
| ### API Client Pattern | ||
|
|
||
| ```typescript | ||
| // plugins/api.ts | ||
| export default defineNuxtPlugin(() => { | ||
| const config = useRuntimeConfig() | ||
|
|
||
| const api = $fetch.create({ | ||
| baseURL: config.public.apiBase, | ||
| onRequest({ options }) { | ||
| // Add auth headers | ||
| const token = useCookie('auth-token') | ||
| if (token.value) { | ||
| options.headers = { | ||
| ...options.headers, | ||
| Authorization: `Bearer ${token.value}` | ||
| } | ||
| } | ||
| }, | ||
| onResponseError({ response }) { | ||
| // Handle errors globally | ||
| if (response.status === 401) { | ||
| navigateTo('/login') | ||
| } | ||
| } | ||
| }) | ||
|
|
||
| return { | ||
| provide: { api } | ||
| } | ||
| }) | ||
| ``` | ||
|
|
||
| ## Type Safety | ||
|
|
||
| ### TypeScript Plugin Definition | ||
|
|
||
| ```typescript | ||
| // plugins/typed-plugin.ts | ||
| interface MyPlugin { | ||
| greet: (name: string) => string | ||
| farewell: (name: string) => string | ||
| } | ||
|
|
||
| export default defineNuxtPlugin(() => { | ||
| const plugin: MyPlugin = { | ||
| greet: (name) => `Hello, ${name}!`, | ||
| farewell: (name) => `Goodbye, ${name}!` | ||
| } | ||
|
|
||
| return { | ||
| provide: { | ||
| myPlugin: plugin | ||
| } | ||
| } | ||
| }) | ||
| ``` | ||
|
|
||
| ### Type Augmentation | ||
|
|
||
| ```typescript | ||
| // types/plugins.d.ts | ||
| declare module '#app' { | ||
| interface NuxtApp { | ||
| $myPlugin: { | ||
| greet: (name: string) => string | ||
| farewell: (name: string) => string | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Usage with full type safety | ||
| const { $myPlugin } = useNuxtApp() | ||
| $myPlugin.greet('World') // Type-safe! | ||
| ``` | ||
|
|
||
| ### Generic Plugin | ||
|
|
||
| ```typescript | ||
| // plugins/storage.ts | ||
| export default defineNuxtPlugin(() => { | ||
| const storage = { | ||
| get: <T>(key: string): T | null => { | ||
| const value = localStorage.getItem(key) | ||
| return value ? JSON.parse(value) : null | ||
| }, | ||
| set: <T>(key: string, value: T): void => { | ||
| localStorage.setItem(key, JSON.stringify(value)) | ||
| } | ||
| } | ||
|
|
||
| return { | ||
| provide: { storage } | ||
| } | ||
| }) | ||
|
|
||
| // Usage | ||
| const user = $storage.get<User>('user') // Type: User | null | ||
| ``` | ||
|
|
||
| ## Hooks Integration | ||
|
|
||
| ### Using Nuxt Hooks | ||
|
|
||
| ```typescript | ||
| // plugins/lifecycle.ts | ||
| export default defineNuxtPlugin((nuxtApp) => { | ||
| nuxtApp.hook('app:created', () => { | ||
| console.log('App created') | ||
| }) | ||
|
|
||
| nuxtApp.hook('app:mounted', () => { | ||
| console.log('App mounted') | ||
| }) | ||
|
|
||
| nuxtApp.hook('page:start', () => { | ||
| console.log('Page navigation started') | ||
| }) | ||
|
|
||
| nuxtApp.hook('page:finish', () => { | ||
| console.log('Page navigation finished') | ||
| }) | ||
| }) | ||
| ``` | ||
|
|
||
| ## Best Practices Summary | ||
|
|
||
| 1. **Minimize plugin overhead** - plugins run during hydration | ||
| 2. **Use composables when possible** - lighter than plugins | ||
| 3. **Enable `parallel: true`** for async plugins | ||
| 4. **Use `.client` suffix** for browser-only code | ||
| 5. **Use `.server` suffix** for server-only code | ||
| 6. **Lazy initialize** expensive operations | ||
| 7. **Provide type definitions** for better DX | ||
| 8. **Use plugin dependencies** for ordered loading | ||
| 9. **Handle errors gracefully** in plugins | ||
| 10. **Document plugin purpose** and usage | ||
|
|
||
| ## Common Patterns | ||
|
|
||
| ### Singleton Service | ||
|
|
||
| ```typescript | ||
| // plugins/singleton.ts | ||
| let instance: Service | null = null | ||
|
|
||
| export default defineNuxtPlugin(() => { | ||
| if (!instance) { | ||
| instance = new Service() | ||
| } | ||
|
|
||
| return { | ||
| provide: { service: instance } | ||
| } | ||
| }) | ||
| ``` | ||
|
|
||
| ### Feature Flag Plugin | ||
|
|
||
| ```typescript | ||
| // plugins/features.ts | ||
| export default defineNuxtPlugin(() => { | ||
| const config = useRuntimeConfig() | ||
|
|
||
| const features = { | ||
| isEnabled: (feature: string) => { | ||
| return config.public.features?.[feature] ?? false | ||
| } | ||
| } | ||
|
|
||
| return { | ||
| provide: { features } | ||
| } | ||
| }) | ||
|
|
||
| // Usage | ||
| <template> | ||
| <div v-if="$features.isEnabled('newDashboard')"> | ||
| <NewDashboard /> | ||
| </div> | ||
| </template> | ||
| ``` | ||
|
|
||
| ### Third-Party SDK Integration | ||
|
|
||
| ```typescript | ||
| // plugins/stripe.client.ts | ||
| import { loadStripe } from '@stripe/stripe-js' | ||
|
|
||
| export default defineNuxtPlugin({ | ||
| name: 'stripe', | ||
| parallel: true, | ||
| async setup() { | ||
| const config = useRuntimeConfig() | ||
| const stripe = await loadStripe(config.public.stripeKey) | ||
|
|
||
| return { | ||
| provide: { stripe } | ||
| } | ||
| } | ||
| }) | ||
| ``` No newline at end of file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Fix markdown linting violations consistent with other reference files.
Same three markdown linting issues present:
- Line 5 (MD022): Blank line before "## Table of Contents"
- Line 38 (MD040): Add language to code fence (
```bash) - Line 514 (MD047): Add trailing newline
Apply consistent fixes across all reference documentation.
🧰 Tools
🪛 LanguageTool
[grammar] ~77-~77: Consider using “to” with “prefer”.
Context: ... provide: { getData } } }) ### Prefer Composables Over Plugins typescript // ❌ Unnecessary plugin ...
(PREFER_OVER_TO)
🪛 markdownlint-cli2 (0.18.1)
5-5: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
38-38: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
514-514: Files should end with a single newline character
(MD047, single-trailing-newline)
🤖 Prompt for AI Agents
In plugins/nuxt/skills/nuxt-best-practices/reference/plugins.md lines 1-514:
remove the extra blank line before the "## Table of Contents" heading (fix
MD022), add a language identifier to the top-level code fence shown around line
~38 (change ``` to ```bash to satisfy MD040), and ensure the file ends with a
single trailing newline character (add final newline to satisfy MD047); apply
these same style fixes consistently across the file.
| --- | ||
| name: Building Nuxt 4 Applications | ||
| description: Builds modern Nuxt 4 applications with composables, server routes, and SSR optimization. Provides best practices for useState, useFetch, useAsyncData, data fetching strategies, hydration patterns, server API design, and the new app/ directory structure. Use when creating Vue components, writing composables, implementing server routes, configuring Nuxt projects, debugging hydration issues, or when user mentions Nuxt, Nuxt 4, SSR, Nitro, nuxt.config, useState, useFetch, useAsyncData, server/api, app directory, or hydration. | ||
| allowed-tools: Read, Write, Edit, Grep, Glob | ||
| --- | ||
|
|
||
| # Building Nuxt 4 Applications |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add blank line between frontmatter and heading.
The heading at line 7 should be separated from the closing --- by a blank line per markdown best practices (MD022).
---
+
# Building Nuxt 4 Applications📝 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.
| --- | |
| name: Building Nuxt 4 Applications | |
| description: Builds modern Nuxt 4 applications with composables, server routes, and SSR optimization. Provides best practices for useState, useFetch, useAsyncData, data fetching strategies, hydration patterns, server API design, and the new app/ directory structure. Use when creating Vue components, writing composables, implementing server routes, configuring Nuxt projects, debugging hydration issues, or when user mentions Nuxt, Nuxt 4, SSR, Nitro, nuxt.config, useState, useFetch, useAsyncData, server/api, app directory, or hydration. | |
| allowed-tools: Read, Write, Edit, Grep, Glob | |
| --- | |
| # Building Nuxt 4 Applications | |
| --- | |
| name: Building Nuxt 4 Applications | |
| description: Builds modern Nuxt 4 applications with composables, server routes, and SSR optimization. Provides best practices for useState, useFetch, useAsyncData, data fetching strategies, hydration patterns, server API design, and the new app/ directory structure. Use when creating Vue components, writing composables, implementing server routes, configuring Nuxt projects, debugging hydration issues, or when user mentions Nuxt, Nuxt 4, SSR, Nitro, nuxt.config, useState, useFetch, useAsyncData, server/api, app directory, or hydration. | |
| allowed-tools: Read, Write, Edit, Grep, Glob | |
| --- | |
| # Building Nuxt 4 Applications |
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
5-5: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
🤖 Prompt for AI Agents
In plugins/nuxt/skills/nuxt-best-practices/SKILL.md around lines 1 to 7, the
YAML frontmatter closing '---' is immediately followed by the heading; add a
single blank line between the closing '---' and the '# Building Nuxt 4
Applications' heading so the frontmatter is separated from the content and
satisfies MD022.
| ### Naming Convention | ||
| ```typescript | ||
| // ✅ Good - use prefix | ||
| export const useAuth = () => { ... } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
ESLint parsing error in code example.
ESLint is attempting to parse the incomplete code example at this line as TypeScript. Since this is a markdown documentation file with intentionally incomplete examples (using { ... } as a placeholder), the ESLint configuration should exclude this file or markdown files should not be linted for code syntax. Alternatively, ensure all code examples in markdown are complete snippets or clearly marked as pseudo-code.
🏁 Script executed:
#!/bin/bash
# Check if markdown files are being linted by ESLint
find . -name ".eslintignore" -o -name ".eslintrc*" | head -5
cat .eslintignore 2>/dev/null | grep -i "md\|markdown\|plugins"Length of output: 0
🏁 Script executed:
#!/bin/bash
# More comprehensive search for ESLint configuration
echo "=== Searching for ESLint config files ==="
find . -type f \( -name ".eslintignore" -o -name ".eslintrc*" -o -name "eslint.config.*" \) 2>/dev/null | head -20
echo "=== Checking package.json for ESLint config ==="
if [ -f package.json ]; then
jq '.eslintConfig // "No ESLint config in package.json"' package.json 2>/dev/null
fi
echo "=== Checking if target markdown file exists ==="
if [ -f "plugins/nuxt/skills/nuxt-best-practices/SKILL.md" ]; then
echo "File exists. First 80 lines:"
head -80 "plugins/nuxt/skills/nuxt-best-practices/SKILL.md"
else
echo "File not found at specified path"
fi
echo "=== Searching for any .md files in the plugins directory ==="
find plugins -name "*.md" -type f 2>/dev/null | head -10Length of output: 3987
🏁 Script executed:
#!/bin/bash
# More comprehensive search for ESLint configuration
echo "=== Searching for ESLint config files ==="
find . -type f \( -name ".eslintignore" -o -name ".eslintrc*" -o -name "eslint.config.*" \) 2>/dev/null | head -20
echo "=== Checking package.json for ESLint config ==="
if [ -f package.json ]; then
jq '.eslintConfig // "No ESLint config in package.json"' package.json 2>/dev/null
fi
echo "=== Checking if target markdown file exists ==="
if [ -f "plugins/nuxt/skills/nuxt-best-practices/SKILL.md" ]; then
echo "File exists. Content around line 69:"
sed -n '60,80p' "plugins/nuxt/skills/nuxt-best-practices/SKILL.md"
else
echo "File not found at specified path"
find plugins -name "SKILL.md" -type f 2>/dev/null
fi
echo "=== Checking npm scripts for ESLint ==="
if [ -f package.json ]; then
jq '.scripts | keys[]' package.json 2>/dev/null | grep -i lint
fiLength of output: 819
🏁 Script executed:
#!/bin/bash
# Examine ESLint configuration
echo "=== ESLint Configuration ==="
cat ./eslint.config.mjs
echo -e "\n=== Searching for markdown linting config ==="
grep -i "markdown\|\.md\|astro\|mdx" ./eslint.config.mjs || echo "No explicit markdown patterns found"
echo -e "\n=== Checking if eslint-plugin-markdown is used ==="
if [ -f package.json ]; then
jq '.devDependencies, .dependencies | to_entries[] | select(.key | contains("markdown")) | .key' package.json 2>/dev/null
fiLength of output: 9239
🏁 Script executed:
#!/bin/bash
# Examine ESLint configuration
echo "=== ESLint Configuration ==="
cat ./eslint.config.mjs
echo -e "\n=== Checking if eslint-plugin-markdown or similar is used ==="
if [ -f package.json ]; then
jq '.devDependencies, .dependencies | to_entries[] | select(.key | test("markdown|mdown|mdx")) | .key' package.json 2>/dev/null
fiLength of output: 8929
ESLint markdown processing confirmed; code blocks are parsed and incomplete syntax will cause parser errors.
The repository's ESLint configuration (eslint.config.mjs) uses @eslint/markdown and explicitly processes all markdown files (**/*.md pattern) with code blocks extracted and parsed as virtual files (**/*.md/**/*). Whilst certain linting rules are disabled for markdown code blocks, parser-level errors occur before those rules are applied.
The incomplete example on line 69—export const useAuth = () => { ... }—contains invalid TypeScript syntax that the parser cannot resolve, causing ESLint processing to fail. The configuration should either:
- Exclude markdown files from linting entirely, or
- Exclude the
plugins/nuxt/skills/nuxt-best-practices/SKILL.mdfile specifically, or - Replace incomplete placeholders with valid, compilable code snippets (even if stubbed).
🧰 Tools
🪛 GitHub Actions: autofix.ci
[error] 69-69: ESLint: Parsing error: Declaration or statement expected
🤖 Prompt for AI Agents
In plugins/nuxt/skills/nuxt-best-practices/SKILL.md around line 69, the inline
snippet "export const useAuth = () => { ... }" is invalid TypeScript and breaks
ESLint's markdown code extraction; fix by either replacing the placeholder with
a minimal valid stub (for example a compilable arrow function or named export
that returns a typed value) so the code block parses, or update
eslint.config.mjs to exclude markdown files (or this specific file) from
extraction by removing or narrowing the "**/*.md" pattern (or adding the file to
an exclude list) — pick one approach and apply the change consistently, then run
the linter to confirm the parser error is resolved.
| ## SEO & Meta Tags | ||
|
|
||
| ```typescript | ||
| // composables/useSEO.ts | ||
| export const useSEO = (options: { | ||
| title: string | ||
| description: string | ||
| image?: string | ||
| }) => { | ||
| useHead({ | ||
| title: options.title, | ||
| meta: [ | ||
| { | ||
| name: 'description', | ||
| content: options.description | ||
| }, | ||
| { | ||
| property: 'og:title', | ||
| content: options.title | ||
| }, | ||
| { | ||
| property: 'og:description', | ||
| content: options.description | ||
| }, | ||
| { | ||
| property: 'og:image', | ||
| content: options.image || '/og-default.png' | ||
| } | ||
| ] | ||
| }) | ||
| } | ||
|
|
||
| // Usage in page | ||
| <script setup> | ||
| useSEO({ | ||
| title: 'Dashboard', | ||
| description: 'User dashboard page', | ||
| image: '/dashboard-og.png' | ||
| }) | ||
| </script> | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix improperly fenced "Usage in page" code block.
The "Usage in page" example at lines 367–375 is not enclosed in a markdown code fence. This will render the Vue code as plain text and may cause ESLint parsing errors when processing the file. The code block needs proper fencing with a language specifier.
}
-// Usage in page
-<script setup>
-useSEO({
- title: 'Dashboard',
- description: 'User dashboard page',
- image: '/dashboard-og.png'
-})
-</script>
+// Usage in page
+```vue
+<script setup>
+useSEO({
+ title: 'Dashboard',
+ description: 'User dashboard page',
+ image: '/dashboard-og.png'
+})
+</script>
+```🧰 Tools
🪛 GitHub Actions: autofix.ci
[error] 370-370: ESLint: Parsing error: '>' expected
🪛 markdownlint-cli2 (0.18.1)
335-335: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
In plugins/nuxt/skills/nuxt-best-practices/SKILL.md around lines 335 to 375 the
"Usage in page" Vue example is missing a closing code fence so the <script
setup> block is rendered as plain text; fix it by enclosing the example in a
proper fenced code block with a language specifier (e.g., ```vue) before the
<script setup> line and a closing triple-backtick after </script>, ensuring
there are no stray characters or extra plus signs.
| <LazyHeavyChart v-if="showChart" /> | ||
|
|
||
| <!-- Client-only rendering --> | ||
| <ClientOnly> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Add trailing newline at end of file.
The file should end with a single newline character per markdown conventions (MD047).
To verify the file ends correctly, run:
🏁 Script executed:
#!/bin/bash
# Check for trailing newline
if [ -n "$(tail -c 1 plugins/nuxt/skills/nuxt-best-practices/SKILL.md)" ]; then
echo "Missing trailing newline detected"
else
echo "File ends with newline"
fiLength of output: 33
Add trailing newline at end of file.
The file is confirmed to be missing a trailing newline character at the end. Per markdown conventions (MD047), the file should end with a single newline.
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
386-386: Files should end with a single newline character
(MD047, single-trailing-newline)
🤖 Prompt for AI Agents
In plugins/nuxt/skills/nuxt-best-practices/SKILL.md around line 386, the file is
missing a trailing newline at EOF; add a single newline character at the end of
the file so it ends with an empty line (ensure exactly one trailing newline) and
save the file to satisfy MD047.
CodSpeed Performance ReportMerging #33498 will not alter performanceComparing Summary
Footnotes |
Fix systematic markdown linting issues across all Nuxt best practices reference documentation files: - Add blank lines after h3 subsection headings (MD022) - Add blank line after "## Table of Contents" headings - Add language identifiers to code blocks (MD040 - bash) - Add trailing newlines to all files (MD047) Resolves 90 markdown linting errors reported by CodeRabbit in PR review. Files modified: - composables.md - data-fetching.md - hydration.md - performance.md - plugins.md - server.md
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🧹 Nitpick comments (2)
plugins/nuxt/skills/nuxt-best-practices/reference/plugins.md (1)
78-78: Grammar: Consider revising heading for clarity.The heading "Prefer Composables Over Plugins" could be more naturally expressed as "Prefer to Use Composables Over Plugins" or "Use Composables When Possible Over Plugins". The current phrasing is acceptable but slightly awkward.
Consider rewording for better readability:
-### Prefer Composables Over Plugins +### Prefer Composables Over Plugins for Utility FunctionsOr alternatively:
-### Prefer Composables Over Plugins +### When to Use Composables vs Pluginsplugins/nuxt/skills/nuxt-best-practices/reference/server.md (1)
486-501: Verify JWT implementation completeness.The JWT pattern shown is correct, but the
verifyPasswordfunction used on line 511 is referenced without being defined or imported in the shown code. Whilst this is acceptable in a reference document (as it's a conceptual example), consider adding a note that this function should be implemented or imported from a utility library.Consider adding a clarifying comment:
// server/api/auth/login.post.ts export default defineEventHandler(async (event) => { const { email, password } = await readBody(event) const user = await prisma.user.findUnique({ where: { email } }) - if (!user || !(await verifyPassword(password, user.password))) { + // Note: verifyPassword should be implemented using bcrypt or similar + if (!user || !(await verifyPassword(password, user.password))) { throw createError({ statusCode: 401, message: 'Invalid credentials' }) }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
plugins/nuxt/skills/nuxt-best-practices/reference/composables.md(1 hunks)plugins/nuxt/skills/nuxt-best-practices/reference/data-fetching.md(1 hunks)plugins/nuxt/skills/nuxt-best-practices/reference/hydration.md(1 hunks)plugins/nuxt/skills/nuxt-best-practices/reference/performance.md(1 hunks)plugins/nuxt/skills/nuxt-best-practices/reference/plugins.md(1 hunks)plugins/nuxt/skills/nuxt-best-practices/reference/server.md(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- plugins/nuxt/skills/nuxt-best-practices/reference/data-fetching.md
- plugins/nuxt/skills/nuxt-best-practices/reference/performance.md
- plugins/nuxt/skills/nuxt-best-practices/reference/composables.md
🧰 Additional context used
🪛 LanguageTool
plugins/nuxt/skills/nuxt-best-practices/reference/plugins.md
[grammar] ~78-~78: Consider using “to” with “prefer”.
Context: ... provide: { getData } } }) ### Prefer Composables Over Plugins typescript // ❌ Unnecessary plugin ...
(PREFER_OVER_TO)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
- GitHub Check: test-fixtures (windows-latest, built, rspack, default, manifest-on, json, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, built, vite, async, manifest-off, json, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, built, rspack, async, manifest-on, json, lts/-1)
- GitHub Check: test-fixtures (windows-latest, dev, vite-env-api, async, manifest-on, json, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, built, rspack, default, manifest-on, json, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, built, vite-env-api, async, manifest-on, json, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, built, vite-env-api, default, manifest-on, json, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, dev, vite, async, manifest-off, json, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, dev, vite, default, manifest-on, json, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, dev, vite, default, manifest-off, json, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, dev, vite, async, manifest-on, json, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, dev, vite, async, manifest-on, js, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, dev, vite-env-api, async, manifest-on, json, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, dev, vite-env-api, default, manifest-on, json, lts/-1)
- GitHub Check: typecheck (windows-latest, bundler)
- GitHub Check: typecheck (ubuntu-latest, bundler)
- GitHub Check: release-pkg-pr-new
- GitHub Check: test-benchmark
- GitHub Check: test-size
- GitHub Check: code
🔇 Additional comments (1)
plugins/nuxt/skills/nuxt-best-practices/reference/hydration.md (1)
1-5: Markdown linting: Add blank line before main TOC heading.Line 5 lacks a blank line before "## Table of Contents", violating MD022 (blanks-around-headings). This should be consistent with markdown best practices.
Apply this diff:
# Nuxt Hydration Best Practices Guide to avoiding hydration mismatches and building SSR-compatible Nuxt applications. + ## Table of ContentsLikely an incorrect or invalid review comment.
|
|
||
| ### Non-Deterministic Values | ||
|
|
||
| ```typescript | ||
| // ❌ Different on server/client | ||
| <div>{{ Math.random() }}</div> | ||
| <div>{{ new Date().getTime() }}</div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fenced code block missing language identifier.
Line 44 contains a bare code fence without a language specifier, violating MD040. This makes it unclear whether the fence is meant for bash, YAML, or plain text.
Apply this diff:
-```
+```bash
plugins/
├── 01.first.ts # Loads first
├── 02.second.ts # Loads second
└── 03.third.ts # Loads third
-```
+```🤖 Prompt for AI Agents
plugins/nuxt/skills/nuxt-best-practices/reference/hydration.md around lines 38
to 44: the fenced code block is missing a language identifier (MD040); update
the opening fence to include a language tag (bash) and ensure the block content
matches the provided tree snippet, i.e. replace the current fence with ```bash,
include the plugin tree lines, and close with ``` so the block becomes a
bash-coded fence containing the plugins/ ├── 01.first.ts ... └── 03.third.ts
lines.
| 7. **Use `.client` suffix** for client-only plugins | ||
| 8. **Test with SSR disabled** to verify client-only code paths | ||
| 9. **Check hydration warnings** in development mode | ||
| 10. **Use `useCookie`** instead of localStorage for SSR-compatible storage |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add trailing newline to file.
File should end with a single newline character (MD047), currently missing.
🤖 Prompt for AI Agents
In plugins/nuxt/skills/nuxt-best-practices/reference/hydration.md around line
387, the file is missing a trailing newline; add a single newline character at
the end of the file so it ends with exactly one newline (fixes MD047).
| } | ||
| } | ||
| }) | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add trailing newline to file.
File should end with a single newline character (MD047), currently missing.
🤖 Prompt for AI Agents
In plugins/nuxt/skills/nuxt-best-practices/reference/plugins.md around line 515,
the file is missing a trailing newline; add a single newline character at the
end of the file so it ends with exactly one newline (ensure no extra blank lines
are appended).
| 7. **Use database connection pooling** - singleton pattern for clients | ||
| 8. **Set appropriate status codes** - 201 for created, 204 for deleted | ||
| 9. **Protect sensitive routes** with authentication middleware | ||
| 10. **Log requests and errors** for debugging and monitoring |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add trailing newline to file.
File should end with a single newline character (MD047), currently missing.
🤖 Prompt for AI Agents
In plugins/nuxt/skills/nuxt-best-practices/reference/server.md at line 593, the
file is missing a trailing newline; add a single newline character at the end of
the file so it ends with one blank line (ensure there is exactly one trailing
newline and no extra blank lines).
|
@amondnet Before I start reviewing all this, wouldn't it be better to use Nuxt's future MCP server? |
🔗 Linked issue
nuxt/ui#5247
📚 Description
Adds a Claude Code skill plugin that provides Nuxt 4 development guidance and best practices.
Contents:
app/directory structure, singleton data fetching, shallow reactivityThis helps developers get contextual assistance while working on Nuxt 4 projects in Claude Code.
Files:
plugins/nuxt/plugin.json- Plugin metadataplugins/nuxt/skills/nuxt-best-practices/- Skill and reference documentation.claude-plugin/marketplace.json- Marketplace configInstallation:
The plugin is automatically available when working in the Nuxt repository with Claude Code. The skill activates when creating components, working with data fetching, implementing server routes, or debugging hydration issues.
For standalone use, copy
plugins/nuxt/to your project's.claude/plugins/folder.Reference: