feat: 위키 저장소 초기 커밋

- CLAUDE.md 운영 규칙
- wiki/ 정리된 지식 페이지 (Nuxt + Claude Code)
- raw/ 원본 자료
- reference/ Nuxt 4.x 공식 문서

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
gil
2026-05-13 00:31:51 +09:00
commit 5f664546cf
275 changed files with 35154 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
.DS_Store
node_modules/
.obsidian/
.claude/

159
CLAUDE.md Normal file
View File

@@ -0,0 +1,159 @@
# Nuxt 개발 지식 위키 — 운영 규칙 (Schema)
이 저장소는 Nuxt(Vue 3) 개발 경험을 축적하는 개인 지식 위키다.
**Raw** → 원본 자료 보관, **Wiki** → 정리된 지식 저장, **CLAUDE.md** → 운영 규칙.
---
## 디렉토리 구조
```
nuxt-wiki/
├── CLAUDE.md # 이 파일. 위키 운영 규칙
├── raw/ # 개인 원본 자료 (플랫, Claude는 수정하지 않음)
├── wiki/ # 정리된 지식 (Claude가 작성·관리, 플랫)
│ ├── index.md # 전체 목차 (카테고리별 분류)
│ └── log.md # 작업 이력
└── reference/ # Nuxt 4.x 공식 문서 (트리 구조 유지, 읽기 전용)
```
**플랫 구조 원칙:** `raw/``wiki/` 내부에는 하위 폴더를 만들지 않는다. 분류는 `wiki/index.md`에서만 한다. `reference/`는 예외 — 공식 문서의 원래 트리 구조를 유지한다.
---
## 카테고리 정의
| 카테고리 | 내용 |
|---|---|
| **핵심 개념** | Nuxt 라이프사이클, 렌더링 모드(SSR/SSG/ISR/SPA), 라우팅, 레이어, 모듈 시스템 |
| **패턴 & 레시피** | Composable 패턴, 상태관리(Pinia), 폼 처리, 인증 패턴 등 재사용 가능한 솔루션 |
| **모듈 & 에코시스템** | Nuxt 공식·서드파티 모듈, Nitro, UnJS 계열(H3, ofetch, defu 등) |
| **트러블슈팅** | 에러 사례, 삽질 기록, 버전별 Breaking Change, 해결책 |
| **성능 & 배포** | 번들 최적화, 캐싱 전략, CI/CD, Vercel·Netlify·Node 환경별 설정 |
| **아키텍처 결정** | ADR, 설계 고민, 트레이드오프 기록, 패턴 선택 근거 |
---
## 운영 방법 3가지
### 1. 자료 넣기 (Ingest)
**트리거:** 사용자가 `raw/`에 파일을 추가하거나, 텍스트/URL을 메시지로 붙여넣을 때.
**Claude가 할 일:**
1. 원본 자료 읽기 (raw 파일이면 그대로, 텍스트면 `raw/`에 파일로 저장)
2. 관련 위키 페이지 파악 — 기존 페이지 업데이트 or 새 페이지 생성
3. 각 위키 페이지 작성/갱신 (아래 페이지 형식 참고)
4. `wiki/index.md` 목차 업데이트
5. `wiki/log.md`에 작업 이력 추가
**판단 기준:**
- 기존 페이지와 70% 이상 주제가 겹치면 → 해당 페이지에 병합
- 새로운 주제면 → 새 페이지 생성
- 한 자료에서 여러 주제가 나오면 → 여러 페이지에 분산하고 서로 [[교차참조]]
---
### 2. 질문하기 (Query)
**트리거:** 사용자가 Nuxt 관련 질문을 할 때.
**Claude가 할 일:**
1. `wiki/index.md`와 관련 위키 페이지를 먼저 확인
2. 위키 기반으로 답변 (어느 페이지를 참고했는지 명시)
3. 위키에 없는 내용이면 → 솔직하게 "위키에 없음"을 밝히고 일반 지식으로 보완
4. 답변 과정에서 새로 정리할 가치가 있는 내용이 나오면 → 사용자에게 위키 추가 여부 제안
---
### 3. 건강검진 (Lint)
**트리거:** 사용자가 "건강검진", "lint", "위키 점검"을 요청할 때.
**Claude가 할 일:**
1. 모든 위키 페이지 스캔
2. 아래 항목 체크:
- 끊긴 [[위키링크]] (참조 대상 페이지가 없는 것)
- 내용 충돌 (두 페이지에서 상반된 내용)
- 고아 페이지 (index.md에 없는 페이지)
- 업데이트 필요 (Nuxt 버전과 맞지 않는 낡은 내용)
- 빈 섹션 또는 TODO로만 채워진 페이지
3. 문제 목록을 리포트하고, 수정 여부를 사용자에게 확인 후 처리
4. `wiki/log.md`에 건강검진 결과 기록
---
## 위키 페이지 형식
```markdown
# 페이지 제목
> **카테고리:** 핵심 개념 | 패턴 & 레시피 | 모듈 & 에코시스템 | 트러블슈팅 | 성능 & 배포 | 아키텍처 결정
> **최종 수정:** YYYY-MM-DD
> **관련:** [[페이지명]], [[페이지명]]
## 요약
한두 문장으로 이 페이지가 무엇을 다루는지.
## 본문
...
## 참고 / 출처
- raw/파일명 (원본 자료)
- 외부 링크
```
**작성 규칙:**
- 제목은 `wiki/페이지명.md` 형태로 저장. 영문 소문자 + 하이픈 (예: `nuxt-rendering-modes.md`)
- 페이지 간 참조는 반드시 `[[페이지명]]` 형식 사용 (확장자 제외)
- 코드 예시는 언어 지정 코드블록으로 작성
- 의견·추측은 `> ⚠️ 주의:` 또는 `> 💡 경험:` 인용 블록으로 구분
- Nuxt 버전 의존적인 내용은 버전을 명시 (예: `Nuxt 3.x`, `Nuxt 4.x`)
---
## 파일 네이밍 규칙
| 종류 | 형식 | 예시 |
|---|---|---|
| raw 원본 | 자유 (원본 이름 유지 권장) | `nuxt-4-migration-guide.md`, `adr-auth-strategy.txt` |
| wiki 페이지 | `kebab-case.md` | `nuxt-rendering-modes.md`, `usefetch-pattern.md` |
| 트러블슈팅 | `ts-{짧은 설명}.md` | `ts-hydration-mismatch.md` |
| ADR | `adr-{주제}.md` | `adr-state-management.md` |
---
## 참조 라이브러리 (Reference)
`reference/`는 Nuxt 4.x 공식 문서 클론이다. 읽기 전용 — 절대 수정하지 않는다.
**언제 사용하나:**
- 사용자 질문에 위키(`wiki/`)만으로 답이 부족할 때 `reference/`를 먼저 검색
- 위키 페이지 작성 시 공식 문서 내용을 인용·발췌할 때
**검색 방법:**
```bash
grep -r "키워드" reference/
# 또는 폴더 구조로 직접 탐색:
# reference/1.getting-started/ → 시작하기, 설치, 설정
# reference/2.directory-structure/ → 파일 구조
# reference/3.guide/ → 개념 가이드 (라우팅, 상태관리 등)
# reference/4.api/ → 컴포저블, 유틸리티, 컴포넌트 API
# reference/5.community/ → 기여 가이드
# reference/6.bridge/ → Nuxt 2→3 브릿지
# reference/7.migration/ → 마이그레이션 가이드
```
**출처 표기:** `reference/` 내용을 위키에 쓸 때는 `reference/{경로}` 형태로 명시한다.
`raw/`와의 차이: `raw/`는 "내가 정리할 개인 자료", `reference/`는 "필요할 때 찾아볼 공식 자료".
---
## 중요한 제약
- `raw/` 파일은 절대 수정하지 않는다.
- `reference/` 파일도 절대 수정하지 않는다.
- 위키 페이지를 삭제할 때는 사용자에게 먼저 확인한다.
- `wiki/log.md`는 항상 최신 상태로 유지한다.
- 위키 내용이 raw 원본과 충돌하면 raw 원본을 우선하되, 충돌 사실을 페이지에 명시한다.

11
README.md Normal file
View File

@@ -0,0 +1,11 @@
---
navigation: false
---
# Nuxt Docs
This repository contains the documentation of Nuxt, hosted on <https://nuxt.com/docs/4.x/getting-started/introduction>
## Contributing
Have a look at <https://github.com/nuxt/nuxt.com> to run the website locally.

20
package.json Normal file
View File

@@ -0,0 +1,20 @@
{
"name": "@nuxt/docs",
"version": "4.3.1",
"repository": {
"type": "git",
"url": "git+https://github.com/nuxt/nuxt.git",
"directory": "docs"
},
"homepage": "https://nuxt.com",
"description": "Documentation for Nuxt in raw markdown",
"license": "MIT",
"type": "module",
"exports": {
"./*": "./*"
},
"files": [
"**/*.md",
"**/*.yml"
]
}

0
raw/.gitkeep Normal file
View File

View File

@@ -0,0 +1,85 @@
# Claude Agent SDK: 에이전트 구축 가이드
> 출처: https://claude.com/blog/building-agents-with-the-claude-agent-sdk
> 수집일: 2026-05-13
---
## 개요
Anthropic이 Claude Code SDK를 **Claude Agent SDK**로 이름 변경. 코딩을 넘어 더 광범위한 에이전트 응용에 대한 반영.
핵심 설계 원칙:
> "에이전트에게 컴퓨터를 주어라. 인간처럼 작동하게 하라."
bash 명령, 파일 작업, 코드 실행 접근 권한을 주면 Claude는 프로그래밍뿐만 아니라 데이터 분석, 리서치 등 다양한 작업을 처리할 수 있다.
---
## 에이전트 루프 아키텍처
효과적인 에이전트는 반복 가능한 피드백 사이클로 동작한다:
```
1. Gather Context → 관련 정보 수집
2. Take Action → 도구로 작업 실행
3. Verify Work → 출력 평가 및 개선
4. Iterate → 목표 달성까지 반복
```
---
## 핵심 기능별 분류
### 컨텍스트 수집 (Gather Context)
| 방법 | 설명 | 사용 시점 |
|---|---|---|
| Agentic Search | bash 스크립트(grep, tail)로 파일시스템 탐색 | **기본값** |
| Semantic Search | 벡터 기반 탐색 | agentic search가 부족할 때만 |
| Subagents | 병렬 작업 + 독립 컨텍스트 | 대규모·복잡 작업 |
| Compaction | 대화 히스토리 자동 요약 | 컨텍스트 오버플로우 방지 |
### 액션 실행 (Take Action)
- **Tools**: 주요 실행 메커니즘 (효율 위해 의식적으로 설계해야 함)
- **Bash & Scripts**: 유연한 범용 실행 (파일 변환, 웹 스크래핑)
- **Code Generation**: 복잡한 작업의 정밀하고 조합 가능한 출력
- **MCPs**: 외부 서비스(Slack, GitHub, Asana) 표준화 통합
### 작업 검증 (Verify Work)
| 방법 | 설명 |
|---|---|
| Rules-Based Feedback | 정의된 검증 기준 (린팅 등) |
| Visual Feedback | UI 작업용 스크린샷·렌더링 |
| LLM as Judge | 보조 모델 평가 (지연 비용 고려) |
---
## 유스케이스 예시
- 포트폴리오·투자 분석 Finance 에이전트
- 일정·여행 관리 Personal 어시스턴트
- 복잡한 요청 처리 Customer Support 에이전트
- 문서 종합 분석 Research 에이전트
---
## 에이전트 테스트 및 개선
실패 사례 분석 시 확인 포인트:
1. 실패 케이스와 근본 원인 검토
2. 에이전트에게 적절한 도구가 있는지 평가
3. 더 나은 정보 탐색 메커니즘 개선
4. 공식적인 검증 규칙 추가
5. 프로그래밍 방식 평가를 위한 대표 테스트 셋 구축
---
## 핵심 인사이트
> "semantic search는 agentic search가 부족할 때만 사용하라."
> 대부분의 경우 bash 기반 탐색이 더 효율적.
> 도구는 **의식적으로 설계**해야 한다. 에이전트 효율성의 핵심.

View File

@@ -0,0 +1,147 @@
# Claude Code: 5가지 에이전틱 워크플로우 패턴
> 출처: https://www.mindstudio.ai/blog/claude-code-agentic-workflow-patterns
> 수집일: 2026-05-13
---
## 패턴 선택 원칙
> "단순한 것부터 시작하라 — Sequential — 더 단순한 패턴이 무너질 때만 복잡성을 추가하라."
---
## 패턴 1: Sequential (순차)
### 동작 방식
각 단계가 순서대로 실행. 이전 출력이 다음 단계의 입력.
"파이프라인: A 완료 → 결과가 B로 전달"
### 적합한 상황
- 명확하고 예측 가능한 작업 순서
- 각 단계 출력이 다음 단계 입력이 되는 경우
- 1~2개 컨텍스트 윈도우 안에 들어오는 작업
### 예시
```
1. 명세 문서 읽기
2. 함수 구현 생성
3. 단위 테스트 작성
4. 테스트 실행 후 결과 보고
```
### 트레이드오프
- ✅ 최대 단순성, 예측 가능성
- ❌ 독립 작업 시 낮은 처리량
- ❌ 긴 작업에서 컨텍스트 윈도우 한계
---
## 패턴 2: Operator (오케스트레이터)
### 동작 방식
제어 에이전트가 계획을 세우고 다른 에이전트나 특화된 도구 호출을 지시.
"하나가 두뇌, 특화된 subagent나 도구가 실행"
### 적합한 상황
- 전체 작업이 단일 컨텍스트 윈도우를 초과
- 컴포넌트마다 다른 전문적 집중이 필요
- 작업 우선순위에 대한 중앙 제어 필요
- 계획과 실행의 명확한 분리가 필요
### 예시 (보안 감사)
오케스트레이터가 3개 subagent에 위임:
- 인증 스캔 담당
- DB 쿼리 스캔 담당
- 입력 검증 스캔 담당
→ 결과를 종합해서 최종 리포트
### 트레이드오프
- ✅ 뛰어난 확장성
- ❌ 오케스트레이팅 에이전트가 병목
- ❌ 명확한 인터페이스를 위한 세심한 프롬프트 엔지니어링 필요
---
## 패턴 3: Split-and-Merge (병렬)
### 동작 방식
코디네이터가 작업을 독립 청크로 분할 → 여러 인스턴스가 동시 작업 → 결과 수집 및 합성
### 적합한 상황
- 상호 의존성 없이 깔끔하게 분리되는 작업
- 속도가 중요할 때 (wall-clock time 단축)
- 대량의 유사 항목 처리
- 격리에 적합한 독립적인 서브태스크
### 예시
함수 50개 문서화 → 5개씩 10 배치로 분할 → 병렬 실행 → docstring 합산
### 트레이드오프
- ✅ 병렬성으로 극적인 성능 향상
- ❌ 합병 단계의 복잡성
- ❌ 불일관한 출력, 부분 실패 처리
- ❌ 동시 실행으로 비용 상승
---
## 패턴 4: Agent Teams (다중 에이전트 팀)
### 동작 방식
여러 특화된 에이전트가 지속적인 워크플로우에서 협업. 각 에이전트는 정의된 역할, 특정 범위, 고유 컨텍스트·도구 세트를 갖는다.
### 적합한 상황
- 여러 세션에 걸친 장기 프로젝트
- 작업의 다양한 측면에서 진정한 전문화 필요
- 범위 분산 없는 집중된 에이전트 작업
- 기존 소프트웨어 개발 파이프라인과 유사한 구조
### 예시 팀 구조
- **Planning agent**: 전체 목표 유지
- **Code agent**: 구현 작성·수정
- **Testing agent**: 동작 검증
- **Review agent**: 품질 검사
- **Documentation agent**: 문서 업데이트
### 트레이드오프
- ✅ 에이전트당 집중된 컨텍스트로 복잡한 지속 작업에 강력
- ❌ 조율 오버헤드
- ❌ 오류 전파 — 신중한 설계와 커뮤니케이션 프로토콜 필요
---
## 패턴 5: Headless Autonomous (자율 자동화)
### 동작 방식
인간 개입 없이 이벤트, 스케줄, 외부 신호로 트리거. 작업 완료까지 독립 실행.
### 적합한 상황
- 잘 정의된 반복·이벤트 기반 작업
- 알려진 실패 모드 + 복구 경로
- 실행 후 출력 검증 가능
- 반복 작업의 완전 자동화
### 예시
- 야간 저장소 스캔 → 의존성 업데이트 → 자동 PR 생성
- CI/CD 트리거 → 실패 진단 → 버그 리포트
- 스케줄 실행 → API 사용 로그 감사 → 주간 요약 생성
### 트레이드오프
- ✅ 반복 작업에서 최대 자동화 수익
- ❌ 엄격한 안전장치 필요:
- 최소 권한 원칙
- 가역적 작업 우선
- 명확한 종료 조건
- 인간 검토 체크포인트
---
## 패턴 선택 매트릭스
| 시나리오 | 최적 패턴 |
|---|---|
| 명확한 단계의 선형 작업 | Sequential |
| 서브 위임 필요한 대규모 작업 | Operator |
| 독립적 항목 다량 처리 | Split-and-merge |
| 다중 도메인 장기 프로젝트 | Agent teams |
| 반복·이벤트 트리거 자동화 | Headless |

View File

@@ -0,0 +1,212 @@
# Best Practices for Claude Code (공식 문서)
> 출처: https://code.claude.com/docs/en/best-practices
> 수집일: 2026-05-13
---
## 개요
Claude Code는 에이전틱 코딩 환경이다. 챗봇처럼 답하고 기다리는 게 아니라 파일 읽기, 명령 실행, 변경, 자율적 문제 해결이 가능하다.
핵심 제약: **컨텍스트 윈도우가 빠르게 채워지고, 채워질수록 성능이 저하된다.**
- 컨텍스트 20~40% 채워지면 품질 저하 시작
- 실제 실무에서 검증된 임계치: 60% 이상 채우지 말 것
---
## 1. 검증 수단을 항상 제공할 것
Claude는 스스로 검증할 수 있을 때 극적으로 성능이 올라간다. 테스트, 스크린샷, 기대 출력을 함께 제공하라.
| 전략 | Before | After |
|---|---|---|
| 검증 기준 제공 | "이메일 검증 함수 구현해줘" | "validateEmail 작성. 테스트 케이스: user@example.com → true, invalid → false. 구현 후 테스트 실행해" |
| UI 변경 시각 검증 | "대시보드 보기 좋게 만들어줘" | "[스크린샷 붙여넣기] 이 디자인 구현하고, 결과 스크린샷 찍어서 원본과 비교해. 차이점 나열하고 수정해" |
| 증상이 아닌 근본 원인 | "빌드가 실패해" | "빌드 오류: [오류 붙여넣기]. 수정 후 빌드 성공 확인. 에러 억제 말고 근본 원인 해결해" |
---
## 2. 탐색 → 계획 → 코딩 순서
곧바로 코딩하면 잘못된 문제를 풀 위험이 있다. Plan mode로 탐색과 실행을 분리하라.
```
1. Explore (Plan mode): 파일 읽기, 질문 — 수정 없음
2. Plan (Plan mode): 상세 구현 계획 작성
3. Implement: Plan mode 해제 후 코딩 + 테스트
4. Commit: 설명적인 메시지 + PR 생성
```
> 단, 작은 수정(오타, 변수명 변경)은 계획 없이 바로 실행해도 됨. 계획은 오버헤드.
---
## 3. 구체적인 컨텍스트 제공
| 전략 | Before | After |
|---|---|---|
| 작업 범위 지정 | "foo.py 테스트 추가해줘" | "foo.py에서 로그아웃 상태 엣지 케이스 테스트 작성해줘. mock 사용 피해줘" |
| 소스 지시 | "왜 ExecutionFactory API가 이상해?" | "ExecutionFactory git 히스토리 보고 API가 어떻게 형성됐는지 요약해줘" |
| 기존 패턴 참조 | "캘린더 위젯 추가해줘" | "홈 페이지 기존 위젯(HotDogWidget.php 참고) 패턴 따라서, 월 선택 + 연도 페이지네이션 캘린더 위젯 만들어줘. 이미 쓰는 라이브러리만 사용해" |
| 증상 설명 | "로그인 버그 고쳐줘" | "세션 만료 후 로그인 실패 보고됨. src/auth/ auth flow, 특히 token refresh 확인해. 실패 재현 테스트 먼저 작성 후 수정해" |
### Rich Content 제공 방법
- `@` 파일 참조: `@src/auth.ts` 처럼 경로 직접 참조
- 이미지 붙여넣기: 직접 복붙 or 드래그앤드롭
- URL 제공: API 문서, 레퍼런스
- 파이프: `cat error.log | claude`
---
## 4. 환경 설정
### CLAUDE.md 효과적으로 작성
`/init` 명령으로 현재 프로젝트 구조 기반 초안 생성.
**포함해야 할 것:**
- Claude가 추측할 수 없는 bash 명령
- 기본값과 다른 코드 스타일 규칙
- 테스트 실행 방법
- 레포 규칙 (브랜치 네이밍, PR 컨벤션)
- 프로젝트 아키텍처 결정 사항
- 흔한 gotcha / 비직관적 동작
**포함하면 안 되는 것:**
- 코드 읽으면 알 수 있는 것
- 자주 바뀌는 정보
- 긴 설명이나 튜토리얼
- "clean code 작성해" 같은 당연한 것
> 규칙을 무시한다면 CLAUDE.md가 너무 긴 것. 불필요한 내용 제거하거나 hook으로 이전.
CLAUDE.md import 문법:
```markdown
See @README.md for project overview.
# Additional Instructions
- Git workflow: @docs/git-instructions.md
```
CLAUDE.md 위치:
- `~/.claude/CLAUDE.md`: 전역 (모든 세션)
- `./CLAUDE.md`: 프로젝트 루트 (git 공유)
- `./CLAUDE.local.md`: 개인 설정 (.gitignore 추가)
- 하위 폴더: 해당 폴더 작업 시 자동 로드
### Hooks 설정
훅은 CLAUDE.md 지시와 달리 **결정론적으로 강제 실행**된다.
```
claude -p "eslint 실행하는 hook 작성해줘"
claude -p "migrations 폴더 쓰기 차단하는 hook 작성해줘"
```
### Skills 생성
`.claude/skills/``SKILL.md` 파일을 넣으면 도메인 지식 및 재사용 워크플로우로 사용 가능.
```markdown
# .claude/skills/fix-issue/SKILL.md
---
name: fix-issue
description: Fix a GitHub issue
disable-model-invocation: true
---
Fix GitHub issue: $ARGUMENTS.
1. gh issue view로 상세 확인
2. 문제 파악
3. 관련 파일 탐색
4. 수정 구현
5. 테스트 작성 및 실행
...
```
`/fix-issue 1234` 로 실행. `disable-model-invocation: true`는 수동 실행 전용 워크플로우에 사용.
### Subagents 생성
`.claude/agents/`에 정의. 독립 컨텍스트 윈도우 + 특화된 도구 세트.
```markdown
# .claude/agents/security-reviewer.md
---
name: security-reviewer
description: Reviews code for security vulnerabilities
tools: Read, Grep, Glob, Bash
model: opus
---
...
```
---
## 5. 세션 관리
### 컨텍스트를 공격적으로 관리
- `/clear`: 무관한 작업 사이에 컨텍스트 초기화
- `/compact <지시사항>`: 부분 요약 (예: `/compact API 변경 사항 위주로`)
- `/btw`: 컨텍스트에 남기지 않는 사이드 질문 (오버레이로 표시)
- `Esc + Esc` / `/rewind`: 이전 체크포인트로 복원
CLAUDE.md에 컴팩션 지시도 가능:
```markdown
When compacting, always preserve the full list of modified files and any test commands.
```
### Subagents로 조사 위임
조사 시 많은 파일을 읽어 컨텍스트를 소비한다. Subagent로 위임하면 메인 컨텍스트 오염 방지.
```
Use subagents to investigate how our authentication system handles token refresh,
and whether we have any existing OAuth utilities I should reuse.
```
---
## 6. 자동화와 규모 확장
### Non-interactive 모드
```bash
# 단발 쿼리
claude -p "이 프로젝트가 무엇인지 설명해줘"
# 구조화된 출력 (스크립트용)
claude -p "API 엔드포인트 나열해줘" --output-format json
# 스트리밍
claude -p "로그 파일 분석해줘" --output-format stream-json
```
### 대규모 파일 병렬 처리 (Fan-out)
```bash
for file in $(cat files.txt); do
claude -p "Migrate $file from React to Vue. Return OK or FAIL." \
--allowedTools "Edit,Bash(git commit *)"
done
```
### Auto mode
```bash
claude --permission-mode auto -p "fix all lint errors"
```
별도 classifier가 명령을 검토해 위험한 것만 차단. CI 파이프라인에 적합.
---
## 7. 흔한 실수 패턴
| 패턴 | 문제 | 해결 |
|---|---|---|
| 주방 싱크 세션 | 무관한 작업이 섞여 컨텍스트 오염 | 작업 사이마다 `/clear` |
| 반복 수정 | 틀린 → 수정 → 또 틀린 → 컨텍스트 오염 | 두 번 실패 후 `/clear` + 더 구체적인 프롬프트로 재시작 |
| 비대한 CLAUDE.md | 규칙이 묻혀서 무시됨 | 규칙 줄이고, hook으로 이전 |
| 신뢰 후 검증 없음 | 그럴듯하지만 엣지케이스 미처리 | 항상 검증 수단 제공 (테스트, 스크린샷) |
| 무한 탐색 | 수백 개 파일 읽어 컨텍스트 폭발 | 조사 범위 좁게, 또는 subagent 사용 |

View File

@@ -0,0 +1,106 @@
# Claude Code 전체 스택 이해: MCP, Skills, Subagents, Hooks
> 출처: https://alexop.dev/posts/understanding-claude-code-full-stack/
> 수집일: 2026-05-13
---
## 개요
Claude Code는 CLI, VS Code, JetBrains, 데스크탑, 웹, iOS 등 다양한 플랫폼에서 AI 에이전트를 오케스트레이션하는 **프레임워크**다. 5개 핵심 컴포넌트가 서로 쌓여서 포괄적인 자동화 시스템을 만든다.
---
## 핵심 컴포넌트 5개
### 1. MCP (Model Context Protocol) — 기반
Claude Code를 외부 도구 및 데이터 소스와 연결하는 **범용 어댑터**.
- `/` slash 명령어로 MCP 서버 기능에 접근
- HTTP transport + OAuth 2.1 with PKCE 인증 지원 (remote server)
- ⚠️ MCP 서버는 명시적으로 제공하지 않으면 Claude의 native 도구를 상속하지 않는다
```bash
claude mcp add # MCP 서버 연결
```
### 2. Project Memory (CLAUDE.md) — 영속 컨텍스트
마크다운 파일로 영속적인 프로젝트 컨텍스트 설정. 계층적으로 병합됨:
**Enterprise → User → Project**
- 코딩 표준, 아키텍처 패턴, 공통 명령어
- `.claude/rules/`로 별도 파일 분리 가능
- `CLAUDE.local.md`: 팀 공유 없는 개인 설정
### 3. Skills — 통합 확장 모델
재사용 가능한 워크플로우와 도메인 지식. CLAUDE.md의 모듈화 버전.
frontmatter로 동작 제어:
- `auto-invoke`: Claude가 컨텍스트 기반으로 자동 적용
- 사용자가 `/slash-command`로 수동 트리거
- `disable-model-invocation: true`: 수동 전용 (사이드 이펙트 있는 워크플로우)
- `context: fork`: 독립 subagent로 실행
### 4. Subagents — 독립 컨텍스트
전문화된 AI 페르소나, 독립 컨텍스트 윈도우.
- "컨텍스트 오염" 방지 (무거운 작업 격리)
- 2026 업데이트: **worktree isolation** — 여러 subagent가 파일을 병렬 편집 가능
- 내장 타입: Explore, Plan, General-purpose
### 5. Hooks — 이벤트 기반 강제
`.claude/settings.json`에 설정. 수동 호출 없이 자동 실행.
이벤트 종류:
- 도구 라이프사이클 (PreToolUse, PostToolUse)
- 세션 관리
- 파일 변경
- 권한
핸들러 타입: command, prompt, HTTP request, async
> PreToolUse hooks는 도구 실행 전 인수를 수정할 수 있다.
---
## 지원 기능
**Plugins**: skills, hooks, 메타데이터를 패키징해서 팀 배포.
**Scheduled Tasks**: 클라우드 인프라에서 cron 스케줄로 실행 — 사용자 없이 반복 자동화.
---
## 용도별 선택 가이드
| 기능 | 사용 시점 |
|---|---|
| CLAUDE.md | 영속이 필요한 정적 지식 |
| Skills | 재사용 워크플로우 (자동/수동) |
| Subagents | 병렬 실행 및 격리가 필요할 때 |
| Hooks | 표준 강제, 이벤트 반응 |
| MCP | 외부 시스템 연동 |
| Scheduled Tasks | 사용자 없는 반복 작업 |
---
## 실전 워크플로우 예시 (통합)
1. **CLAUDE.md**: 표준 수립
2. **load-context skill**: 새 채팅 시작 시 컨텍스트 초기화
3. **documentation skill** (auto-invoke): 구현 후 자동 문서 업데이트
4. **hooks**: 린팅 강제
5. **subagents**: 격리된 구현 작업 처리
---
## 핵심 인사이트
> "대부분의 사람들은 기능 한두 개만 쓰고 어떻게 스택으로 연결되는지 보지 못한다."
>
> 이 컴포넌트들을 단순한 코딩 도구가 아니라 **컴퓨터 자동화를 위한 통합 시스템**으로 봐야 한다.

View File

@@ -0,0 +1,154 @@
# Claude Code Hooks: 프로덕션 CI/CD 패턴 완전 가이드
> 출처: https://www.pixelmojo.io/blogs/claude-code-hooks-production-quality-ci-cd-patterns
> 수집일: 2026-05-13
---
## 개요
Claude Code hooks는 특정 라이프사이클 포인트에서 자동으로 실행되는 강제 메커니즘. 2026년 초 출시. 코딩 표준을 "권고 사항"에서 "Claude가 코드베이스에 접근할 때마다 실행되는 강제 게이트"로 전환.
---
## 핸들러 타입 3가지
### 1. Command Hooks
shell script 실행. JSON을 stdin으로 받고 exit code로 결과 반환.
→ 포맷팅, 린팅, 파일 검증에 적합.
### 2. Prompt Hooks
`$ARGUMENTS` 플레이스홀더로 컨텍스트를 주입해 Claude 모델에 평가 요청.
→ regex로 자동화하기 어려운 **의미론적 결정** (보안 분류 등)에 적합.
### 3. Agent Hooks
도구 접근 권한 있는 subagent 실행 (Read, Grep, Glob).
→ 크로스 파일 검증, 아키텍처 패턴 준수 검사에 적합.
---
## 12개 라이프사이클 이벤트
핵심 이벤트:
- **PreToolUse** (blocking): 실행 전 평가. **유일하게 작업을 거부할 수 있는 훅 타입**
- **PostToolUse** (non-blocking): 실행 후 포맷팅·검증
- **Stop** (non-blocking): Claude 응답 완료 시
- **SubagentStop** (non-blocking): subagent 완료 검증
---
## 프로덕션 패턴
### 자동 포맷팅 (PostToolUse)
```json
{
"hooks": {
"PostToolUse": [{
"matcher": "Edit|Write",
"handler": {
"type": "command",
"command": "npx prettier --write $FILEPATH"
}
}]
}
}
```
### 중요 파일 보호 (PreToolUse)
```json
{
"hooks": {
"PreToolUse": [{
"matcher": "Edit",
"handler": {
"type": "command",
"command": "node scripts/block-critical.js"
}
}]
}
}
```
### 의존성 가드 (PreToolUse)
프로덕션 패키지 무단 설치 차단:
```json
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"handler": {
"type": "command",
"command": "node scripts/block-deps.js"
}
}]
}
}
```
### Prompt 기반 보안 (PreToolUse)
컨텍스트 의존적 결정을 Claude에 위임:
```json
{
"hooks": {
"PreToolUse": [{
"matcher": "Edit",
"handler": {
"type": "prompt",
"prompt": "Analyze edit: $ARGUMENTS. Check for auth, DB, payments. DENY if any touched."
}
}]
}
}
```
---
## CI/CD 통합
GitHub Actions, GitLab CI와 동일한 hooks 실행. PR/MR 생성 시 트리거 → 결과를 PR 코멘트로 게시 → 모든 품질 게이트 통과 전까지 merge 차단.
통합 포인트:
- PR opened/updated → PostToolUse: lint + type check
- MR opened/updated → PostToolUse: lint + security scan
- git commit → Stop: 전체 품질 스위트
- Scheduled tasks → Agent: 코드베이스 감사
---
## 경쟁 도구 비교
| 기능 | Claude Code | Cursor | Copilot |
|---|---|---|---|
| 핸들러 타입 | Command, Prompt, **Agent** | Command만 | Command만 |
| 작업 차단 가능 | ✅ PreToolUse | ✅ | ✅ |
| LLM 기반 훅 | ✅ | ❌ | ❌ |
| Subagent 훅 | ✅ | ❌ | ❌ |
---
## 설정 파일 위치
- `.claude/settings.json`: 프로젝트 레벨 (팀 공유)
- `~/.claude/settings.json`: 개인 기본값
- 프로젝트 설정이 우선 적용
---
## 도입 로드맵 (단계별)
1. **Phase 1**: PostToolUse 포맷팅 훅 (리스크 없음, 즉각 효과)
2. **Phase 2**: 중요 경로 파일 보호 PreToolUse
3. **Phase 3**: 공급망 공격 대비 의존성 가드
4. **Phase 4**: 의미론적 보안 검토를 위한 Prompt 훅
5. **Phase 5**: 아키텍처 심층 검증을 위한 Agent 훅
---
## 기술 부채 방지 효과 (통계)
- **66% 생산성 세금**: PostToolUse 훅이 포맷팅·타입 오류를 즉시 잡아냄
- **41% 코드 churne**: PreToolUse prompt 훅이 아키텍처 패턴 검증 → 리팩토링 필요 감소
- **45% 취약점 비율**: PreToolUse 보안 훅이 리뷰 없는 민감 영역 편집 차단
> 인간 리뷰어는 아키텍처, 비즈니스 로직, 시스템 설계 등 섬세한 결정에 집중.
> 반복 가능한 검증 작업은 훅이 처리.

View File

@@ -0,0 +1,75 @@
# 장기 실행 에이전트를 위한 효과적인 Harness 설계
> 출처: https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents
> (Anthropic 엔지니어링 블로그)
> 수집일: 2026-05-13
---
## 핵심 문제
AI 에이전트는 여러 컨텍스트 윈도우에 걸쳐 일관성을 유지하기 어렵다. 컨텍스트 한계 때문에, 몇 시간 또는 며칠 걸리는 복잡한 작업은 여러 세션으로 나눠야 하는데, 각 세션은 이전 작업을 기억하지 못한 채 시작된다.
---
## 해결책: 2단계 구조
### 1단계: Initializer Agent (첫 번째 세션)
기반을 마련하는 역할:
- `init.sh` 스크립트 생성 — 환경 설정
- `claude-progress.txt` 파일 생성 — 진행 상황 추적 로그
- 초기 git commit (추가된 파일 기록)
- 200+ 항목의 기능 목록 작성 (claude.ai 클론 예시 기준)
### 2단계: Coding Agent (이후 세션들)
점진적 진행에 집중:
- progress 파일과 git 히스토리 읽어서 컨텍스트 복원
- 한 번에 기능 하나씩 처리
- 코드를 "merge-ready" 상태 유지 + 명확한 커밋
- 완료 표시 전 철저한 테스트 필수
---
## 실패 모드와 해결책
| 문제 | 해결책 |
|---|---|
| 에이전트가 전체 프로젝트를 한 번에 완성하려 함 | 기능을 "failing" 상태로 목록화, 구조화된 feature list 강제 |
| 에이전트가 미완성 상태에서 "완료" 선언 | 포괄적 기능 추적 + 한 번에 하나의 기능만 허용 |
| 테스트 없이 완료 표시 | Puppeteer MCP로 브라우저 자동화 테스트 의무화 |
| 버그를 코드베이스에 기록 없이 방치 | 설명적 커밋 메시지 + progress 파일 업데이트 강제 |
---
## 환경 관리 실천법
### Feature List
JSON 파일로 관리. 에이전트가 임의로 수정하지 못하게 구조 고정; "passing" 상태만 변경 가능.
### 점진적 작업
세션당 기능 하나. 컨텍스트 과부하 방지.
### 테스트 도구
브라우저 자동화(Puppeteer MCP)로 코드 검사로는 잡기 어려운 버그 검출.
### Startup Routine
매 세션 시작 시:
1. `pwd` (현재 디렉토리 확인)
2. progress 파일 읽기
3. 다음 기능 선택
4. `init.sh` 실행
5. 핵심 기능 동작 확인
6. 점진적 변경 구현
---
## 핵심 인사이트
> "에이전트가 스스로 무엇을 했는지 추적하게 하라.
> 인간이 세션 사이 컨텍스트를 복구해주는 역할을 하면 안 된다."
- `claude-progress.txt` 같은 파일은 세션 간 "메모리"를 대신한다
- git 커밋 히스토리도 에이전트의 진행 상황 추적에 활용 가능
- 초기화 스크립트로 각 세션이 동일한 환경에서 시작하도록 보장

27
reference/00_Index.md Normal file
View File

@@ -0,0 +1,27 @@
# 🟢 Nuxt Official Wiki (MOC)
최근 업데이트: 2026-05-12 (Source: Nuxt GitHub)
## 🗺️ 문서 지도
### 1. [Getting Started](./1.getting-started/01.introduction.md)
- 시작하기 전에 알아야 할 기본 개념과 설치 방법
### 2. [Directory Structure](./2.directory-structure/)
- Nuxt 프로젝트의 각 폴더별 역할과 특징
### 3. [Guide](./3.guide/)
- 실무 핵심: 라우팅, 데이터 페칭, 상태 관리, 미들웨어 등
### 4. [API](./4.api/)
- Composables, Components, Utilities 상세 레퍼런스
### 5. [Migration & Bridge](./7.migration/)
- 버전 업그레이드 및 브릿지 가이드
---
## 🛠️ 활용 팁 (Obsidian)
- `Ctrl + O` (또는 `Cmd + O`): 문서 제목으로 검색
- `Ctrl + Shift + F`: 전체 내용 검색
- `[[Link]]`: 문서 간 연결 확인

View File

@@ -0,0 +1,3 @@
title: Get Started
titleTemplate: '%s · Get Started with Nuxt'
icon: i-lucide-rocket

View File

@@ -0,0 +1,81 @@
---
title: Introduction
description: Nuxt's goal is to make web development intuitive and performant with a great Developer Experience in mind.
navigation:
icon: i-lucide-info
---
Nuxt is a free and [open-source framework](https://github.com/nuxt/nuxt) with an intuitive and extendable way to create type-safe, performant and production-grade full-stack web applications and websites with [Vue.js](https://vuejs.org).
We made everything so you can start writing `.vue` files from the beginning while enjoying hot module replacement in development and a performant application in production with server-side rendering by default.
Nuxt has no vendor lock-in, allowing you to deploy your application [**everywhere, even on the edge**](/blog/nuxt-on-the-edge).
::tip
If you want to play around with Nuxt in your browser, you can [try it out in one of our online sandboxes](/docs/4.x/getting-started/installation#play-online).
::
## Automation and Conventions
Nuxt uses conventions and an opinionated directory structure to automate repetitive tasks and allow developers to focus on pushing features. The configuration file can still customize and override its default behaviors.
- **File-based routing:** define routes based on the structure of your [`app/pages/` directory](/docs/4.x/directory-structure/app/pages). This can make it easier to organize your application and avoid the need for manual route configuration.
- **Code splitting:** Nuxt automatically splits your code into smaller chunks, which can help reduce the initial load time of your application.
- **Server-side rendering out of the box:** Nuxt comes with built-in SSR capabilities, so you don't have to set up a separate server yourself.
- **Auto-imports:** write Vue composables and components in their respective directories and use them without having to import them with the benefits of tree-shaking and optimized JS bundles.
- **Data-fetching utilities:** Nuxt provides composables to handle SSR-compatible data fetching as well as different strategies.
- **Zero-config TypeScript support:** write type-safe code without having to learn TypeScript with our auto-generated types and `tsconfig.json`.
- **Configured build tools:** we use [Vite](https://vite.dev) by default to support hot module replacement (HMR) in development and bundling your code for production with best-practices baked-in.
Nuxt takes care of these and provides both frontend and backend functionality so you can focus on what matters: **creating your web application**.
## Server-Side Rendering
Nuxt comes with built-in server-side rendering (SSR) capabilities by default, without having to configure a server yourself, which has many benefits for web applications:
- **Faster initial page load time:** Nuxt sends a fully rendered HTML page to the browser, which can be displayed immediately. This can provide a faster perceived page load time and a better user experience (UX), especially on slower networks or devices.
- **Improved SEO:** search engines can better index SSR pages because the HTML content is available immediately, rather than requiring JavaScript to render the content on the client-side.
- **Better performance on low-powered devices:** it reduces the amount of JavaScript that needs to be downloaded and executed on the client-side, which can be beneficial for low-powered devices that may struggle with processing heavy JavaScript applications.
- **Better accessibility:** the content is immediately available on the initial page load, improving accessibility for users who rely on screen readers or other assistive technologies.
- **Easier caching:** pages can be cached on the server-side, which can further improve performance by reducing the amount of time it takes to generate and send the content to the client.
Overall, server-side rendering can provide a faster and more efficient user experience, as well as improve search engine optimization and accessibility.
As Nuxt is a versatile framework, it gives you the possibility to statically render your whole application to a static hosting with `nuxt generate`,
disable SSR globally with the `ssr: false` option or leverage hybrid rendering by setting up the `routeRules` option.
:read-more{title="Nuxt rendering modes" to="/docs/4.x/guide/concepts/rendering"}
### Server engine
The Nuxt server engine [Nitro](https://nitro.build/) unlocks new full-stack capabilities.
In development, it uses Rollup and Node.js workers for your server code and context isolation. It also generates your server API by reading files in `server/api/` and server middleware from `server/middleware/`.
In production, Nitro builds your app and server into one universal `.output` directory. This output is light: minified and removed from any Node.js modules (except polyfills). You can deploy this output on any system supporting JavaScript, from Node.js, Serverless, Workers, Edge-side rendering or purely static.
:read-more{title="Nuxt server engine" to="/docs/4.x/guide/concepts/server-engine"}
### Production-ready
A Nuxt application can be deployed on a Node or Deno server, pre-rendered to be hosted in static environments, or deployed to serverless and edge providers.
:read-more{title="Deployment section" to="/docs/4.x/getting-started/deployment"}
### Modular
A module system allows you to extend Nuxt with custom features and integrations with third-party services.
:read-more{title="Nuxt Modules Concept" to="/docs/4.x/guide/concepts/modules"}
### Architecture
Nuxt is composed of different [core packages](https://github.com/nuxt/nuxt/tree/main/packages):
- Core engine: [nuxt](https://github.com/nuxt/nuxt/tree/main/packages/nuxt)
- Bundlers: [@nuxt/vite-builder](https://github.com/nuxt/nuxt/tree/main/packages/vite), [@nuxt/rspack-builder](https://github.com/nuxt/nuxt/tree/main/packages/rspack) and [@nuxt/webpack-builder](https://github.com/nuxt/nuxt/tree/main/packages/webpack)
- Command line interface: [@nuxt/cli](https://github.com/nuxt/cli)
- Server engine: [nitro](https://github.com/nitrojs/nitro)
- Development kit: [@nuxt/kit](https://github.com/nuxt/nuxt/tree/main/packages/kit)
We recommend reading each concept to have a full vision of Nuxt capabilities and the scope of each package.

View File

@@ -0,0 +1,116 @@
---
title: 'Installation'
description: 'Get started with Nuxt quickly with our online starters or start locally with your terminal.'
navigation.icon: i-lucide-play
---
## Play Online
If you just want to play around with Nuxt in your browser without setting up a project, you can use one of our online sandboxes:
::card-group
:card{title="Open on StackBlitz" icon="i-simple-icons-stackblitz" to="https://nuxt.new/s/v4" target="_blank"}
:card{title="Open on CodeSandbox" icon="i-simple-icons-codesandbox" to="https://nuxt.new/c/v4" target="_blank"}
::
Or follow the steps below to set up a new Nuxt project on your computer.
## New Project
### Prerequisites
- **Node.js** - [`20.x`](https://nodejs.org/en) or newer (but we recommend the [active LTS release](https://github.com/nodejs/release#release-schedule))
- **Text editor** - There is no IDE requirement, but we recommend [Visual Studio Code](https://code.visualstudio.com/) with the [official Vue extension](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (previously known as Volar) or [WebStorm](https://www.jetbrains.com/webstorm/), which, along with [other JetBrains IDEs](https://www.jetbrains.com/ides/), offers great Nuxt support right out-of-the-box. If you use another editor, such as Neovim, you can configure [Vue Language Server](https://github.com/vuejs/language-tools) support by following the [Vue Language Tools setup guides](https://github.com/vuejs/language-tools/wiki).
- **Terminal** - In order to run Nuxt commands
::note
::details
:summary[Additional notes for an optimal setup:]
- **Node.js**: Make sure to use an even numbered version (20, 22, etc.)
- **Neovim**: When configuring the Vue TypeScript plugin, make sure `location` points to the `@vue/language-server` package directory, not its binary. See the [Neovim setup guide](https://github.com/vuejs/language-tools/wiki/Neovim) for a working configuration.
- **WSL**: If you are using Windows and experience slow HMR, you may want to try using [WSL (Windows Subsystem for Linux)](https://learn.microsoft.com/en-us/windows/wsl/install) which may solve some performance issues.
- **Windows slow DNS resolution**: Instead of using `localhost:3000` for local dev server on Windows, use `127.0.0.1` for much faster loading experience on browsers.
::
::
Open a terminal (if you're using [Visual Studio Code](https://code.visualstudio.com), you can open an [integrated terminal](https://code.visualstudio.com/docs/terminal/basics)) and use the following command to create a new starter project:
::code-group{sync="pm"}
```bash [npm]
npm create nuxt@latest <project-name>
```
```bash [yarn]
yarn create nuxt <project-name>
```
```bash [pnpm]
pnpm create nuxt@latest <project-name>
```
```bash [bun]
bun create nuxt@latest <project-name>
```
```bash [deno]
deno -A npm:create-nuxt@latest <project-name>
```
::
::tip
Alternatively, you can find other starters or themes by opening [nuxt.new](https://nuxt.new) and following the instructions there.
::
Open your project folder in Visual Studio Code:
```bash [Terminal]
code <project-name>
```
Or change directory into your new project from your terminal:
```bash
cd <project-name>
```
## Development Server
Now you'll be able to start your Nuxt app in development mode:
::code-group{sync="pm"}
```bash [npm]
npm run dev -- -o
```
```bash [yarn]
yarn dev --open
```
```bash [pnpm]
pnpm dev -o
```
```bash [bun]
bun run dev -o
# To use the Bun runtime during development
# bun --bun run dev -o
```
```bash [deno]
deno run dev -o
```
::
::tip{icon="i-lucide-circle-check"}
Well done! A browser window should automatically open for <http://localhost:3000>.
::
## Next Steps
Now that you've created your Nuxt project, you are ready to start building your application.
:read-more{title="Nuxt Concepts" to="/docs/4.x/guide/concepts"}

View File

@@ -0,0 +1,226 @@
---
title: Configuration
description: Nuxt is configured with sensible defaults to make you productive.
navigation.icon: i-lucide-cog
---
By default, Nuxt is configured to cover most use cases. The [`nuxt.config.ts`](/docs/4.x/directory-structure/nuxt-config) file can override or extend this default configuration.
## Nuxt Configuration
The [`nuxt.config.ts`](/docs/4.x/directory-structure/nuxt-config) file is located at the root of a Nuxt project and can override or extend the application's behavior.
A minimal configuration file exports the `defineNuxtConfig` function containing an object with your configuration. The `defineNuxtConfig` helper is globally available without import.
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
// My Nuxt config
})
```
This file will often be mentioned in the documentation, for example to add custom scripts, register modules or change rendering modes.
::read-more{to="/docs/4.x/api/configuration/nuxt-config"}
Every option is described in the **Configuration Reference**.
::
::note
You don't have to use TypeScript to build an application with Nuxt. However, it is strongly recommended to use the `.ts` extension for the `nuxt.config` file. This way you can benefit from hints in your IDE to avoid typos and mistakes while editing your configuration.
::
### Environment Overrides
You can configure fully typed, per-environment overrides in your nuxt.config
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
$production: {
routeRules: {
'/**': { isr: true },
},
},
$development: {
//
},
$env: {
staging: {
//
},
},
})
```
To select an environment when running a Nuxt CLI command, simply pass the name to the `--envName` flag, like so: `nuxt build --envName staging`.
To learn more about the mechanism behind these overrides, please refer to the `c12` documentation on [environment-specific configuration](https://github.com/unjs/c12?tab=readme-ov-file#environment-specific-configuration).
:video-accordion{title="Watch a video from Alexander Lichter about the env-aware nuxt.config.ts" videoId="DFZI2iVCrNc"}
::note
If you're authoring layers, you can also use the `$meta` key to provide metadata that you or the consumers of your layer might use.
::
### Environment Variables and Private Tokens
The `runtimeConfig` API exposes values like environment variables to the rest of your application. By default, these keys are only available server-side. The keys within `runtimeConfig.public` and `runtimeConfig.app` (which is used by Nuxt internally) are also available client-side.
Those values should be defined in `nuxt.config` and can be overridden using environment variables.
::code-group
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
runtimeConfig: {
// The private keys which are only available server-side
apiSecret: '123',
// Keys within public are also exposed client-side
public: {
apiBase: '/api',
},
},
})
```
```ini [.env]
# This will override the value of apiSecret
NUXT_API_SECRET=api_secret_token
```
::
These variables are exposed to the rest of your application using the [`useRuntimeConfig()`](/docs/4.x/api/composables/use-runtime-config) composable.
```vue [app/pages/index.vue]
<script setup lang="ts">
const runtimeConfig = useRuntimeConfig()
</script>
```
:read-more{to="/docs/4.x/guide/going-further/runtime-config"}
## App Configuration
The `app.config.ts` file, located in the source directory (by default `app/`), is used to expose public variables that can be determined at build time. Contrary to the `runtimeConfig` option, these cannot be overridden using environment variables.
A minimal configuration file exports the `defineAppConfig` function containing an object with your configuration. The `defineAppConfig` helper is globally available without import.
```ts [app/app.config.ts]
export default defineAppConfig({
title: 'Hello Nuxt',
theme: {
dark: true,
colors: {
primary: '#ff0000',
},
},
})
```
These variables are exposed to the rest of your application using the [`useAppConfig`](/docs/4.x/api/composables/use-app-config) composable.
```vue [app/pages/index.vue]
<script setup lang="ts">
const appConfig = useAppConfig()
</script>
```
:read-more{to="/docs/4.x/directory-structure/app/app-config"}
## `runtimeConfig` vs. `app.config`
As stated above, `runtimeConfig` and `app.config` are both used to expose variables to the rest of your application. To determine whether you should use one or the other, here are some guidelines:
- `runtimeConfig`: Private or public tokens that need to be specified after build using environment variables.
- `app.config`: Public tokens that are determined at build time, website configuration such as theme variant, title and any project config that are not sensitive.
| Feature | `runtimeConfig` | `app.config` |
|---------------------------|-----------------|--------------|
| Client-side | Hydrated | Bundled |
| Environment variables | ✅ Yes | ❌ No |
| Reactive | ✅ Yes | ✅ Yes |
| Types support | ✅ Partial | ✅ Yes |
| Configuration per request | ❌ No | ✅ Yes |
| Hot module replacement | ❌ No | ✅ Yes |
| Non-primitive JS types | ❌ No | ✅ Yes |
## External Configuration Files
Nuxt uses [`nuxt.config.ts`](/docs/4.x/directory-structure/nuxt-config) file as the single source of truth for configurations and skips reading external configuration files. During the course of building your project, you may have a need to configure those. The following table highlights common configurations and, where applicable, how they can be configured with Nuxt.
| Name | Config File | How To Configure |
|-----------------------------------|-------------------------|---------------------------------------------------------------------------|
| [Nitro](https://nitro.build) | ~~`nitro.config.ts`~~ | Use [`nitro`](/docs/4.x/api/nuxt-config#nitro) key in `nuxt.config` |
| [PostCSS](https://postcss.org) | ~~`postcss.config.js`~~ | Use [`postcss`](/docs/4.x/api/nuxt-config#postcss) key in `nuxt.config` |
| [Vite](https://vite.dev) | ~~`vite.config.ts`~~ | Use [`vite`](/docs/4.x/api/nuxt-config#vite) key in `nuxt.config` |
| [webpack](https://webpack.js.org) | ~~`webpack.config.ts`~~ | Use [`webpack`](/docs/4.x/api/nuxt-config#webpack-1) key in `nuxt.config` |
Here is a list of other common config files:
| Name | Config File | How To Configure |
|----------------------------------------------|-----------------------|-------------------------------------------------------------------------------|
| [TypeScript](https://www.typescriptlang.org) | `tsconfig.json` | [More Info](/docs/4.x/directory-structure/tsconfig) |
| [ESLint](https://eslint.org) | `eslint.config.js` | [More Info](https://eslint.org/docs/latest/use/configure/configuration-files) |
| [Prettier](https://prettier.io) | `prettier.config.js` | [More Info](https://prettier.io/docs/configuration.html) |
| [Stylelint](https://stylelint.io) | `stylelint.config.js` | [More Info](https://stylelint.io/user-guide/configure/) |
| [TailwindCSS](https://tailwindcss.com) | `tailwind.config.js` | [More Info](https://tailwindcss.nuxtjs.org/tailwindcss/configuration/) |
| [Vitest](https://vitest.dev) | `vitest.config.ts` | [More Info](https://vitest.dev/config/) |
## Vue Configuration
### With Vite
If you need to pass options to `@vitejs/plugin-vue` or `@vitejs/plugin-vue-jsx`, you can do this in your `nuxt.config` file.
- `vite.vue` for `@vitejs/plugin-vue`. Check [available options](https://github.com/vitejs/vite-plugin-vue/tree/main/packages/plugin-vue).
- `vite.vueJsx` for `@vitejs/plugin-vue-jsx`. Check [available options](https://github.com/vitejs/vite-plugin-vue/tree/main/packages/plugin-vue-jsx).
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
vite: {
vue: {
customElement: true,
},
vueJsx: {
mergeProps: true,
},
},
})
```
:read-more{to="/docs/4.x/api/configuration/nuxt-config#vue"}
### With webpack
If you use webpack and need to configure `vue-loader`, you can do this using `webpack.loaders.vue` key inside your `nuxt.config` file. The available options are [defined here](https://github.com/vuejs/vue-loader/blob/main/src/index.ts#L32-L62).
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
webpack: {
loaders: {
vue: {
hotReload: true,
},
},
},
})
```
:read-more{to="/docs/4.x/api/configuration/nuxt-config#loaders"}
### Enabling Experimental Vue Features
You may need to enable experimental features in Vue, such as `propsDestructure`. Nuxt provides an easy way to do that in `nuxt.config.ts`, no matter which builder you are using:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
vue: {
propsDestructure: true,
},
})
```
#### experimental `reactivityTransform` migration from Vue 3.4 and Nuxt 3.9
Since Nuxt 3.9 and Vue 3.4, `reactivityTransform` has been moved from Vue to Vue Macros which has a [Nuxt integration](https://vue-macros.dev/guide/nuxt-integration.html).
:read-more{to="/docs/4.x/api/configuration/nuxt-config#vue-1"}

View File

@@ -0,0 +1,167 @@
---
title: 'Views'
description: 'Nuxt provides several component layers to implement the user interface of your application.'
navigation.icon: i-lucide-panels-top-left
---
## `app.vue`
![The app.vue file is the entry point of your application](/assets/docs/getting-started/views/app.svg)
By default, Nuxt will treat this file as the **entrypoint** and render its content for every route of the application.
```vue [app/app.vue]
<template>
<div>
<h1>Welcome to the homepage</h1>
</div>
</template>
```
::tip
If you are familiar with Vue, you might wonder where `main.js` is (the file that normally creates a Vue app). Nuxt does this behind the scene.
::
## Components
![Components are reusable pieces of UI](/assets/docs/getting-started/views/components.svg)
Most components are reusable pieces of the user interface, like buttons and menus. In Nuxt, you can create these components in the [`app/components/`](/docs/4.x/directory-structure/app/components) directory, and they will be automatically available across your application without having to explicitly import them.
::code-group
```vue [app/app.vue]
<template>
<div>
<h1>Welcome to the homepage</h1>
<AppAlert>
This is an auto-imported component.
</AppAlert>
</div>
</template>
```
```vue [app/components/AppAlert.vue]
<template>
<span>
<slot />
</span>
</template>
```
::
## Pages
![Pages are views tied to a specific route](/assets/docs/getting-started/views/pages.svg)
Pages represent views for each specific route pattern. Every file in the [`app/pages/`](/docs/4.x/directory-structure/app/pages) directory represents a different route displaying its content.
To use pages, create an `app/pages/index.vue` file and add `<NuxtPage />` component to the [`app/app.vue`](/docs/4.x/directory-structure/app/app) (or remove `app/app.vue` for default entry). You can now create more pages and their corresponding routes by adding new files in the [`app/pages/`](/docs/4.x/directory-structure/app/pages) directory.
::code-group
```vue [app/pages/index.vue]
<template>
<div>
<h1>Welcome to the homepage</h1>
<AppAlert>
This is an auto-imported component
</AppAlert>
</div>
</template>
```
```vue [app/pages/about.vue]
<template>
<section>
<p>This page will be displayed at the /about route.</p>
</section>
</template>
```
::
:read-more{title="Routing Section" to="/docs/4.x/getting-started/routing"}
## Layouts
![Layouts are wrapper around pages](/assets/docs/getting-started/views/layouts.svg)
Layouts are wrappers around pages that contain a common User Interface for several pages, such as header and footer displays. Layouts are Vue files using `<slot />` components to display the **page** content. The `app/layouts/default.vue` file will be used by default. Custom layouts can be set as part of your page metadata.
::note
If you only have a single layout in your application, we recommend using [`app/app.vue`](/docs/4.x/directory-structure/app/app) with [`<NuxtPage />`](/docs/4.x/api/components/nuxt-page) instead.
::
::code-group
```vue [app/app.vue]
<template>
<div>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</div>
</template>
```
```vue [app/layouts/default.vue]
<template>
<div>
<AppHeader />
<slot />
<AppFooter />
</div>
</template>
```
```vue [app/pages/index.vue]
<template>
<div>
<h1>Welcome to the homepage</h1>
<AppAlert>
This is an auto-imported component
</AppAlert>
</div>
</template>
```
```vue [app/pages/about.vue]
<template>
<section>
<p>This page will be displayed at the /about route.</p>
</section>
</template>
```
::
If you want to create more layouts and learn how to use them in your pages, find more information in the [Layouts section](/docs/4.x/directory-structure/app/layouts).
## Advanced: Extending the HTML Template
::note
If you only need to modify the `<head>`, you can refer to the [SEO and meta section](/docs/4.x/getting-started/seo-meta).
::
You can have full control over the HTML template by adding a Nitro plugin that registers a hook.
The callback function of the `render:html` hook allows you to mutate the HTML before it is sent to the client.
<!-- TODO: figure out how to use twoslash to inject types for a different context -->
```ts [server/plugins/extend-html.ts]
import { definePlugin } from 'nitro'
export default definePlugin((nitroApp) => {
nitroApp.hooks.hook('render:html', (html, { event }) => {
// This will be an object representation of the html template.
console.log(html)
html.head.push(`<meta name="description" content="My custom description" />`)
})
// You can also intercept the response here.
nitroApp.hooks.hook('render:response', (response, { event }) => { console.log(response) })
})
```
:read-more{to="/docs/4.x/guide/going-further/hooks"}

View File

@@ -0,0 +1,54 @@
---
title: 'Assets'
description: 'Nuxt offers two options for your assets.'
navigation.icon: i-lucide-image
---
Nuxt uses two directories to handle assets like stylesheets, fonts or images.
- The [`public/`](/docs/4.x/directory-structure/public) directory content is served at the server root as-is.
- The [`app/assets/`](/docs/4.x/directory-structure/app/assets) directory contains by convention every asset that you want the build tool (Vite or webpack) to process.
## Public Directory
The [`public/`](/docs/4.x/directory-structure/public) directory is used as a public server for static assets publicly available at a defined URL of your application.
You can get a file in the [`public/`](/docs/4.x/directory-structure/public) directory from your application's code or from a browser by the root URL `/`.
### Example
For example, referencing an image file in the `public/img/` directory, available at the static URL `/img/nuxt.png`:
```vue [app/app.vue]
<template>
<img
src="/img/nuxt.png"
alt="Discover Nuxt"
>
</template>
```
## Assets Directory
Nuxt uses [Vite](https://vite.dev/guide/assets) (default) or [webpack](https://webpack.js.org/guides/asset-management/) to build and bundle your application. The main function of these build tools is to process JavaScript files, but they can be extended through [plugins](https://vite.dev/plugins/) (for Vite) or [loaders](https://webpack.js.org/loaders/) (for webpack) to process other kinds of assets, like stylesheets, fonts or SVGs. This step transforms the original file, mainly for performance or caching purposes (such as stylesheet minification or browser cache invalidation).
By convention, Nuxt uses the [`app/assets/`](/docs/4.x/directory-structure/app/assets) directory to store these files but there is no auto-scan functionality for this directory, and you can use any other name for it.
In your application's code, you can reference a file located in the [`app/assets/`](/docs/4.x/directory-structure/app/assets) directory by using the `~/assets/` path.
### Example
For example, referencing an image file that will be processed if a build tool is configured to handle this file extension:
```vue [app/app.vue]
<template>
<img
src="~/assets/img/nuxt.png"
alt="Discover Nuxt"
>
</template>
```
::note
Nuxt won't serve files in the [`app/assets/`](/docs/4.x/directory-structure/app/assets) directory at a static URL like `/assets/my-file.png`. If you need a static URL, use the [`public/`](/docs/4.x/getting-started/assets#public-directory) directory.
::

View File

@@ -0,0 +1,583 @@
---
title: 'Styling'
description: 'Learn how to style your Nuxt application.'
navigation.icon: i-lucide-palette
---
Nuxt is highly flexible when it comes to styling. Write your own styles, or reference local and external stylesheets.
You can use CSS preprocessors, CSS frameworks, UI libraries and Nuxt modules to style your application.
## Local Stylesheets
If you're writing local stylesheets, the natural place to put them is the [`app/assets/` directory](/docs/4.x/directory-structure/app/assets).
### Importing Within Components
You can import stylesheets in your pages, layouts and components directly.
You can use a JavaScript import, or a CSS [`@import` statement](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@import).
```vue [app/pages/index.vue]
<script>
// Use a static import for server-side compatibility
import '~/assets/css/first.css'
// Caution: Dynamic imports are not server-side compatible
import('~/assets/css/first.css')
</script>
<style>
@import url("~/assets/css/second.css");
</style>
```
::tip
The stylesheets will be inlined in the HTML rendered by Nuxt.
::
### The CSS Property
You can also use the `css` property in the Nuxt configuration.
The natural place for your stylesheets is the [`app/assets/` directory](/docs/4.x/directory-structure/app/assets). You can then reference its path and Nuxt will include it to all the pages of your application.
```ts [nuxt.config.ts]
export default defineNuxtConfig({
css: ['~/assets/css/main.css'],
})
```
::tip
The stylesheets will be inlined in the HTML rendered by Nuxt, injected globally and present in all pages.
::
### Working With Fonts
Place your local fonts files in your `public/` directory, for example in `public/fonts`. You can then reference them in your stylesheets using `url()`.
```css [assets/css/main.css]
@font-face {
font-family: 'FarAwayGalaxy';
src: url('/fonts/FarAwayGalaxy.woff') format('woff');
font-weight: normal;
font-style: normal;
font-display: swap;
}
```
Then reference your fonts by name in your stylesheets, pages or components:
```vue
<style>
h1 {
font-family: 'FarAwayGalaxy', sans-serif;
}
</style>
```
### Stylesheets Distributed Through NPM
You can also reference stylesheets that are distributed through npm. Let's use the popular `animate.css` library as an example.
::code-group{sync="pm"}
```bash [npm]
npm install animate.css
```
```bash [yarn]
yarn add animate.css
```
```bash [pnpm]
pnpm install animate.css
```
```bash [bun]
bun install animate.css
```
```bash [deno]
deno install npm:animate.css
```
::
Then you can reference it directly in your pages, layouts and components:
```vue [app/app.vue]
<script>
import 'animate.css'
</script>
<style>
@import url("animate.css");
</style>
```
The package can also be referenced as a string in the css property of your Nuxt configuration.
```ts [nuxt.config.ts]
export default defineNuxtConfig({
css: ['animate.css'],
})
```
## External Stylesheets
You can include external stylesheets in your application by adding a link element in the head section of your nuxt.config file. You can achieve this result using different methods. Note that local stylesheets can also be included this way.
You can manipulate the head with the [`app.head`](/docs/4.x/api/nuxt-config#head) property of your Nuxt configuration:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
app: {
head: {
link: [{ rel: 'stylesheet', href: 'https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css' }],
},
},
})
```
### Dynamically Adding Stylesheets
You can use the useHead composable to dynamically set a value in your head in your code.
:read-more{to="/docs/4.x/api/composables/use-head"}
```ts twoslash
useHead({
link: [{ rel: 'stylesheet', href: 'https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css' }],
})
```
Nuxt uses `unhead` under the hood, and you can refer to [its full documentation](https://unhead.unjs.io).
### Modifying The Rendered Head With A Nitro Plugin
If you need more advanced control, you can intercept the rendered html with a hook and modify the head programmatically.
Create a plugin in `~~/server/plugins/my-plugin.ts` like this:
<!-- TODO: figure out how to use twoslash to inject types for a different context -->
```ts [server/plugins/my-plugin.ts]
import { definePlugin } from 'nitro'
export default definePlugin((nitro) => {
nitro.hooks.hook('render:html', (html) => {
html.head.push('<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">')
})
})
```
External stylesheets are render-blocking resources: they must be loaded and processed before the browser renders the page. Web pages that contain unnecessarily large styles take longer to render. You can read more about it on [web.dev](https://web.dev/articles/defer-non-critical-css).
## Using Preprocessors
To use a preprocessor like SCSS, Sass, Less or Stylus, install it first.
::code-group
```bash [Sass & SCSS]
npm install -D sass
```
```bash [Less]
npm install -D less
```
```bash [Stylus]
npm install -D stylus
```
::
The natural place to write your stylesheets is the `app/assets` directory.
You can then import your source files in your `app.vue` (or layouts files) using your preprocessor's syntax.
```vue [app/pages/app.vue]
<style lang="scss">
@use "~/assets/scss/main.scss";
</style>
```
Alternatively, you can use the `css` property of your Nuxt configuration.
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
css: ['~/assets/scss/main.scss'],
})
```
::tip
In both cases, the compiled stylesheets will be inlined in the HTML rendered by Nuxt.
::
If you need to inject code in pre-processed files, like a [Sass partial](https://sass-lang.com/documentation/at-rules/use/#partials) with color variables, you can do so with the Vite [preprocessors options](https://vite.dev/config/shared-options#css-preprocessoroptions).
Create some partials in your `app/assets` directory:
::code-group{sync="preprocessor"}
```scss [assets/_colors.scss]
$primary: #49240F;
$secondary: #E4A79D;
```
```sass [assets/_colors.sass]
$primary: #49240F
$secondary: #E4A79D
```
::
Then in your `nuxt.config` :
::code-group
```ts twoslash [SCSS]
export default defineNuxtConfig({
vite: {
css: {
preprocessorOptions: {
scss: {
additionalData: '@use "~/assets/_colors.scss" as *;',
},
},
},
},
})
```
```ts twoslash [SASS]
export default defineNuxtConfig({
vite: {
css: {
preprocessorOptions: {
sass: {
additionalData: '@use "~/assets/_colors.sass" as *\n',
},
},
},
},
})
```
::
Nuxt uses Vite by default. If you wish to use webpack instead, refer to each preprocessor loader [documentation](https://webpack.js.org/loaders/sass-loader/).
### Preprocessor Workers (Experimental)
Vite has made available an [experimental option](https://vite.dev/config/shared-options#css-preprocessormaxworkers) which can speed up using preprocessors.
You can enable this in your `nuxt.config`:
```ts
export default defineNuxtConfig({
vite: {
css: {
preprocessorMaxWorkers: true, // number of CPUs minus 1
},
},
})
```
::note
This is an experimental option and you should refer to the Vite documentation and [provide feedback](https://github.com/vitejs/vite/discussions/15835).
::
## Single File Components (SFC) Styling
One of the best things about Vue and SFC is how great it is at naturally dealing with styling. You can directly write CSS or preprocessor code in the style block of your components file, therefore you will have fantastic developer experience without having to use something like CSS-in-JS. However if you wish to use CSS-in-JS, you can find 3rd party libraries and modules that support it, such as [pinceau](https://github.com/Tahul/pinceau).
You can refer to the [Vue docs](https://vuejs.org/api/sfc-css-features) for a comprehensive reference about styling components in SFC.
### Class And Style Bindings
You can leverage Vue SFC features to style your components with class and style attributes.
::code-group
```vue [Ref and Reactive]
<script setup lang="ts">
const isActive = ref(true)
const hasError = ref(false)
const classObject = reactive({
'active': true,
'text-danger': false,
})
</script>
<template>
<div
class="static"
:class="{ 'active': isActive, 'text-danger': hasError }"
/>
<div :class="classObject" />
</template>
```
```vue [Computed]
<script setup lang="ts">
const isActive = ref(true)
const error = ref(null)
const classObject = computed(() => ({
'active': isActive.value && !error.value,
'text-danger': error.value && error.value.type === 'fatal',
}))
</script>
<template>
<div :class="classObject" />
</template>
```
```vue [Array]
<script setup lang="ts">
const isActive = ref(true)
const errorClass = ref('text-danger')
</script>
<template>
<div :class="[{ active: isActive }, errorClass]" />
</template>
```
```vue [Style]
<script setup lang="ts">
const activeColor = ref('red')
const fontSize = ref(30)
const styleObject = reactive({ color: 'red', fontSize: '13px' })
</script>
<template>
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }" />
<div :style="[baseStyles, overridingStyles]" />
<div :style="styleObject" />
</template>
```
::
Refer to the [Vue docs](https://vuejs.org/guide/essentials/class-and-style) for more information.
### Dynamic Styles With `v-bind`
You can reference JavaScript variable and expression within your style blocks with the v-bind function.
The binding will be dynamic, meaning that if the variable value changes, the style will be updated.
```vue
<script setup lang="ts">
const color = ref('red')
</script>
<template>
<div class="text">
hello
</div>
</template>
<style>
.text {
color: v-bind(color);
}
</style>
```
### Scoped Styles
The scoped attribute allows you to style components in isolation. The styles declared with this attribute will only apply to this component.
```vue
<template>
<div class="example">
hi
</div>
</template>
<style scoped>
.example {
color: red;
}
</style>
```
### CSS Modules
You can use [CSS Modules](https://github.com/css-modules/css-modules) with the module attribute. Access it with the injected `$style` variable.
```vue
<template>
<p :class="$style.red">
This should be red
</p>
</template>
<style module>
.red {
color: red;
}
</style>
```
### Preprocessors Support
SFC style blocks support preprocessor syntax. Vite comes with built-in support for .scss, .sass, .less, .styl and .stylus files without configuration. You just need to install them first, and they will be available directly in SFC with the lang attribute.
::code-group
```vue [SCSS]
<style lang="scss">
/* Write scss here */
</style>
```
```vue [Sass]
<style lang="sass">
/* Write sass here */
</style>
```
```vue [LESS]
<style lang="less">
/* Write less here */
</style>
```
```vue [Stylus]
<style lang="stylus">
/* Write stylus here */
</style>
```
::
You can refer to the [Vite CSS docs](https://vite.dev/guide/features#css) and the [@vitejs/plugin-vue docs](https://github.com/vitejs/vite-plugin-vue/tree/main/packages/plugin-vue).
For webpack users, refer to the [vue loader docs](https://vue-loader.vuejs.org).
## Using PostCSS
Nuxt comes with postcss built-in. You can configure it in your `nuxt.config` file.
```ts [nuxt.config.ts]
export default defineNuxtConfig({
postcss: {
plugins: {
'postcss-nested': {},
'postcss-custom-media': {},
},
},
})
```
For proper syntax highlighting in SFC, you can use the postcss lang attribute.
```vue
<style lang="postcss">
/* Write postcss here */
</style>
```
By default, Nuxt comes with the following plugins already pre-configured:
- [postcss-import](https://github.com/postcss/postcss-import): Improves the `@import` rule
- [postcss-url](https://github.com/postcss/postcss-url): Transforms `url()` statements
- [autoprefixer](https://github.com/postcss/autoprefixer): Automatically adds vendor prefixes
- [cssnano](https://cssnano.github.io/cssnano/): Minification and purge
## Leveraging Layouts For Multiple Styles
If you need to style different parts of your application completely differently, you can use layouts.
Use different styles for different layouts.
```vue
<template>
<div class="default-layout">
<h1>Default Layout</h1>
<slot />
</div>
</template>
<style>
.default-layout {
color: red;
}
</style>
```
:read-more{to="/docs/4.x/directory-structure/app/layouts"}
## Third Party Libraries And Modules
Nuxt isn't opinionated when it comes to styling and provides you with a wide variety of options. You can use any styling tool that you want, such as popular libraries like [UnoCSS](https://unocss.dev) or [Tailwind CSS](https://tailwindcss.com).
The community and the Nuxt team have developed plenty of Nuxt modules to make the integration easier.
You can discover them on the [modules section](/modules) of the website.
Here are a few modules to help you get started:
- [UnoCSS](/modules/unocss): Instant on-demand atomic CSS engine
- [Tailwind CSS](/modules/tailwindcss): Utility-first CSS framework
- [Fontaine](https://github.com/nuxt-modules/fontaine): Font metric fallback
- [Pinceau](https://github.com/Tahul/pinceau): Adaptable styling framework
- [Nuxt UI](https://ui.nuxt.com): A UI Library for Modern Web Apps
- [Panda CSS](https://panda-css.com/docs/installation/nuxt): CSS-in-JS engine that generates atomic CSS at build time
Nuxt modules provide you with a good developer experience out of the box, but remember that if your favorite tool doesn't have a module, it doesn't mean that you can't use it with Nuxt! You can configure it yourself for your own project. Depending on the tool, you might need to use a [Nuxt plugin](/docs/4.x/directory-structure/app/plugins) and/or [make your own module](/docs/4.x/guide/modules). Share them with the [community](/modules) if you do!
### Easily Load Webfonts
You can use [the Nuxt Google Fonts module](https://github.com/nuxt-modules/google-fonts) to load Google Fonts.
If you are using [UnoCSS](https://unocss.dev/integrations/nuxt), note that it comes with a [web fonts presets](https://unocss.dev/presets/web-fonts) to conveniently load fonts from common providers, including Google Fonts and more.
## Advanced
### Transitions
Nuxt comes with the same `<Transition>` element that Vue has, and also has support for the experimental [View Transitions API](/docs/4.x/getting-started/transitions#view-transitions-api-experimental).
:read-more{to="/docs/4.x/getting-started/transitions"}
### Font Advanced Optimization
We would recommend using [Fontaine](https://github.com/nuxt-modules/fontaine) to reduce your [CLS](https://web.dev/articles/cls). If you need something more advanced, consider creating a Nuxt module to extend the build process or the Nuxt runtime.
::tip
Always remember to take advantage of the various tools and techniques available in the Web ecosystem at large to make styling your application easier and more efficient. Whether you're using native CSS, a preprocessor, postcss, a UI library or a module, Nuxt has got you covered. Happy styling!
::
### LCP Advanced Optimizations
You can do the following to speed-up the download of your global CSS files:
- Use a CDN so the files are physically closer to your users
- Compress your assets, ideally using Brotli
- Use HTTP2/HTTP3 for delivery
- Host your assets on the same domain (do not use a different subdomain)
Most of these things should be done for you automatically if you're using modern platforms like Cloudflare, Netlify or Vercel.
You can find an LCP optimization guide on [web.dev](https://web.dev/articles/optimize-lcp).
If all of your CSS is inlined by Nuxt, you can (experimentally) completely stop external CSS files from being referenced in your rendered HTML.
You can achieve that with a hook, that you can place in a module, or in your Nuxt configuration file.
```ts [nuxt.config.ts]
export default defineNuxtConfig({
hooks: {
'build:manifest': (manifest) => {
// find the app entry, css list
const css = Object.values(manifest).find(options => options.isEntry)?.css
if (css) {
// start from the end of the array and go to the beginning
for (let i = css.length - 1; i >= 0; i--) {
// if it starts with 'entry', remove it from the list
if (css[i].startsWith('entry')) {
css.splice(i, 1)
}
}
}
},
},
})
```

View File

@@ -0,0 +1,153 @@
---
title: 'Routing'
description: Nuxt file-system routing creates a route for every file in the pages/ directory.
navigation.icon: i-lucide-milestone
---
One core feature of Nuxt is the file system router. Every Vue file inside the [`app/pages/`](/docs/4.x/directory-structure/app/pages) directory creates a corresponding URL (or route) that displays the contents of the file. By using dynamic imports for each page, Nuxt leverages code-splitting to ship the minimum amount of JavaScript for the requested route.
## Pages
Nuxt routing is based on [vue-router](https://router.vuejs.org) and generates the routes from every component created in the [`app/pages/` directory](/docs/4.x/directory-structure/app/pages), based on their filename.
This file system routing uses naming conventions to create dynamic and nested routes:
::code-group
```bash [Directory Structure]
-| pages/
---| about.vue
---| index.vue
---| posts/
-----| [id].vue
```
```json [Generated Router File]
{
"routes": [
{
"path": "/about",
"component": "pages/about.vue"
},
{
"path": "/",
"component": "pages/index.vue"
},
{
"path": "/posts/:id",
"component": "pages/posts/[id].vue"
}
]
}
```
::
:read-more{to="/docs/4.x/directory-structure/app/pages"}
## Navigation
The [`<NuxtLink>`](/docs/4.x/api/components/nuxt-link) component links pages between them. It renders an `<a>` tag with the `href` attribute set to the route of the page. Once the application is hydrated, page transitions are performed in JavaScript by updating the browser URL. This prevents full-page refreshes and allows for animated transitions.
When a [`<NuxtLink>`](/docs/4.x/api/components/nuxt-link) enters the viewport on the client side, Nuxt will automatically prefetch components and payload (generated pages) of the linked pages ahead of time, resulting in faster navigation.
```vue [app/pages/index.vue]
<template>
<header>
<nav>
<ul>
<li><NuxtLink to="/about">About</NuxtLink></li>
<li><NuxtLink to="/posts/1">Post 1</NuxtLink></li>
<li><NuxtLink to="/posts/2">Post 2</NuxtLink></li>
</ul>
</nav>
</header>
</template>
```
:read-more{to="/docs/4.x/api/components/nuxt-link"}
## Route Parameters
The [`useRoute()`](/docs/4.x/api/composables/use-route) composable can be used in a `<script setup>` block or a `setup()` method of a Vue component to access the current route details.
```vue twoslash [pages/posts/[id\\].vue]
<script setup lang="ts">
const route = useRoute()
// When accessing /posts/1, route.params.id will be 1
console.log(route.params.id)
</script>
```
:read-more{to="/docs/4.x/api/composables/use-route"}
## Route Middleware
Nuxt provides a customizable route middleware framework you can use throughout your application, ideal for extracting code that you want to run before navigating to a particular route.
::note
Route middleware runs within the Vue part of your Nuxt app. Despite the similar name, they are completely different from server middleware, which are run in the Nitro server part of your app.
::
::important
Route middleware does **not** run for server routes (e.g. `/api/*`) or other server requests. To apply middleware to these requests, use [server middleware](/docs/4.x/directory-structure/server#server-middleware) instead.
::
There are three kinds of route middleware:
1. Anonymous (or inline) route middleware, which are defined directly in the pages where they are used.
2. Named route middleware, which are placed in the [`app/middleware/`](/docs/4.x/directory-structure/app/middleware) directory and will be automatically loaded via asynchronous import when used on a page. (**Note**: The route middleware name is normalized to kebab-case, so `someMiddleware` becomes `some-middleware`.)
3. Global route middleware, which are placed in the [`app/middleware/`](/docs/4.x/directory-structure/app/middleware) directory (with a `.global` suffix) and will be automatically run on every route change.
Example of an `auth` middleware protecting the `/dashboard` page:
::code-group
```ts twoslash [middleware/auth.ts]
function isAuthenticated (): boolean { return false }
// ---cut---
export default defineNuxtRouteMiddleware((to, from) => {
// isAuthenticated() is an example method verifying if a user is authenticated
if (isAuthenticated() === false) {
return navigateTo('/login')
}
})
```
```vue twoslash [pages/dashboard.vue]
<script setup lang="ts">
definePageMeta({
middleware: 'auth',
})
</script>
<template>
<h1>Welcome to your dashboard</h1>
</template>
```
::
:read-more{to="/docs/4.x/directory-structure/app/middleware"}
## Route Validation
Nuxt offers route validation via the `validate` property in [`definePageMeta()`](/docs/4.x/api/utils/define-page-meta) in each page you wish to validate.
The `validate` property accepts the `route` as an argument. You can return a boolean value to determine whether or not this is a valid route to be rendered with this page. If you return `false`, this will cause a 404 error. You can also directly return an object with `status`/`statusText` to customize the error returned.
If you have a more complex use case, then you can use anonymous route middleware instead.
```vue twoslash [pages/posts/[id\\].vue]
<script setup lang="ts">
definePageMeta({
validate (route) {
// Check if the id is made up of digits
return typeof route.params.id === 'string' && /^\d+$/.test(route.params.id)
},
})
</script>
```
:read-more{to="/docs/4.x/api/utils/define-page-meta"}

View File

@@ -0,0 +1,377 @@
---
title: SEO and Meta
description: Improve your Nuxt app's SEO with powerful head config, composables and components.
navigation.icon: i-lucide-file-search
---
Nuxt head tag management is powered by [Unhead](https://unhead.unjs.io). It provides sensible defaults, several powerful composables
and numerous configuration options to manage your app's head and SEO meta tags.
## Nuxt Config
Providing an [`app.head`](/docs/4.x/api/nuxt-config#head) property in your [`nuxt.config.ts`](/docs/4.x/directory-structure/nuxt-config) allows you to statically customize the head for your entire app.
::important
This method does not allow you to provide reactive data. We recommend using `useHead()` in `app.vue`.
::
It's good practice to set tags here that won't change such as your site title default, language and favicon.
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
app: {
head: {
title: 'Nuxt', // default fallback title
htmlAttrs: {
lang: 'en',
},
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
],
},
},
})
```
You can also provide any of the keys listed below in [Types](/docs/4.x/getting-started/seo-meta#types).
### Defaults Tags
Some tags are provided by Nuxt by default to ensure your website works well out of the box.
- `viewport`: `width=device-width, initial-scale=1`
- `charset`: `utf-8`
While most sites won't need to override these defaults, you can update them using the keyed shortcuts.
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
app: {
head: {
// update Nuxt defaults
charset: 'utf-16',
viewport: 'width=device-width, initial-scale=1, maximum-scale=1',
},
},
})
```
## `useHead`
The [`useHead`](/docs/4.x/api/composables/use-head) composable function supports reactive input, allowing you to manage your head tags programmatically.
```vue twoslash [app/app.vue]
<script setup lang="ts">
useHead({
title: 'My App',
meta: [
{ name: 'description', content: 'My amazing site.' },
],
bodyAttrs: {
class: 'test',
},
script: [{ innerHTML: 'console.log(\'Hello world\')' }],
})
</script>
```
We recommend taking a look at the [`useHead`](/docs/4.x/api/composables/use-head) and [`useHeadSafe`](/docs/4.x/api/composables/use-head-safe) composables.
## `useSeoMeta`
The [`useSeoMeta`](/docs/4.x/api/composables/use-seo-meta) composable lets you define your site's SEO meta tags as an object with full type safety.
This helps you avoid typos and common mistakes, such as using `name` instead of `property`.
```vue twoslash [app/app.vue]
<script setup lang="ts">
useSeoMeta({
title: 'My Amazing Site',
ogTitle: 'My Amazing Site',
description: 'This is my amazing site, let me tell you all about it.',
ogDescription: 'This is my amazing site, let me tell you all about it.',
ogImage: 'https://example.com/image.png',
twitterCard: 'summary_large_image',
})
</script>
```
:read-more{to="/docs/4.x/api/composables/use-seo-meta"}
## Components
While using [`useHead`](/docs/4.x/api/composables/use-head) is recommended in all cases, you may have a personal preference for defining your head tags in your template using components.
Nuxt provides the following components for this purpose: `<Title>`, `<Base>`, `<NoScript>`, `<Style>`, `<Meta>`, `<Link>`, `<Body>`, `<Html>` and `<Head>`. Note
the capitalization of these components ensuring we don't use invalid native HTML tags.
`<Head>` and `<Body>` can accept nested meta tags (for aesthetic reasons) but this does not affect _where_ the nested meta tags are rendered in the final HTML.
<!-- @case-police-ignore html -->
```vue [app/app.vue]
<script setup lang="ts">
const title = ref('Hello World')
</script>
<template>
<div>
<Head>
<Title>{{ title }}</Title>
<Meta
name="description"
:content="title"
/>
<Style>
body { background-color: green; }
</Style>
</Head>
<h1>{{ title }}</h1>
</div>
</template>
```
It's suggested to wrap your components in either a `<Head>` or `<Html>` components as tags will be deduped more intuitively.
::warning
If you need to duplicate tags across client-server boundaries, apply a `key` attribute on the `<Head>` component.
::
## Types
Below are the non-reactive types used for [`useHead`](/docs/4.x/api/composables/use-head), [`app.head`](/docs/4.x/api/nuxt-config#head) and components.
```ts
interface MetaObject {
title?: string
titleTemplate?: string | ((title?: string) => string)
templateParams?: Record<string, string | Record<string, string>>
base?: Base
link?: Link[]
meta?: Meta[]
style?: Style[]
script?: Script[]
noscript?: Noscript[]
htmlAttrs?: HtmlAttributes
bodyAttrs?: BodyAttributes
}
```
See [@unhead/vue](https://github.com/unjs/unhead/blob/main/packages/vue/src/types/schema.ts) for more detailed types.
## Features
### Reactivity
Reactivity is supported on all properties, by providing a computed value, a getter, or a reactive object.
::code-group
```vue twoslash [useHead]
<script setup lang="ts">
const description = ref('My amazing site.')
useHead({
meta: [
{ name: 'description', content: description },
],
})
</script>
```
```vue twoslash [useSeoMeta]
<script setup lang="ts">
const description = ref('My amazing site.')
useSeoMeta({
description,
})
</script>
```
```vue [app/Components]
<script setup lang="ts">
const description = ref('My amazing site.')
</script>
<template>
<div>
<Meta
name="description"
:content="description"
/>
</div>
</template>
```
::
### Title Template
You can use the `titleTemplate` option to provide a dynamic template for customizing the title of your site. For example, you could add the name of your site to the title of every page.
The `titleTemplate` can either be a string, where `%s` is replaced with the title, or a function.
If you want to use a function (for full control), then this cannot be set in your `nuxt.config`. It is recommended instead to set it within your `app.vue` file where it will apply to all pages on your site:
::code-group
```vue twoslash [useHead]
<script setup lang="ts">
useHead({
titleTemplate: (titleChunk) => {
return titleChunk ? `${titleChunk} - Site Title` : 'Site Title'
},
})
</script>
```
::
Now, if you set the title to `My Page` with [`useHead`](/docs/4.x/api/composables/use-head) on another page of your site, the title would appear as 'My Page - Site Title' in the browser tab. You could also pass `null` to default to 'Site Title'.
### Template Params
You can use `templateParams` to provide additional placeholders in your `titleTemplate` besides the default `%s`. This allows for more dynamic title generation.
::code-group
```vue twoslash [useHead]
<script setup lang="ts">
useHead({
titleTemplate: (titleChunk) => {
return titleChunk ? `${titleChunk} %separator %siteName` : '%siteName'
},
templateParams: {
siteName: 'Site Title',
separator: '-',
},
})
</script>
```
::
### Body Tags
You can use the `tagPosition: 'bodyClose'` option on applicable tags to append them to the end of the `<body>` tag.
For example:
```vue twoslash
<script setup lang="ts">
useHead({
script: [
{
src: 'https://third-party-script.com',
// valid options are: 'head' | 'bodyClose' | 'bodyOpen'
tagPosition: 'bodyClose',
},
],
})
</script>
```
## Examples
### With `definePageMeta`
Within your [`app/pages/` directory](/docs/4.x/directory-structure/app/pages), you can use `definePageMeta` along with [`useHead`](/docs/4.x/api/composables/use-head) to set metadata based on the current route.
For example, you can first set the current page title (this is extracted at build time via a macro, so it can't be set dynamically):
```vue twoslash [pages/some-page.vue]
<script setup lang="ts">
definePageMeta({
title: 'Some Page',
})
</script>
```
And then in your layout file, you might use the route's metadata you have previously set:
```vue twoslash [layouts/default.vue]
<script setup lang="ts">
const route = useRoute()
useHead({
meta: [{ property: 'og:title', content: `App Name - ${route.meta.title}` }],
})
</script>
```
:link-example{to="/docs/4.x/examples/features/meta-tags"}
:read-more{to="/docs/4.x/directory-structure/app/pages/#page-metadata"}
### Dynamic Title
In the example below, `titleTemplate` is set either as a string with the `%s` placeholder or as a `function`, which allows greater flexibility in setting the page title dynamically for each route of your Nuxt app:
```vue twoslash [app/app.vue]
<script setup lang="ts">
useHead({
// as a string,
// where `%s` is replaced with the title
titleTemplate: '%s - Site Title',
})
</script>
```
```vue twoslash [app/app.vue]
<script setup lang="ts">
useHead({
// or as a function
titleTemplate: (productCategory) => {
return productCategory
? `${productCategory} - Site Title`
: 'Site Title'
},
})
</script>
```
`nuxt.config` is also used as an alternative way of setting the page title. However, `nuxt.config` does not allow the page title to be dynamic. Therefore, it is recommended to use `titleTemplate` in the `app.vue` file to add a dynamic title, which is then applied to all routes of your Nuxt app.
### External CSS
The example below shows how you might enable Google Fonts using either the `link` property of the [`useHead`](/docs/4.x/api/composables/use-head) composable or using the `<Link>` component:
::code-group
```vue twoslash [useHead]
<script setup lang="ts">
useHead({
link: [
{
rel: 'preconnect',
href: 'https://fonts.googleapis.com',
},
{
rel: 'stylesheet',
href: 'https://fonts.googleapis.com/css2?family=Roboto&display=swap',
crossorigin: '',
},
],
})
</script>
```
```vue [app/Components]
<template>
<div>
<Link
rel="preconnect"
href="https://fonts.googleapis.com"
/>
<Link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Roboto&display=swap"
crossorigin=""
/>
</div>
</template>
```
::

View File

@@ -0,0 +1,578 @@
---
title: 'Transitions'
description: Apply transitions between pages and layouts with Vue or native browser View Transitions.
navigation.icon: i-lucide-toggle-right
---
::note
Nuxt leverages Vue's [`<Transition>`](https://vuejs.org/guide/built-ins/transition#the-transition-component) component to apply transitions between pages and layouts.
::
## Page Transitions
You can enable page transitions to apply an automatic transition for all your [pages](/docs/4.x/directory-structure/app/pages).
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
app: {
pageTransition: { name: 'page', mode: 'out-in' },
},
})
```
::note
If you are changing layouts as well as page, the page transition you set here will not run. Instead, you should set a [layout transition](/docs/4.x/getting-started/transitions#layout-transitions).
::
To start adding transition between your pages, add the following CSS to your [`app.vue`](/docs/4.x/directory-structure/app/app):
::code-group
```vue [app/app.vue]
<template>
<NuxtPage />
</template>
<style>
.page-enter-active,
.page-leave-active {
transition: all 0.4s;
}
.page-enter-from,
.page-leave-to {
opacity: 0;
filter: blur(1rem);
}
</style>
```
```vue [app/pages/index.vue]
<template>
<div>
<h1>Home page</h1>
<NuxtLink to="/about">About page</NuxtLink>
</div>
</template>
```
```vue [app/pages/about.vue]
<template>
<div>
<h1>About page</h1>
<NuxtLink to="/">Home page</NuxtLink>
</div>
</template>
```
::
This produces the following result when navigating between pages:
<video controls class="rounded" poster="https://res.cloudinary.com/nuxt/video/upload/v1665061349/nuxt3/nuxt3-page-transitions_umwvmh.jpg">
<source src="https://res.cloudinary.com/nuxt/video/upload/v1665061349/nuxt3/nuxt3-page-transitions_umwvmh.mp4" type="video/mp4">
</video>
To set a different transition for a page, set the `pageTransition` key in [`definePageMeta`](/docs/4.x/api/utils/define-page-meta) of the page:
::code-group
```vue twoslash [pages/about.vue]
<script setup lang="ts">
definePageMeta({
pageTransition: {
name: 'rotate',
},
})
</script>
```
```vue [app/app.vue]
<template>
<NuxtPage />
</template>
<style>
/* ... */
.rotate-enter-active,
.rotate-leave-active {
transition: all 0.4s;
}
.rotate-enter-from,
.rotate-leave-to {
opacity: 0;
transform: rotate3d(1, 1, 1, 15deg);
}
</style>
```
::
Moving to the about page will add the 3d rotation effect:
<video controls class="rounded" poster="https://res.cloudinary.com/nuxt/video/upload/v1665063233/nuxt3/nuxt3-page-transitions-cutom.jpg">
<source src="https://res.cloudinary.com/nuxt/video/upload/v1665063233/nuxt3/nuxt3-page-transitions-cutom.mp4" type="video/mp4">
</video>
## Layout Transitions
You can enable layout transitions to apply an automatic transition for all your [layouts](/docs/4.x/directory-structure/app/layouts).
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
app: {
layoutTransition: { name: 'layout', mode: 'out-in' },
},
})
```
To start adding transition between your pages and layouts, add the following CSS to your [`app.vue`](/docs/4.x/directory-structure/app/app):
::code-group
```vue [app/app.vue]
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
<style>
.layout-enter-active,
.layout-leave-active {
transition: all 0.4s;
}
.layout-enter-from,
.layout-leave-to {
filter: grayscale(1);
}
</style>
```
```vue [app/layouts/default.vue]
<template>
<div>
<pre>default layout</pre>
<slot />
</div>
</template>
<style scoped>
div {
background-color: lightgreen;
}
</style>
```
```vue [app/layouts/orange.vue]
<template>
<div>
<pre>orange layout</pre>
<slot />
</div>
</template>
<style scoped>
div {
background-color: #eebb90;
padding: 20px;
height: 100vh;
}
</style>
```
```vue [app/pages/index.vue]
<template>
<div>
<h1>Home page</h1>
<NuxtLink to="/about">About page</NuxtLink>
</div>
</template>
```
```vue [app/pages/about.vue]
<script setup lang="ts">
definePageMeta({
layout: 'orange',
})
</script>
<template>
<div>
<h1>About page</h1>
<NuxtLink to="/">Home page</NuxtLink>
</div>
</template>
```
::
This produces the following result when navigating between pages:
<video controls class="rounded" poster="https://res.cloudinary.com/nuxt/video/upload/v1665065289/nuxt3/nuxt3-layouts-transitions_c9hwlx.jpg">
<source src="https://res.cloudinary.com/nuxt/video/upload/v1665065289/nuxt3/nuxt3-layouts-transitions_c9hwlx.mp4" type="video/mp4">
</video>
Similar to `pageTransition`, you can apply a custom `layoutTransition` to the page component using `definePageMeta`:
```vue twoslash [pages/about.vue]
<script setup lang="ts">
definePageMeta({
layout: 'orange',
layoutTransition: {
name: 'slide-in',
},
})
</script>
```
## Global Settings
You can customize these default transition names globally using `nuxt.config`.
Both `pageTransition` and `layoutTransition` keys accept [`TransitionProps`](https://vuejs.org/api/built-in-components#transition) as JSON serializable values where you can pass the `name`, `mode` and other valid transition-props of the custom CSS transition.
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
app: {
pageTransition: {
name: 'fade',
mode: 'out-in', // default
},
layoutTransition: {
name: 'slide',
mode: 'out-in', // default
},
},
})
```
::warning
If you change the `name` property, you also have to rename the CSS classes accordingly.
::
To override the global transition property, use the `definePageMeta` to define page or layout transitions for a single Nuxt page and override any page or layout transitions that are defined globally in `nuxt.config` file.
```vue twoslash [pages/some-page.vue]
<script setup lang="ts">
definePageMeta({
pageTransition: {
name: 'bounce',
mode: 'out-in', // default
},
})
</script>
```
## Disable Transitions
`pageTransition` and `layoutTransition` can be disabled for a specific route:
```vue twoslash [pages/some-page.vue]
<script setup lang="ts">
definePageMeta({
pageTransition: false,
layoutTransition: false,
})
</script>
```
Or globally in the `nuxt.config`:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
app: {
pageTransition: false,
layoutTransition: false,
},
})
```
## JavaScript Hooks
For advanced use-cases, you can use JavaScript hooks to create highly dynamic and custom transitions for your Nuxt pages.
This way presents perfect use-cases for JavaScript animation libraries such as [GSAP](https://gsap.com).
```vue twoslash [pages/some-page.vue]
<script setup lang="ts">
definePageMeta({
pageTransition: {
name: 'custom-flip',
mode: 'out-in',
onBeforeEnter: (el) => {
console.log('Before enter...')
},
onEnter: (el, done) => {},
onAfterEnter: (el) => {},
},
})
</script>
```
::tip
Learn more about additional [JavaScript hooks](https://vuejs.org/guide/built-ins/transition#javascript-hooks) available in the `Transition` component.
::
## Dynamic Transitions
To apply dynamic transitions using conditional logic, you can leverage inline [middleware](/docs/4.x/directory-structure/app/middleware) to assign a different transition name to `to.meta.pageTransition`.
::code-group
```vue twoslash [pages/[id\\].vue]
<script setup lang="ts">
definePageMeta({
pageTransition: {
name: 'slide-right',
mode: 'out-in',
},
middleware (to, from) {
if (to.meta.pageTransition && typeof to.meta.pageTransition !== 'boolean') {
to.meta.pageTransition.name = +to.params.id! > +from.params.id! ? 'slide-left' : 'slide-right'
}
},
})
</script>
<template>
<h1>#{{ $route.params.id }}</h1>
</template>
<style>
.slide-left-enter-active,
.slide-left-leave-active,
.slide-right-enter-active,
.slide-right-leave-active {
transition: all 0.2s;
}
.slide-left-enter-from {
opacity: 0;
transform: translate(50px, 0);
}
.slide-left-leave-to {
opacity: 0;
transform: translate(-50px, 0);
}
.slide-right-enter-from {
opacity: 0;
transform: translate(-50px, 0);
}
.slide-right-leave-to {
opacity: 0;
transform: translate(50px, 0);
}
</style>
```
```vue [app/layouts/default.vue]
<script setup lang="ts">
const route = useRoute()
const id = computed(() => Number(route.params.id || 1))
const prev = computed(() => '/' + (id.value - 1))
const next = computed(() => '/' + (id.value + 1))
</script>
<template>
<div>
<slot />
<div v-if="$route.params.id">
<NuxtLink :to="prev">⬅️</NuxtLink> |
<NuxtLink :to="next">➡️</NuxtLink>
</div>
</div>
</template>
```
::
The page now applies the `slide-left` transition when going to the next id and `slide-right` for the previous:
<video controls class="rounded" poster="https://res.cloudinary.com/nuxt/video/upload/v1665069410/nuxt3/nuxt-dynamic-page-transitions.jpg">
<source src="https://res.cloudinary.com/nuxt/video/upload/v1665069410/nuxt3/nuxt-dynamic-page-transitions.mp4" type="video/mp4">
</video>
## Transition with NuxtPage
When `<NuxtPage />` is used in `app.vue`, transitions can be configured with the `transition` prop to activate transitions globally.
```vue [app/app.vue]
<template>
<div>
<NuxtLayout>
<NuxtPage
:transition="{
name: 'bounce',
mode: 'out-in',
}"
/>
</NuxtLayout>
</div>
</template>
```
::note
Remember, this page transition cannot be overridden with `definePageMeta` on individual pages.
::
## View Transitions API (experimental)
Nuxt ships with an experimental implementation of the [**View Transitions API**](https://developer.chrome.com/docs/web-platform/view-transitions) (see [MDN](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API)). This is an exciting new way to implement native browser transitions which (among other things) have the ability to transition between unrelated elements on different pages.
You can check a demo [on StackBlitz](https://stackblitz.com/edit/nuxt-view-transitions).
The Nuxt integration can be enabled with the `experimental.viewTransition` option in your configuration file:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
viewTransition: true,
},
})
```
The possible values are: `false`, `true`, or `'always'`.
If set to true, Nuxt will not apply transitions if the user's browser matches `prefers-reduced-motion: reduce` (recommended). If set to `always`, Nuxt will always apply the transition and it is up to you to respect the user's preference.
By default, view transitions are enabled for all [pages](/docs/4.x/directory-structure/app/pages), but you can set a different global default.
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
app: {
// Disable view transitions globally, and opt-in on a per page basis
viewTransition: false,
},
})
```
It is possible to override the default `viewTransition` value for a page by setting the `viewTransition` key in [`definePageMeta`](/docs/4.x/api/utils/define-page-meta) of the page:
```vue twoslash [pages/about.vue]
<script setup lang="ts">
definePageMeta({
viewTransition: false,
})
</script>
```
::warning
Overriding view transitions on a per-page basis will only have an effect if you have enabled the `experimental.viewTransition` option.
::
### View Transition Types
[View transition types](https://developer.chrome.com/blog/view-transitions-update-io24#view-transition-types) allow you to apply different CSS animations depending on the type of navigation. This is useful for creating asymmetric transitions (e.g., a different animation when navigating forward vs. backward).
Types are set on the [`ViewTransition`](https://developer.mozilla.org/en-US/docs/Web/API/ViewTransition) and can be targeted in CSS using the [`:active-view-transition-type()`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/:active-view-transition-type) pseudo-class selector.
You can set default types globally in your `nuxt.config.ts`:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
app: {
viewTransition: {
enabled: true,
types: ['slide'],
},
},
})
```
Or configure types per-page using `definePageMeta`. Per-page types support both static arrays and functions for dynamic behavior:
```vue twoslash [pages/detail.vue]
<script setup lang="ts">
definePageMeta({
viewTransition: {
enabled: true,
// Types applied to any transition involving this page
types: ['slide'],
// Types applied only when navigating TO this page
toTypes: ['slide-in'],
// Types applied only when navigating FROM this page
fromTypes: ['slide-out'],
},
})
</script>
```
You can also use functions for `types`, `toTypes`, and `fromTypes` in `definePageMeta` to determine types dynamically based on the route:
```vue twoslash [pages/[id].vue]
<script setup lang="ts">
definePageMeta({
viewTransition: {
enabled: true,
toTypes: (to, from) => {
// Slide left when going to a higher ID, right otherwise
return Number(to.params.id) > Number(from.params.id)
? ['slide-left']
: ['slide-right']
},
},
})
</script>
```
Then target these types in your CSS:
```css
/* Default crossfade */
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 0.3s;
}
/* Slide left animation */
html:active-view-transition-type(slide-left) {
&::view-transition-old(root) {
animation: slide-out-left 0.3s ease-in-out;
}
&::view-transition-new(root) {
animation: slide-in-right 0.3s ease-in-out;
}
}
/* Slide right animation */
html:active-view-transition-type(slide-right) {
&::view-transition-old(root) {
animation: slide-out-right 0.3s ease-in-out;
}
&::view-transition-new(root) {
animation: slide-in-left 0.3s ease-in-out;
}
}
```
::note
Function values for `types`, `toTypes`, and `fromTypes` only work in `definePageMeta`, not in `nuxt.config.ts` (where only static `string[]` is supported).
::
The `page:view-transition:start` hook provides access to the [`ViewTransition`](https://developer.mozilla.org/en-US/docs/Web/API/ViewTransition) object, which includes a [`types`](https://developer.mozilla.org/en-US/docs/Web/API/ViewTransition/types) property (`ViewTransitionTypeSet`) that can be read or modified at runtime:
```ts [plugins/view-transition.client.ts]
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.hook('page:view-transition:start', (transition) => {
// Read or modify types at runtime
console.log([...transition.types])
})
})
```
If you are also using Vue transitions like `pageTransition` and `layoutTransition` (see above) to achieve the same result as the new View Transitions API, then you may wish to _disable_ Vue transitions if the user's browser supports the newer, native web API. You can do this by creating `~/middleware/disable-vue-transitions.global.ts` with the following contents:
```ts
export default defineNuxtRouteMiddleware((to) => {
if (import.meta.server || !document.startViewTransition) {
return
}
// Disable built-in Vue transitions
to.meta.pageTransition = false
to.meta.layoutTransition = false
})
```
### Known Issues
- If you perform data fetching within your page setup functions, you may wish to reconsider using this feature for the moment. (By design, View Transitions completely freeze DOM updates whilst they are taking place.) We're looking at restricting the View Transition to the final moments before `<Suspense>` resolves, but in the interim you may want to consider carefully whether to adopt this feature if this describes you.

View File

@@ -0,0 +1,817 @@
---
title: 'Data Fetching'
description: Nuxt provides composables to handle data fetching within your application.
navigation.icon: i-lucide-cable
---
Nuxt comes with two composables and a built-in library to perform data-fetching in browser or server environments: `useFetch`, [`useAsyncData`](/docs/4.x/api/composables/use-async-data) and `$fetch`.
In a nutshell:
- [`$fetch`](/docs/4.x/api/utils/dollarfetch) is the simplest way to make a network request.
- [`useFetch`](/docs/4.x/api/composables/use-fetch) is a wrapper around `$fetch` that fetches data only once in [universal rendering](/docs/4.x/guide/concepts/rendering#universal-rendering).
- [`useAsyncData`](/docs/4.x/api/composables/use-async-data) is similar to `useFetch` but offers more fine-grained control.
Both `useFetch` and `useAsyncData` share a common set of options and patterns that we will detail in the last sections.
## The need for `useFetch` and `useAsyncData`
Nuxt is a framework which can run isomorphic (or universal) code in both server and client environments. If the [`$fetch` function](/docs/4.x/api/utils/dollarfetch) is used to perform data fetching in the setup function of a Vue component, this may cause data to be fetched twice, once on the server (to render the HTML) and once again on the client (when the HTML is hydrated). This can cause hydration issues, increase the time to interactivity and cause unpredictable behavior.
The [`useFetch`](/docs/4.x/api/composables/use-fetch) and [`useAsyncData`](/docs/4.x/api/composables/use-async-data) composables solve this problem by ensuring that if an API call is made on the server, the data is forwarded to the client in the payload.
The payload is a JavaScript object accessible through [`useNuxtApp().payload`](/docs/4.x/api/composables/use-nuxt-app#payload). It is used on the client to avoid refetching the same data when the code is executed in the browser [during hydration](/docs/4.x/guide/concepts/rendering#universal-rendering).
::tip
Use the [Nuxt DevTools](https://devtools.nuxt.com) to inspect this data in the **Payload tab**.
::
```vue [app/app.vue]
<script setup lang="ts">
const { data } = await useFetch('/api/data')
async function handleFormSubmit () {
const res = await $fetch('/api/submit', {
method: 'POST',
body: {
// My form data
},
})
}
</script>
<template>
<div v-if="data == undefined">
No data
</div>
<div v-else>
<form @submit="handleFormSubmit">
<!-- form input tags -->
</form>
</div>
</template>
```
In the example above, `useFetch` would make sure that the request would occur in the server and is properly forwarded to the browser. `$fetch` has no such mechanism and is a better option to use when the request is solely made from the browser.
### Suspense
Nuxt uses Vue's [`<Suspense>`](https://vuejs.org/guide/built-ins/suspense) component under the hood to prevent navigation before every async data is available to the view. The data fetching composables can help you leverage this feature and use what suits best on a per-call basis.
::note
You can add the [`<NuxtLoadingIndicator>`](/docs/4.x/api/components/nuxt-loading-indicator) to add a progress bar between page navigations.
::
## `$fetch`
Nuxt includes the [ofetch](https://github.com/unjs/ofetch) library, and is auto-imported as the `$fetch` alias globally across your application.
```vue twoslash [pages/todos.vue]
<script setup lang="ts">
async function addTodo () {
const todo = await $fetch('/api/todos', {
method: 'POST',
body: {
// My todo data
},
})
}
</script>
```
::warning
Beware that using only `$fetch` will not provide [network calls de-duplication and navigation prevention](/docs/4.x/getting-started/data-fetching#the-need-for-usefetch-and-useasyncdata). :br
It is recommended to use `$fetch` for client-side interactions (event-based) or combined with [`useAsyncData`](/docs/4.x/getting-started/data-fetching#useasyncdata) when fetching the initial component data.
::
::read-more{to="/docs/4.x/api/utils/dollarfetch"}
Read more about `$fetch`.
::
### Pass Client Headers to the API
When calling `useFetch` on the server, Nuxt will use [`useRequestFetch`](/docs/4.x/api/composables/use-request-fetch) to proxy client headers and cookies (with the exception of headers not meant to be forwarded, like `host`).
```vue
<script setup lang="ts">
const { data } = await useFetch('/api/echo')
</script>
```
```ts
// /api/echo.ts
export default defineEventHandler(event => parseCookies(event))
```
Alternatively, the example below shows how to use [`useRequestHeaders`](/docs/4.x/api/composables/use-request-headers) to access and send cookies to the API from a server-side request (originating on the client). Using an isomorphic `$fetch` call, we ensure that the API endpoint has access to the same `cookie` header originally sent by the user's browser. This is only necessary if you aren't using `useFetch`.
```vue
<script setup lang="ts">
const headers = useRequestHeaders(['cookie'])
async function getCurrentUser () {
return await $fetch('/api/me', { headers })
}
</script>
```
::tip
You can also use [`useRequestFetch`](/docs/4.x/api/composables/use-request-fetch) to proxy headers to the call automatically.
::
::caution
Be very careful before proxying headers to an external API and just include headers that you need. Not all headers are safe to be bypassed and might introduce unwanted behavior. Here is a list of common headers that are NOT to be proxied:
- `host`, `accept`
- `content-length`, `content-md5`, `content-type`
- `x-forwarded-host`, `x-forwarded-port`, `x-forwarded-proto`
- `cf-connecting-ip`, `cf-ray`
::
## `useFetch`
The [`useFetch`](/docs/4.x/api/composables/use-fetch) composable uses `$fetch` under-the-hood to make SSR-safe network calls in the setup function.
```vue twoslash [app/app.vue]
<script setup lang="ts">
const { data: count } = await useFetch('/api/count')
</script>
<template>
<p>Page visits: {{ count }}</p>
</template>
```
This composable is a wrapper around the [`useAsyncData`](/docs/4.x/api/composables/use-async-data) composable and `$fetch` utility.
:video-accordion{title="Watch a video from Alexander Lichter to avoid using useFetch the wrong way" videoId="njsGVmcWviY"}
:read-more{to="/docs/4.x/api/composables/use-fetch"}
:link-example{to="/docs/4.x/examples/features/data-fetching"}
## `useAsyncData`
The `useAsyncData` composable is responsible for wrapping async logic and returning the result once it is resolved.
::tip
`useFetch(url)` is nearly equivalent to `useAsyncData(url, () => event.$fetch(url))`. :br
It's developer experience sugar for the most common use case. (You can find out more about `event.fetch` at [`useRequestFetch`](/docs/4.x/api/composables/use-request-fetch).)
::
:video-accordion{title="Watch a video from Alexander Lichter to dig deeper into the difference between useFetch and useAsyncData" videoId="0X-aOpSGabA"}
There are some cases when using the [`useFetch`](/docs/4.x/api/composables/use-fetch) composable is not appropriate, for example when a CMS or a third-party provide their own query layer. In this case, you can use [`useAsyncData`](/docs/4.x/api/composables/use-async-data) to wrap your calls and still keep the benefits provided by the composable.
```vue [app/pages/users.vue]
<script setup lang="ts">
const { data, error } = await useAsyncData('users', () => myGetFunction('users'))
// This is also possible:
const { data, error } = await useAsyncData(() => myGetFunction('users'))
</script>
```
::note
The first argument of [`useAsyncData`](/docs/4.x/api/composables/use-async-data) is a unique key used to cache the response of the second argument, the querying function. This key can be ignored by directly passing the querying function, the key will be auto-generated.
:br :br
Since the autogenerated key only takes into account the file and line where `useAsyncData` is invoked, it is recommended to always create your own key to avoid unwanted behavior, like when you are creating your own custom composable wrapping `useAsyncData`.
:br :br
Setting a key can be useful to share the same data between components using [`useNuxtData`](/docs/4.x/api/composables/use-nuxt-data) or to [refresh specific data](/docs/4.x/api/utils/refresh-nuxt-data#refresh-specific-data).
::
```vue [app/pages/users/[id\\].vue]
<script setup lang="ts">
const { id } = useRoute().params
const { data, error } = await useAsyncData(`user:${id}`, () => {
return myGetFunction('users', { id })
})
</script>
```
The `useAsyncData` composable is a great way to wrap and wait for multiple `$fetch` requests to be completed, and then process the results.
```vue
<script setup lang="ts">
const { data: discounts, status } = await useAsyncData('cart-discount', async (_nuxtApp, { signal }) => {
const [coupons, offers] = await Promise.all([
$fetch('/cart/coupons', { signal }),
$fetch('/cart/offers', { signal }),
])
return { coupons, offers }
})
// discounts.value.coupons
// discounts.value.offers
</script>
```
::note
`useAsyncData` is for fetching and caching data, not triggering side effects like calling Pinia actions, as this can cause unintended behavior such as repeated executions with nullish values. If you need to trigger side effects, use the [`callOnce`](/docs/4.x/api/utils/call-once) utility to do so.
```vue
<script setup lang="ts">
const offersStore = useOffersStore()
// you can't do this
await useAsyncData(() => offersStore.getOffer(route.params.slug))
</script>
```
::
::read-more{to="/docs/4.x/api/composables/use-async-data"}
Read more about `useAsyncData`.
::
## Return Values
`useFetch` and `useAsyncData` have the same return values listed below.
- `data`: the result of the asynchronous function that is passed in.
- `refresh`/`execute`: a function that can be used to refresh the data returned by the `handler` function.
- `clear`: a function that can be used to set `data` to `undefined` (or the value of `options.default()` if provided), set `error` to `undefined`, set `status` to `idle`, and mark any currently pending requests as cancelled.
- `error`: an error object if the data fetching failed.
- `status`: a string indicating the status of the data request (`"idle"`, `"pending"`, `"success"`, `"error"`).
::note
`data`, `error` and `status` are Vue refs accessible with `.value` in `<script setup>`
::
By default, Nuxt waits until a `refresh` is finished before it can be executed again.
::note
If you have not fetched data on the server (for example, with `server: false`), then the data _will not_ be fetched until hydration completes. This means even if you await `useFetch` on client-side, `data` will remain null within `<script setup>`.
::
## Options
[`useAsyncData`](/docs/4.x/api/composables/use-async-data) and [`useFetch`](/docs/4.x/api/composables/use-fetch) return the same object type and accept a common set of options as their last argument. They can help you control the composables behavior, such as navigation blocking, caching or execution.
### Lazy
By default, data fetching composables will wait for the resolution of their asynchronous function before navigating to a new page by using Vue's Suspense. This feature can be ignored on client-side navigation with the `lazy` option. In that case, you will have to manually handle loading state using the `status` value.
```vue twoslash [app/app.vue]
<script setup lang="ts">
const { status, data: posts } = useFetch('/api/posts', {
lazy: true,
})
</script>
<template>
<!-- you will need to handle a loading state -->
<div v-if="status === 'pending'">
Loading ...
</div>
<div v-else>
<div v-for="post in posts">
<!-- do something -->
</div>
</div>
</template>
```
You can alternatively use [`useLazyFetch`](/docs/4.x/api/composables/use-lazy-fetch) and `useLazyAsyncData` as convenient methods to perform the same.
```vue twoslash
<script setup lang="ts">
const { status, data: posts } = useLazyFetch('/api/posts')
</script>
```
::read-more{to="/docs/4.x/api/composables/use-lazy-fetch"}
Read more about `useLazyFetch`.
::
::read-more{to="/docs/4.x/api/composables/use-lazy-async-data"}
Read more about `useLazyAsyncData`.
::
:video-accordion{title="Watch a video from Vue School on blocking vs. non-blocking (lazy) requests" videoId="1022000555" platform="vimeo"}
### Client-only fetching
By default, data fetching composables will perform their asynchronous function on both client and server environments. Set the `server` option to `false` to only perform the call on the client-side. On initial load, the data will not be fetched before hydration is complete so you have to handle a pending state, though on subsequent client-side navigation the data will be awaited before loading the page.
Combined with the `lazy` option, this can be useful for data that is not needed on the first render (for example, non-SEO sensitive data).
```ts twoslash
/* This call is performed before hydration */
const articles = await useFetch('/api/article')
/* This call will only be performed on the client */
const { status, data: comments } = useFetch('/api/comments', {
lazy: true,
server: false,
})
```
The `useFetch` composable is meant to be invoked in setup method or called directly at the top level of a function in lifecycle hooks, otherwise you should use [`$fetch` method](/docs/4.x/getting-started/data-fetching#fetch).
### Minimize payload size
The `pick` option helps you to minimize the payload size stored in your HTML document by only selecting the fields that you want returned from the composables.
```vue
<script setup lang="ts">
/* only pick the fields used in your template */
const { data: mountain } = await useFetch('/api/mountains/everest', {
pick: ['title', 'description'],
})
</script>
<template>
<h1>{{ mountain.title }}</h1>
<p>{{ mountain.description }}</p>
</template>
```
If you need more control or map over several objects, you can use the `transform` function to alter the result of the query.
```ts
const { data: mountains } = await useFetch('/api/mountains', {
transform: (mountains) => {
return mountains.map(mountain => ({ title: mountain.title, description: mountain.description }))
},
})
```
::note
Both `pick` and `transform` don't prevent the unwanted data from being fetched initially. But they will prevent unwanted data from being added to the payload transferred from server to client.
::
:video-accordion{title="Watch a video from Vue School on minimizing payload size" videoId="1026410430" platform="vimeo"}
### Caching and refetching
#### Keys
[`useFetch`](/docs/4.x/api/composables/use-fetch) and [`useAsyncData`](/docs/4.x/api/composables/use-async-data) use keys to prevent refetching the same data.
- [`useFetch`](/docs/4.x/api/composables/use-fetch) uses the provided URL as a key. Alternatively, a `key` value can be provided in the `options` object passed as a last argument.
- [`useAsyncData`](/docs/4.x/api/composables/use-async-data) uses its first argument as a key if it is a string. If the first argument is the handler function that performs the query, then a key that is unique to the file name and line number of the instance of `useAsyncData` will be generated for you.
::tip
To get the cached data by key, you can use [`useNuxtData`](/docs/4.x/api/composables/use-nuxt-data)
::
:video-accordion{title="Watch a video from Vue School on caching data with the key option" videoId="1026410044" platform="vimeo"}
#### Shared State and Option Consistency
When multiple components use the same key with `useAsyncData` or `useFetch`, they will share the same `data`, `error` and `status` refs. This ensures consistency across components but requires some options to be consistent.
The following options **must be consistent** across all calls with the same key:
- `handler` function
- `deep` option
- `transform` function
- `pick` array
- `getCachedData` function
- `default` value
```ts
// ❌ This will trigger a development warning
const { data: users1 } = useAsyncData('users', (_nuxtApp, { signal }) => $fetch('/api/users', { signal }), { deep: false })
const { data: users2 } = useAsyncData('users', (_nuxtApp, { signal }) => $fetch('/api/users', { signal }), { deep: true })
```
The following options **can safely differ** without triggering warnings:
- `server`
- `lazy`
- `immediate`
- `dedupe`
- `watch`
```ts
// ✅ This is allowed
const { data: users1 } = useAsyncData('users', (_nuxtApp, { signal }) => $fetch('/api/users', { signal }), { immediate: true })
const { data: users2 } = useAsyncData('users', (_nuxtApp, { signal }) => $fetch('/api/users', { signal }), { immediate: false })
```
If you need independent instances, use different keys:
```ts
// These are completely independent instances
const { data: users1 } = useAsyncData('users-1', (_nuxtApp, { signal }) => $fetch('/api/users', { signal }))
const { data: users2 } = useAsyncData('users-2', (_nuxtApp, { signal }) => $fetch('/api/users', { signal }))
```
#### Reactive Keys
You can use computed refs, plain refs or getter functions as keys, allowing for dynamic data fetching that automatically updates when dependencies change:
```ts
// Using a computed property as a key
const userId = ref('123')
const { data: user } = useAsyncData(
computed(() => `user-${userId.value}`),
() => fetchUser(userId.value),
)
// When userId changes, the data will be automatically refetched
// and the old data will be cleaned up if no other components use it
userId.value = '456'
```
#### Refresh and execute
If you want to fetch or refresh data manually, use the `execute` or `refresh` function provided by the composables.
```vue twoslash
<script setup lang="ts">
const { data, error, execute, refresh } = await useFetch('/api/users')
</script>
<template>
<div>
<p>{{ data }}</p>
<button @click="() => refresh()">
Refresh data
</button>
</div>
</template>
```
The `execute` function is an alias for `refresh` that works in exactly the same way but is more semantic for cases when the fetch is [not immediate](/docs/4.x/getting-started/data-fetching#not-immediate).
::tip
To globally refetch or invalidate cached data, see [`clearNuxtData`](/docs/4.x/api/utils/clear-nuxt-data) and [`refreshNuxtData`](/docs/4.x/api/utils/refresh-nuxt-data).
::
#### Clear
If you want to clear the data provided, for whatever reason, without needing to know the specific key to pass to `clearNuxtData`, you can use the `clear` function provided by the composables.
```vue twoslash
<script setup lang="ts">
const { data, clear } = await useFetch('/api/users')
const route = useRoute()
watch(() => route.path, (path) => {
if (path === '/') {
clear()
}
})
</script>
```
#### Watch
To re-run your fetching function each time other reactive values in your application change, use the `watch` option. You can use it for one or multiple _watchable_ elements.
```vue twoslash
<script setup lang="ts">
const id = ref(1)
const { data, error, refresh } = await useFetch('/api/users', {
/* Changing the id will trigger a refetch */
watch: [id],
})
</script>
```
Note that **watching a reactive value won't change the URL fetched**. For example, this will keep fetching the same initial ID of the user because the URL is constructed at the moment the function is invoked.
```vue
<script setup lang="ts">
const id = ref(1)
const { data, error, refresh } = await useFetch(`/api/users/${id.value}`, {
watch: [id],
})
</script>
```
If you need to change the URL based on a reactive value, you may want to use a [computed URL](/docs/4.x/getting-started/data-fetching#computed-url) instead.
When reactive fetch options are provided, they'll be automatically watched and trigger refetches. In some cases, it can be useful to opt-out of this behavior by specifying `watch: false`.
```ts
const id = ref(1)
// Won't automatically refetch when id changes
const { data, execute } = await useFetch('/api/users', {
query: { id }, // id is watched by default
watch: false, // disables automatic watching of id
})
// doesn't trigger refetch
id.value = 2
```
#### Computed URL
Sometimes you may need to compute a URL from reactive values, and refresh the data each time these change. Instead of juggling your way around, you can attach each param as a reactive value. Nuxt will automatically use the reactive value and re-fetch each time it changes.
```vue
<script setup lang="ts">
const id = ref(null)
const { data, status } = useLazyFetch('/api/user', {
query: {
user_id: id,
},
})
</script>
```
In the case of more complex URL construction, you may use a callback as a [computed getter](https://vuejs.org/guide/essentials/computed) that returns the URL string.
Every time a dependency changes, the data will be fetched using the newly constructed URL. Combine this with [not-immediate](/docs/4.x/getting-started/data-fetching#not-immediate), and you can wait until the reactive element changes before fetching.
```vue
<script setup lang="ts">
const id = ref(null)
const { data, status } = useLazyFetch(() => `/api/users/${id.value}`, {
immediate: false,
})
const pending = computed(() => status.value === 'pending')
</script>
<template>
<div>
<!-- disable the input while fetching -->
<input
v-model="id"
type="number"
:disabled="pending"
>
<div v-if="status === 'idle'">
Type an user ID
</div>
<div v-else-if="pending">
Loading ...
</div>
<div v-else>
{{ data }}
</div>
</div>
</template>
```
If you need to force a refresh when other reactive values change, you can also [watch other values](/docs/4.x/getting-started/data-fetching#watch).
### Not immediate
The `useFetch` composable will start fetching data the moment is invoked. You may prevent this by setting `immediate: false`, for example, to wait for user interaction.
With that, you will need both the `status` to handle the fetch lifecycle, and `execute` to start the data fetch.
```vue
<script setup lang="ts">
const { data, error, execute, status } = await useLazyFetch('/api/comments', {
immediate: false,
})
</script>
<template>
<div v-if="status === 'idle'">
<button @click="execute">
Get data
</button>
</div>
<div v-else-if="status === 'pending'">
Loading comments...
</div>
<div v-else>
{{ data }}
</div>
</template>
```
For finer control, the `status` variable can be:
- `idle` when the fetch hasn't started
- `pending` when a fetch has started but not yet completed
- `error` when the fetch fails
- `success` when the fetch is completed successfully
## Passing Headers and Cookies
When we call `$fetch` in the browser, user headers like `cookie` will be directly sent to the API.
Normally, during server-side-rendering, due to security considerations, the `$fetch` wouldn't include the user's browser cookies, nor pass on cookies from the fetch response.
However, when calling `useFetch` with a relative URL on the server, Nuxt will use [`useRequestFetch`](/docs/4.x/api/composables/use-request-fetch) to proxy headers and cookies (with the exception of headers not meant to be forwarded, like `host`).
### Pass Cookies From Server-side API Calls on SSR Response
If you want to pass on/proxy cookies in the other direction, from an internal request back to the client, you will need to handle this yourself.
```ts [app/composables/fetch.ts]
import type { H3Event } from 'h3'
export const fetchWithCookie = async (event: H3Event, url: string) => {
/* Get the response from the server endpoint */
const res = await $fetch.raw(url)
/* Get the cookies from the response */
const cookies = res.headers.getSetCookie()
/* Attach each cookie to our incoming Request */
for (const cookie of cookies) {
event.res.headers.append('set-cookie', cookie)
}
/* Return the data of the response */
return res._data
}
```
```vue
<script setup lang="ts">
// This composable will automatically pass cookies to the client
const event = useRequestEvent()
const { data: result } = await useAsyncData(() => fetchWithCookie(event!, '/api/with-cookie'))
onMounted(() => console.log(document.cookie))
</script>
```
## Options API Support
Nuxt provides a way to perform `asyncData` fetching within the Options API. You must wrap your component definition within `defineNuxtComponent` for this to work.
```vue
<script>
export default defineNuxtComponent({
/* Use the fetchKey option to provide a unique key */
fetchKey: 'hello',
async asyncData () {
return {
hello: await $fetch('/api/hello'),
}
},
})
</script>
```
::note
Using `<script setup>` or `<script setup lang="ts">` are the recommended way of declaring Vue components in Nuxt.
::
:read-more{to="/docs/4.x/api/utils/define-nuxt-component"}
## Serializing Data From Server to Client
When using `useAsyncData` and `useLazyAsyncData` to transfer data fetched on server to the client (as well as anything else that utilizes [the Nuxt payload](/docs/4.x/api/composables/use-nuxt-app#payload)), the payload is serialized with [`devalue`](https://github.com/sveltejs/devalue). This allows us to transfer not just basic JSON but also to serialize and revive/deserialize more advanced kinds of data, such as regular expressions, Dates, Map and Set, `ref`, `reactive`, `shallowRef`, `shallowReactive` and `NuxtError` - and more.
It is also possible to define your own serializer/deserializer for types that are not supported by Nuxt. You can read more in the [`useNuxtApp`](/docs/4.x/api/composables/use-nuxt-app#payload) docs.
::note
Note that this _does not apply_ to data passed from your server routes when fetched with `$fetch` or `useFetch` - see the next section for more information.
::
## Serializing Data From API Routes
When fetching data from the `server` directory, the response is serialized using `JSON.stringify`. However, since serialization is limited to only JavaScript primitive types, Nuxt does its best to convert the return type of `$fetch` and [`useFetch`](/docs/4.x/api/composables/use-fetch) to match the actual value.
::read-more{icon="i-simple-icons-mdnwebdocs" to="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#description" target="_blank"}
Learn more about `JSON.stringify` limitations.
::
### Example
```ts [server/api/foo.ts]
export default defineEventHandler(() => {
return new Date()
})
```
```vue [app/app.vue]
<script setup lang="ts">
// Type of `data` is inferred as string even though we returned a Date object
const { data } = await useFetch('/api/foo')
</script>
```
### Custom serializer function
To customize the serialization behavior, you can define a `toJSON` function on your returned object. If you define a `toJSON` method, Nuxt will respect the return type of the function and will not try to convert the types.
```ts [server/api/bar.ts]
export default defineEventHandler(() => {
const data = {
createdAt: new Date(),
toJSON () {
return {
createdAt: {
year: this.createdAt.getFullYear(),
month: this.createdAt.getMonth(),
day: this.createdAt.getDate(),
},
}
},
}
return data
})
```
```vue [app/app.vue]
<script setup lang="ts">
// Type of `data` is inferred as
// {
// createdAt: {
// year: number
// month: number
// day: number
// }
// }
const { data } = await useFetch('/api/bar')
</script>
```
### Using an alternative serializer
Nuxt does not currently support an alternative serializer to `JSON.stringify`. However, you can return your payload as a normal string and utilize the `toJSON` method to maintain type safety.
In the example below, we use [superjson](https://github.com/flightcontrolhq/superjson) as our serializer.
```ts [server/api/superjson.ts]
import superjson from 'superjson'
export default defineEventHandler(() => {
const data = {
createdAt: new Date(),
// Workaround the type conversion
toJSON () {
return this
},
}
// Serialize the output to string, using superjson
return superjson.stringify(data) as unknown as typeof data
})
```
```vue [app/app.vue]
<script setup lang="ts">
import superjson from 'superjson'
// `date` is inferred as { createdAt: Date } and you can safely use the Date object methods
const { data } = await useFetch('/api/superjson', {
transform: (value) => {
return superjson.parse(value as unknown as string)
},
})
</script>
```
## Recipes
### Consuming SSE (Server-Sent Events) via POST request
::tip
If you're consuming SSE via GET request, you can use [`EventSource`](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) or VueUse composable [`useEventSource`](https://vueuse.org/core/useeventsource/).
::
When consuming SSE via POST request, you need to handle the connection manually. Here's how you can do it:
```ts
// Make a POST request to the SSE endpoint
const response = await $fetch<ReadableStream>('/chats/ask-ai', {
method: 'POST',
body: {
query: 'Hello AI, how are you?',
},
responseType: 'stream',
})
// Create a new ReadableStream from the response with TextDecoderStream to get the data as text
const reader = response.pipeThrough(new TextDecoderStream()).getReader()
// Read the chunk of data as we get it
while (true) {
const { value, done } = await reader.read()
if (done) { break }
console.log('Received:', value)
}
```
### Making parallel requests
When requests don't rely on each other, you can make them in parallel with `Promise.all()` to boost performance.
```ts
const { data } = await useAsyncData((_nuxtApp, { signal }) => {
return Promise.all([
$fetch('/api/comments/', { signal }),
$fetch('/api/author/12', { signal }),
])
})
const comments = computed(() => data.value?.[0])
const author = computed(() => data.value?.[1])
```
:video-accordion{title="Watch a video from Vue School on parallel data fetching" videoId="1024262536" platform="vimeo"}

View File

@@ -0,0 +1,230 @@
---
title: 'State Management'
description: Nuxt provides powerful state management libraries and the useState composable to create a reactive and SSR-friendly shared state.
navigation.icon: i-lucide-database
---
Nuxt provides the [`useState`](/docs/4.x/api/composables/use-state) composable to create a reactive and SSR-friendly shared state across components.
[`useState`](/docs/4.x/api/composables/use-state) is an SSR-friendly [`ref`](https://vuejs.org/api/reactivity-core#ref) replacement. Its value will be preserved after server-side rendering (during client-side hydration) and shared across all components using a unique key.
:video-accordion{title="Watch a video from Alexander Lichter about why and when to use useState" videoId="mv0WcBABcIk"}
::important
Because the data inside [`useState`](/docs/4.x/api/composables/use-state) will be serialized to JSON, it is important that it does not contain anything that cannot be serialized, such as classes, functions or symbols.
::
::read-more{to="/docs/4.x/api/composables/use-state"}
Read more about `useState` composable.
::
## Best Practices
::warning
Never define `const state = ref()` outside of `<script setup>` or `setup()` function.<br>
For example, doing `export myState = ref({})` would result in state shared across requests on the server and can lead to memory leaks.
::
::tip{icon="i-lucide-circle-check"}
Instead use `const useX = () => useState('x')`
::
## Examples
### Basic Usage
In this example, we use a component-local counter state. Any other component that uses `useState('counter')` shares the same reactive state.
```vue twoslash [app/app.vue]
<script setup lang="ts">
const counter = useState('counter', () => Math.round(Math.random() * 1000))
</script>
<template>
<div>
Counter: {{ counter }}
<button @click="counter++">
+
</button>
<button @click="counter--">
-
</button>
</div>
</template>
```
:link-example{to="/docs/4.x/examples/features/state-management"}
::note
To globally invalidate cached state, see [`clearNuxtState`](/docs/4.x/api/utils/clear-nuxt-state) util.
::
### Initializing State
Most of the time, you will want to initialize your state with data that resolves asynchronously. You can use the [`app.vue`](/docs/4.x/directory-structure/app/app) component with the [`callOnce`](/docs/4.x/api/utils/call-once) util to do so.
```vue twoslash [app/app.vue]
<script setup lang="ts">
const websiteConfig = useState('config')
await callOnce(async () => {
websiteConfig.value = await $fetch('https://my-cms.com/api/website-config')
})
</script>
```
::tip
This is similar to the [`nuxtServerInit` action](https://v2.nuxt.com/docs/directory-structure/store/#the-nuxtserverinit-action) in Nuxt 2, which allows filling the initial state of your store server-side before rendering the page.
::
:read-more{to="/docs/4.x/api/utils/call-once"}
### Usage with Pinia
In this example, we leverage the [Pinia module](/modules/pinia) to create a global store and use it across the app.
::important
Make sure to install the Pinia module with `npx nuxt module add pinia` or follow the [module's installation steps](https://pinia.vuejs.org/ssr/nuxt.html#Installation).
::
::code-group
```ts [app/stores/website.ts]
export const useWebsiteStore = defineStore('websiteStore', {
state: () => ({
name: '',
description: '',
}),
actions: {
async fetch () {
const infos = await $fetch('https://api.nuxt.com/modules/pinia')
this.name = infos.name
this.description = infos.description
},
},
})
```
```vue [app/app.vue]
<script setup lang="ts">
const website = useWebsiteStore()
await callOnce(website.fetch)
</script>
<template>
<main>
<h1>{{ website.name }}</h1>
<p>{{ website.description }}</p>
</main>
</template>
```
::
## Advanced Usage
::code-group
```ts [app/composables/locale.ts]
import type { Ref } from 'vue'
export const useLocale = () => {
return useState<string>('locale', () => useDefaultLocale().value)
}
export const useDefaultLocale = (fallback = 'en-US') => {
const locale = ref(fallback)
if (import.meta.server) {
const reqLocale = useRequestHeaders()['accept-language']?.split(',')[0]
if (reqLocale) {
locale.value = reqLocale
}
} else if (import.meta.client) {
const navLang = navigator.language
if (navLang) {
locale.value = navLang
}
}
return locale
}
export const useLocales = () => {
const locale = useLocale()
const locales = ref([
'en-US',
'en-GB',
// ...,
'ja-JP-u-ca-japanese',
])
if (!locales.value.includes(locale.value)) {
locales.value.unshift(locale.value)
}
return locales
}
export const useLocaleDate = (date: Ref<Date> | Date, locale = useLocale()) => {
return computed(() => new Intl.DateTimeFormat(locale.value, { dateStyle: 'full' }).format(unref(date)))
}
```
```vue [app/app.vue]
<script setup lang="ts">
const locales = useLocales()
const locale = useLocale()
const date = useLocaleDate(new Date('2016-10-26'))
</script>
<template>
<div>
<h1>Nuxt birthday</h1>
<p>{{ date }}</p>
<label for="locale-chooser">Preview a different locale</label>
<select
id="locale-chooser"
v-model="locale"
>
<option
v-for="loc of locales"
:key="loc"
:value="loc"
>
{{ loc }}
</option>
</select>
</div>
</template>
```
::
:link-example{to="/docs/4.x/examples/advanced/locale"}
## Shared State
By using [auto-imported composables](/docs/4.x/directory-structure/app/composables) we can define global type-safe states and import them across the app.
```ts twoslash [composables/states.ts]
export const useColor = () => useState<string>('color', () => 'pink')
```
```vue [app/app.vue]
<script setup lang="ts">
// ---cut-start---
const useColor = () => useState<string>('color', () => 'pink')
// ---cut-end---
const color = useColor() // Same as useState('color')
</script>
<template>
<p>Current color: {{ color }}</p>
</template>
```
:video-accordion{title="Watch a video from Daniel Roe on how to deal with global state and SSR in Nuxt" videoId="dZSNW07sO-A"}
## Using third-party libraries
Nuxt **used to rely** on the Vuex library to provide global state management. If you are migrating from Nuxt 2, please head to [the migration guide](/docs/4.x/migration/configuration#vuex).
Nuxt is not opinionated about state management, so feel free to choose the right solution for your needs. There are multiple integrations with the most popular state management libraries, including:
- [Pinia](/modules/pinia) - the official Vue recommendation
- [Harlem](/modules/harlem) - immutable global state management
- [XState](/modules/xstate) - state machine approach with tools for visualizing and testing your state logic

View File

@@ -0,0 +1,241 @@
---
title: 'Error Handling'
description: 'Learn how to catch and handle errors in Nuxt.'
navigation.icon: i-lucide-bug-off
---
Nuxt is a full-stack framework, which means there are several sources of unpreventable user runtime errors that can happen in different contexts:
- Errors during the Vue rendering lifecycle (SSR & CSR)
- Server and client startup errors (SSR + CSR)
- Errors during Nitro server lifecycle ([`server/`](/docs/4.x/directory-structure/server) directory)
- Errors downloading JS chunks
::tip
**SSR** stands for **Server-Side Rendering** and **CSR** for **Client-Side Rendering**.
::
## Vue Errors
You can hook into Vue errors using [`onErrorCaptured`](https://vuejs.org/api/composition-api-lifecycle#onerrorcaptured).
In addition, Nuxt provides a [`vue:error`](/docs/4.x/api/advanced/hooks#app-hooks-runtime) hook that will be called if any errors propagate up to the top level.
If you are using an error reporting framework, you can provide a global handler through [`vueApp.config.errorHandler`](https://vuejs.org/api/application#app-config-errorhandler). It will receive all Vue errors, even if they are handled.
```ts twoslash [plugins/error-handler.ts]
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.config.errorHandler = (error, instance, info) => {
// handle error, e.g. report to a service
}
// Also possible
nuxtApp.hook('vue:error', (error, instance, info) => {
// handle error, e.g. report to a service
})
})
```
::note
Note that the `vue:error` hook is based on [`onErrorCaptured`](https://vuejs.org/api/composition-api-lifecycle#onerrorcaptured) lifecycle hook.
::
## Startup Errors
Nuxt will call the `app:error` hook if there are any errors in starting your Nuxt application.
This includes:
- running [Nuxt plugins](/docs/4.x/directory-structure/app/plugins)
- processing `app:created` and `app:beforeMount` hooks
- rendering your Vue app to HTML (during SSR)
- mounting the app (on client-side), though you should handle this case with `onErrorCaptured` or with `vue:error`
- processing the `app:mounted` hook
## Nitro Server Errors
You cannot currently define a server-side handler for these errors, but can render an error page, see the [Render an Error Page](/docs/4.x/getting-started/error-handling#error-page) section.
## Errors with JS Chunks
You might encounter chunk loading errors due to a network connectivity failure or a new deployment (which invalidates your old, hashed JS chunk URLs). Nuxt provides built-in support for handling chunk loading errors by performing a hard reload when a chunk fails to load during route navigation.
You can change this behavior by setting `experimental.emitRouteChunkError` to `false` (to disable hooking into these errors at all) or to `manual` if you want to handle them yourself. If you want to handle chunk loading errors manually, you can check out the [the automatic implementation](https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/app/plugins/chunk-reload.client.ts) for ideas.
## Error Page
::note
When Nuxt encounters a fatal error (any unhandled error on the server, or an error created with `fatal: true` on the client) it will either render a JSON response (if requested with `Accept: application/json` header) or trigger a full-screen error page.
::
An error may occur during the server lifecycle when:
- processing your Nuxt plugins
- rendering your Vue app into HTML
- a server API route throws an error
It can also occur on the client side when:
- processing your Nuxt plugins
- before mounting the application (`app:beforeMount` hook)
- mounting your app if the error was not handled with `onErrorCaptured` or `vue:error` hook
- the Vue app is initialized and mounted in browser (`app:mounted`).
::read-more{to="/docs/4.x/api/advanced/hooks"}
Discover all the Nuxt lifecycle hooks.
::
Customize the default error page by adding `~/error.vue` in the source directory of your application, alongside `app.vue`.
<!-- TODO:twoslash: Twoslash does not support tsconfig paths yet -->
```vue [error.vue]
<script setup lang="ts">
import type { NuxtError } from '#app'
const props = defineProps({
error: Object as () => NuxtError,
})
const handleError = () => clearError({ redirect: '/' })
</script>
<template>
<div>
<h2>{{ error?.status }}</h2>
<button @click="handleError">
Clear errors
</button>
</div>
</template>
```
::read-more{to="/docs/4.x/directory-structure/app/error"}
Read more about `error.vue` and its uses.
::
For custom errors we highly recommend using `onErrorCaptured` composable that can be called in a page/component setup function or `vue:error` runtime nuxt hook that can be configured in a nuxt plugin.
```ts twoslash [plugins/error-handler.ts]
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.hook('vue:error', (err) => {
//
})
})
```
When you are ready to remove the error page, you can call the [`clearError`](/docs/4.x/api/utils/clear-error) helper function, which takes an optional path to redirect to (for example, if you want to navigate to a 'safe' page).
::important
Make sure to check before using anything dependent on Nuxt plugins, such as `$route` or `useRouter`, as if a plugin threw an error, then it won't be re-run until you clear the error.
::
::note
Rendering an error page is an entirely separate page load, meaning any registered middleware will run again. You can use [`useError`](/docs/4.x/getting-started/error-handling#useerror) in middleware to check if an error is being handled.
::
::note
If you are running on Node 16 and you set any cookies when rendering your error page, they will [overwrite cookies previously set](https://github.com/nuxt/nuxt/pull/20585). We recommend using a newer version of Node as Node 16 reached end-of-life in September 2023.
::
## Error Utils
### `useError`
```ts [TS Signature]
function useError (): Ref<Error | { url, status, statusText, message, description, data }>
```
This function will return the global Nuxt error that is being handled.
::read-more{to="/docs/4.x/api/composables/use-error"}
Read more about `useError` composable.
::
### `createError`
```ts [TS Signature]
function createError (err: string | { cause, data, message, name, stack, status, statusText, fatal }): Error
```
Create an error object with additional metadata. You can pass a string to be set as the error `message` or an object containing error properties. It is usable in both the Vue and Server portions of your app, and is meant to be thrown.
If you throw an error created with `createError`:
- on server-side, it will trigger a full-screen error page which you can clear with [`clearError`](/docs/4.x/getting-started/error-handling#clearerror).
- on client-side, it will throw a non-fatal error for you to handle. If you need to trigger a full-screen error page, then you can do this by setting `fatal: true`.
```vue twoslash [pages/movies/[slug\\].vue]
<script setup lang="ts">
const route = useRoute()
const { data } = await useFetch(`/api/movies/${route.params.slug}`)
if (!data.value) {
throw createError({
status: 404,
statusText: 'Page Not Found',
})
}
</script>
```
::tip
The `statusText` property is intended for short, HTTP-compliant status texts (e.g., "Not Found"). It should only contain horizontal tabs, spaces, and visible ASCII characters (`[\t\u0020-\u007E]`).
For any detailed descriptions, multi-line messages, or content with non-ASCII characters, you should always use the `message` property instead.
::
::read-more{to="/docs/4.x/api/utils/create-error"}
Read more about `createError` util.
::
### `showError`
```ts [TS Signature]
function showError (err: string | Error | { status, statusText }): Error
```
You can call this function at any point on client-side, or (on server side) directly within middleware, plugins or `setup()` functions. It will trigger a full-screen error page which you can clear with [`clearError`](/docs/4.x/getting-started/error-handling#clearerror).
It is recommended instead to use `throw createError()`.
::read-more{to="/docs/4.x/api/utils/show-error"}
Read more about `showError` util.
::
### `clearError`
```ts [TS Signature]
function clearError (options?: { redirect?: string }): Promise<void>
```
This function will clear the currently handled Nuxt error. It also takes an optional path to redirect to (for example, if you want to navigate to a 'safe' page).
::read-more{to="/docs/4.x/api/utils/clear-error"}
Read more about `clearError` util.
::
## Render Error in Component
Nuxt also provides a [`<NuxtErrorBoundary>`](/docs/4.x/api/components/nuxt-error-boundary) component that allows you to handle client-side errors within your app, without replacing your entire site with an error page.
This component is responsible for handling errors that occur within its default slot. On client-side, it will prevent the error from bubbling up to the top level, and will render the `#error` slot instead.
The `#error` slot will receive `error` as a prop. (If you set `error = null` it will trigger re-rendering the default slot; you'll need to ensure that the error is fully resolved first or the error slot will just be rendered a second time.)
::tip
If you navigate to another route, the error will be cleared automatically.
::
```vue [app/pages/index.vue]
<template>
<!-- some content -->
<NuxtErrorBoundary @error="someErrorLogger">
<!-- You use the default slot to render your content -->
<template #error="{ error, clearError }">
You can display the error locally here: {{ error }}
<button @click="clearError">
This will clear the error.
</button>
</template>
</NuxtErrorBoundary>
</template>
```
:link-example{to="/docs/4.x/examples/advanced/error-handling"}

View File

@@ -0,0 +1,96 @@
---
title: 'Server'
description: Build full-stack applications with Nuxt's server framework. You can fetch data from your database or another server, create APIs, or even generate static server-side content like a sitemap or a RSS feed - all from a single codebase.
navigation.icon: i-lucide-pc-case
---
:read-more{to="/docs/4.x/directory-structure/server"}
## Powered by Nitro
![Server engine](/assets/docs/getting-started/server.svg)
Nuxt's server is [Nitro](https://github.com/nitrojs/nitro). It was originally created for Nuxt but is now part of [UnJS](https://unjs.io) and open for other frameworks - and can even be used on its own.
Using Nitro gives Nuxt superpowers:
- Full control of the server-side part of your app
- Universal deployment on any provider (many zero-config)
- Hybrid rendering
Nitro is internally using [h3](https://github.com/h3js/h3), a minimal H(TTP) framework built for high performance and portability.
:video-accordion{title="Watch a video from Alexander Lichter to understand the responsibilities of Nuxt and Nitro in your application" videoId="DkvgJa-X31k"}
## Server Endpoints & Middleware
You can easily manage the server-only part of your Nuxt app, from API endpoints to middleware.
Both endpoints and middleware can be defined like this:
```ts twoslash [server/api/test.ts]
import { defineEventHandler } from 'nitro/h3'
export default defineEventHandler(async (event) => {
// ... Do whatever you want here
})
```
And you can directly return `text`, `json`, `html` or even a `stream`.
Out-of-the-box, it supports **hot module replacement** and **auto-import** like the other parts of your Nuxt application.
:read-more{to="/docs/4.x/directory-structure/server"}
## Universal Deployment
Nitro offers the ability to deploy your Nuxt app anywhere, from a bare metal server to the edge network, with a start time of just a few milliseconds. That's fast!
:read-more{to="/blog/nuxt-on-the-edge"}
There are more than 15 presets to build your Nuxt app for different cloud providers and servers, including:
- [Cloudflare Workers](https://workers.cloudflare.com)
- [Netlify Functions](https://www.netlify.com/platform/core/functions/)
- [Vercel Cloud](https://vercel.com/home)
Or for other runtimes:
::card-group
:card{icon="i-logos-deno" title="Deno" to="https://deno.com" target="_blank"}
:card{icon="i-logos-bun" title="Bun" to="https://bun.com" target="_blank"}
::
:read-more{to="/docs/4.x/getting-started/deployment"}
## Hybrid Rendering
Nitro has a powerful feature called `routeRules` which allows you to define a set of rules to customize how each route of your Nuxt app is rendered (and more).
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
routeRules: {
// Generated at build time for SEO purpose
'/': { prerender: true },
// Cached for 1 hour
'/api/*': { cache: { maxAge: 60 * 60 } },
// Redirection to avoid 404
'/old-page': {
redirect: { to: '/new-page', status: 302 },
},
// ...
},
})
```
::read-more{to="/docs/4.x/guide/concepts/rendering#hybrid-rendering"}
Learn about all available route rules are available to customize the rendering mode of your routes.
::
In addition, there are some route rules (for example, `ssr`, `appMiddleware`, and `noScripts`) that are Nuxt specific to change the behavior when rendering your pages to HTML.
Some route rules (`appMiddleware`, `redirect` and `prerender`) also affect client-side behavior.
Nitro is used to build the app for server side rendering, as well as pre-rendering.
:read-more{to="/docs/4.x/guide/concepts/rendering"}

View File

@@ -0,0 +1,172 @@
---
title: 'Layers'
description: Nuxt provides a powerful system that allows you to extend the default files, configs, and much more.
navigation.icon: i-lucide-layers
---
One of the core features of Nuxt is the layers and extending support. You can extend a default Nuxt application to reuse components, utils, and configuration. The layers structure is almost identical to a standard Nuxt application which makes them easy to author and maintain.
## Use Cases
- Share reusable configuration presets across projects using `nuxt.config` and `app.config`
- Create a component library using [`app/components/`](/docs/4.x/directory-structure/app/components) directory
- Create utility and composable library using [`app/composables/`](/docs/4.x/directory-structure/app/composables) and [`app/utils/`](/docs/4.x/directory-structure/app/utils) directories
- Create Nuxt module presets
- Share standard setup across projects
- Create Nuxt themes
- Enhance code organization by implementing a modular architecture and support Domain-Driven Design (DDD) pattern in large scale projects.
## Usage
By default, any layers within your project in the `~~/layers` directory will be automatically registered as layers in your project.
::note
Layer auto-registration was introduced in Nuxt v3.12.0.
::
In addition, named layer aliases to the `srcDir` of each of these layers will automatically be created. For example, you will be able to access the `~~/layers/test` layer via `#layers/test`.
::note
Named layer aliases were introduced in Nuxt v3.16.0.
::
In addition, you can extend from a layer by adding the [extends](/docs/4.x/api/nuxt-config#extends) property to your [`nuxt.config`](/docs/4.x/directory-structure/nuxt-config) file.
```ts [nuxt.config.ts]
export default defineNuxtConfig({
extends: [
// Extend from a local layer
'../base',
// Extend from an installed npm package
'@my-themes/awesome',
// Extend from a git repository
'github:my-themes/awesome#v1',
],
})
```
You can also pass an authentication token if you are extending from a private GitHub repository:
```ts [nuxt.config.ts]
export default defineNuxtConfig({
extends: [
// per layer configuration
['github:my-themes/private-awesome', { auth: process.env.GITHUB_TOKEN }],
],
})
```
::note
If a branch is not specified, this will clone `main`.
::
::tip
You can override a layer's alias by specifying it in the options next to the layer source.
```ts [nuxt.config.ts]
export default defineNuxtConfig({
extends: [
[
'github:my-themes/awesome',
{
meta: {
name: 'my-awesome-theme',
},
},
],
],
})
```
::
Nuxt uses [unjs/c12](https://github.com/unjs/c12) and [unjs/giget](https://github.com/unjs/giget) for extending remote layers. Check the documentation for more information and all available options.
## Layer Priority
When using multiple layers, it's important to understand the override order. Layers with **higher priority** override layers with lower priority when they define the same files or components.
### Priority Order
From highest to lowest priority:
1. **Your project files** - always have the highest priority
2. **Auto-scanned layers** from `~~/layers` directory - sorted alphabetically (Z has higher priority than A)
3. **Layers in `extends`** config - first entry has higher priority than second
### Practical Example
Consider multiple layers defining the same component:
```bash [Directory structure]
layers/
1.base/
app/components/Button.vue # Base button style
2.theme/
app/components/Button.vue # Themed button (overrides base)
app/
components/Button.vue # Project button (overrides all layers)
```
In this case:
- If only layers exist, `2.theme/Button.vue` is used (higher alphabetically)
- If `app/components/Button.vue` exists in your project, it overrides all layers
### Controlling Priority
You can prefix layer directories with numbers to control the order:
```bash [Directory structure]
layers/
1.base/ # Lowest priority
2.features/ # Medium priority
3.admin/ # Highest priority (among layers)
```
::tip
This pattern is useful for creating base layers with defaults that can be progressively overridden by more specific layers.
::
### When to Use Each
- **`~~/layers` directory** - Use for local layers that are part of your project
- **`extends`** - Use for external dependencies (npm packages, remote repositories) or layers outside your project directory
### Full Example with extends
```ts [nuxt.config.ts]
export default defineNuxtConfig({
extends: [
'../base', // Local layer outside project
'@my-themes/awesome', // NPM package
'github:my-themes/awesome#v1', // Remote repository
],
})
```
If you also have `~~/layers/custom`, the priority order is:
- Your project files (highest)
- `~~/layers/custom`
- `../base`
- `@my-themes/awesome`
- `github:my-themes/awesome#v1` (lowest)
::read-more{to="/docs/4.x/directory-structure/layers"}
Learn about the **layers/ directory** to organize and share reusable code, components, composables, and configurations across your Nuxt application.
::
::read-more{to="/docs/4.x/guide/going-further/layers"}
Read more about layers in the **Layer Author Guide**.
::
:video-accordion{title="Watch a video from Learn Vue about Nuxt Layers" videoId="lnFCM7c9f7I"}
:video-accordion{title="Watch a video from Alexander Lichter about Nuxt Layers" videoId="fr5yo3aVkfA"}
## Examples
::card-group
::card{icon="i-simple-icons-github" title="Content Wind" to="https://github.com/Atinux/content-wind" target="_blank"}
A lightweight Nuxt theme to build a Markdown driven website. Powered by Nuxt Content, TailwindCSS and Iconify.
::
::

View File

@@ -0,0 +1,208 @@
---
title: "Prerendering"
description: Nuxt allows pages to be statically rendered at build time to improve certain performance or SEO metrics
navigation.icon: i-lucide-file-code-2
---
Nuxt allows for select pages from your application to be rendered at build time. Nuxt will serve the prebuilt pages when requested instead of generating them on the fly.
:read-more{title="Nuxt rendering modes" to="/docs/4.x/guide/concepts/rendering"}
## Crawl-based Pre-rendering
Use the [`nuxt generate` command](/docs/4.x/api/commands/generate) to build and pre-render your application using the [Nitro](/docs/4.x/guide/concepts/server-engine) crawler. This command is similar to `nuxt build` with the `nitro.static` option set to `true`, or running `nuxt build --prerender`.
This will build your site, stand up a nuxt instance, and, by default, prerender the root page `/` along with any of your site's pages it links to, any of your site's pages they link to, and so on.
::code-group{sync="pm"}
```bash [npm]
npx nuxt generate
```
```bash [yarn]
yarn nuxt generate
```
```bash [pnpm]
pnpm nuxt generate
```
```bash [bun]
bun x nuxt generate
```
```bash [deno]
deno x nuxt generate
```
::
You can now deploy the `.output/public` directory to any static hosting service or preview it locally with `npx serve .output/public`.
Working of the Nitro crawler:
1. Load the HTML of your application's root route (`/`), any non-dynamic pages in your `~/pages` directory, and any other routes in the `nitro.prerender.routes` array.
2. Save the HTML and `payload.json` to the `~/.output/public/` directory to be served statically.
3. Find all anchor tags (`<a href="...">`) in the HTML to navigate to other routes.
4. Repeat steps 1-3 for each anchor tag found until there are no more anchor tags to crawl.
This is important to understand since pages that are not linked to a discoverable page can't be pre-rendered automatically.
### Payload Extraction
Nuxt generates `_payload.json` alongside HTML for:
- Prerendered routes (at build time)
- ISR/SWR routes (on first request)
Payloads contain serialized data from `useAsyncData` and `useFetch`. Client-side navigation loads these cached payloads instead of re-fetching data. Configure dynamic routes like `pages/[...slug].vue` with route rules: `'/**': { isr: true }`.
::read-more{to="/docs/4.x/api/commands/generate#nuxt-generate"}
Read more about the `nuxt generate` command.
::
### Selective Pre-rendering
You can manually specify routes that [Nitro](/docs/4.x/guide/concepts/server-engine) will fetch and pre-render during the build or ignore routes that you don't want to pre-render like `/dynamic` in the `nuxt.config` file:
```ts twoslash [nuxt.config.ts]
// @errors: 2353
export default defineNuxtConfig({
nitro: {
prerender: {
routes: ['/user/1', '/user/2'],
ignore: ['/dynamic'],
},
},
})
```
You can combine this with the `crawlLinks` option to pre-render a set of routes that the crawler can't discover like your `/sitemap.xml` or `/robots.txt`:
```ts twoslash [nuxt.config.ts]
// @errors: 2353
export default defineNuxtConfig({
nitro: {
prerender: {
crawlLinks: true,
routes: ['/sitemap.xml', '/robots.txt'],
},
},
})
```
Setting `nitro.prerender` to `true` is similar to `nitro.prerender.crawlLinks` to `true`.
::read-more{to="https://nitro.build/config#prerender"}
Read more about pre-rendering in the Nitro documentation.
::
Lastly, you can manually configure this using routeRules.
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
routeRules: {
// Set prerender to true to configure it to be prerendered
'/rss.xml': { prerender: true },
// Set it to false to configure it to be skipped for prerendering
'/this-DOES-NOT-get-prerendered': { prerender: false },
// Everything under /blog gets prerendered as long as it
// is linked to from another page
'/blog/**': { prerender: true },
},
})
```
::read-more{to="https://nitro.build/config#routerules"}
Read more about Nitro's `routeRules` configuration.
::
As a shorthand, you can also configure this in a page file using [`defineRouteRules`](/docs/4.x/api/utils/define-route-rules).
::read-more{to="/docs/4.x/guide/going-further/experimental-features#inlinerouterules" icon="i-lucide-star"}
This feature is experimental and in order to use it you must enable the `experimental.inlineRouteRules` option in your `nuxt.config`.
::
```vue [app/pages/index.vue]
<script setup>
// Or set at the page level
defineRouteRules({
prerender: true,
})
</script>
<template>
<div>
<h1>Homepage</h1>
<p>Pre-rendered at build time</p>
</div>
</template>
```
This will be translated to:
```ts [nuxt.config.ts]
export default defineNuxtConfig({
routeRules: {
'/': { prerender: true },
},
})
```
## Runtime Prerender Configuration
### `prerenderRoutes`
You can use this at runtime within a [Nuxt context](/docs/4.x/guide/going-further/nuxt-app#the-nuxt-context) to add more routes for Nitro to prerender.
```vue [app/pages/index.vue]
<script setup>
prerenderRoutes(['/some/other/url'])
prerenderRoutes('/api/content/article/my-article')
</script>
<template>
<div>
<h1>This will register other routes for prerendering when prerendered</h1>
</div>
</template>
```
:read-more{title="prerenderRoutes" to="/docs/4.x/api/utils/prerender-routes"}
### `prerender:routes` Nuxt hook
This is called before prerendering for additional routes to be registered.
```ts [nuxt.config.ts]
export default defineNuxtConfig({
hooks: {
async 'prerender:routes' (ctx) {
const { pages } = await fetch('https://api.some-cms.com/pages').then(
res => res.json(),
)
for (const page of pages) {
ctx.routes.add(`/${page.name}`)
}
},
},
})
```
### `prerender:generate` Nitro hook
This is called for each route during prerendering. You can use this for fine-grained handling of each route that gets prerendered.
```ts [nuxt.config.ts]
export default defineNuxtConfig({
nitro: {
hooks: {
'prerender:generate' (route) {
if (route.route?.includes('private')) {
route.skip = true
}
},
},
},
})
```

View File

@@ -0,0 +1,137 @@
---
title: 'Deployment'
description: Learn how to deploy your Nuxt application to any hosting provider.
navigation.icon: i-lucide-cloud
---
A Nuxt application can be deployed on a Node.js server, pre-rendered for static hosting, or deployed to serverless or edge (CDN) environments.
::tip
If you are looking for a list of cloud providers that support Nuxt, see the [Hosting providers](/deploy) section.
::
## Node.js Server
Discover the Node.js server preset with Nitro to deploy on any Node hosting.
- **Default output format** if none is specified or auto-detected <br>
- Loads only the required chunks to render the request for optimal cold start timing <br>
- Useful for deploying Nuxt apps to any Node.js hosting
### Entry Point
When running `nuxt build` with the Node server preset, the result will be an entry point that launches a ready-to-run Node server.
```bash [Terminal]
NODE_ENV=production node .output/server/index.mjs
```
This will launch your production Nuxt server that listens on port 3000 by default.
::important
Set `NODE_ENV=production` when running the server. Some dependencies (notably Vue Router) only strip development-only warnings when this is set, so leaving it unset can flood your logs with messages like `[Vue Router warn]: No match found for location with path …` on unmatched routes.
::
It respects the following runtime environment variables:
- `NITRO_PORT` or `PORT` (defaults to `3000`)
- `NITRO_HOST` or `HOST` (defaults to `'0.0.0.0'`)
- `NITRO_SSL_CERT` and `NITRO_SSL_KEY` - if both are present, this will launch the server in HTTPS mode. In the vast majority of cases, this should not be used other than for testing, and the Nitro server should be run behind a reverse proxy like nginx or Cloudflare which terminates SSL.
### PM2
[PM2](https://pm2.keymetrics.io/) (Process Manager 2) is a fast and easy solution for hosting your Nuxt application on your server or VM.
To use `pm2`, use an `ecosystem.config.cjs`:
```ts [ecosystem.config.cjs]
module.exports = {
apps: [
{
name: 'NuxtAppName',
port: '3000',
exec_mode: 'cluster',
instances: 'max',
script: './.output/server/index.mjs',
env: {
NODE_ENV: 'production',
},
},
],
}
```
### Cluster Mode
You can use `NITRO_PRESET=node_cluster` in order to leverage multi-process performance using Node.js [cluster](https://nodejs.org/dist/latest/docs/api/cluster.html) module.
By default, the workload gets distributed to the workers with the round robin strategy.
### Learn More
:read-more{to="https://nitro.build/deploy/runtimes/node" title="the Nitro documentation for node-server preset"}
:video-accordion{title="Watch Daniel Roe's short video on the topic" videoId="0x1H6K5yOfs"}
## Static Hosting
There are two ways to deploy a Nuxt application to any static hosting services:
- Static site generation (SSG) with `ssr: true` pre-renders routes of your application at build time. (This is the default behavior when running `nuxt generate`.) It will also generate `/200.html` and `/404.html` single-page app fallback pages, which can render dynamic routes or 404 errors on the client (though you may need to configure this on your static host).
- Alternatively, you can prerender your site with `ssr: false` (static single-page app). This will produce HTML pages with an empty `<div id="__nuxt"></div>` where your Vue app would normally be rendered. You will lose many SEO benefits of prerendering your site, so it is suggested instead to use [`<ClientOnly>`](/docs/4.x/api/components/client-only) to wrap the portions of your site that cannot be server rendered (if any).
:read-more{title="Nuxt prerendering" to="/docs/4.x/getting-started/prerendering"}
### Client-side Only Rendering
If you don't want to pre-render your routes, another way of using static hosting is to set the `ssr` property to `false` in the `nuxt.config` file. The `nuxt generate` command will then output an `.output/public/index.html` entrypoint and JavaScript bundles like a classic client-side Vue.js application.
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
ssr: false,
})
```
## Hosting Providers
Nuxt can be deployed to several cloud providers with a minimal amount of configuration:
:read-more{to="/deploy"}
## Presets
In addition to Node.js servers and static hosting services, a Nuxt project can be deployed with several well-tested presets and minimal amount of configuration.
You can explicitly set the desired preset in the [`nuxt.config.ts`](/docs/4.x/directory-structure/nuxt-config) file:
```ts twoslash [nuxt.config.ts]
// @errors: 2353
export default defineNuxtConfig({
nitro: {
preset: 'node-server',
},
})
```
... or use the `NITRO_PRESET` environment variable when running `nuxt build`:
```bash [Terminal]
NITRO_PRESET=node-server nuxt build
```
🔎 Check [the Nitro deployment](https://nitro.build/deploy) for all possible deployment presets and providers.
## CDN Proxy
In most cases, Nuxt can work with third-party content that is not generated or created by Nuxt itself. But sometimes such content can cause problems, especially Cloudflare's "Minification and Security Options".
Accordingly, you should make sure that the following options are unchecked / disabled in Cloudflare. Otherwise, unnecessary re-rendering or hydration errors could impact your production application.
1. Speed > Settings > Content Optimization > Disable "Rocket Loader™"
2. Security > Settings > Disable "Email Address Obfuscation"
With these settings, you can be sure that Cloudflare won't inject scripts into your Nuxt application that may cause unwanted side effects.
::tip
Their location on the Cloudflare dashboard sometimes changes so don't hesitate to look around.
::

View File

@@ -0,0 +1,877 @@
---
title: Testing
description: How to test your Nuxt application.
navigation.icon: i-lucide-circle-check
---
::tip
If you are a module author, you can find more specific information in the [Module Author's guide](/docs/4.x/guide/modules/testing).
::
Nuxt offers first-class support for end-to-end and unit testing of your Nuxt application via `@nuxt/test-utils`, a library of test utilities and configuration that currently powers the [tests we use on Nuxt itself](https://github.com/nuxt/nuxt/tree/main/test) and tests throughout the module ecosystem.
:video-accordion{title="Watch a video from Alexander Lichter about getting started with @nuxt/test-utils" videoId="yGzwk9xi9gU"}
## Installation
In order to allow you to manage your other testing dependencies, `@nuxt/test-utils` ships with various optional peer dependencies. For example:
- you can choose between `happy-dom` and `jsdom` for a runtime Nuxt environment
- you can choose between `vitest`, `cucumber`, `jest` and `playwright` for end-to-end test runners
- `playwright-core` is only required if you wish to use the built-in browser testing utilities (and are not using `@playwright/test` as your test runner)
::code-group{sync="pm"}
```bash [npm]
npm i --save-dev @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core
```
```bash [yarn]
yarn add --dev @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core
```
```bash [pnpm]
pnpm add -D @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core
```
```bash [bun]
bun add --dev @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core
```
::
## Unit Testing
We currently ship an environment for unit testing code that needs a [Nuxt](https://nuxt.com) runtime environment. It currently _only has support for `vitest`_ (although contribution to add other runtimes would be welcome).
### Setup
1. Add `@nuxt/test-utils/module` to your `nuxt.config` file (optional). It adds a Vitest integration to your Nuxt DevTools which supports running your unit tests in development.
```ts twoslash
export default defineNuxtConfig({
modules: [
'@nuxt/test-utils/module',
],
})
```
2. Create a `vitest.config.ts` with the following content:
```ts twoslash
import { defineConfig } from 'vitest/config'
import { defineVitestProject } from '@nuxt/test-utils/config'
export default defineConfig({
test: {
projects: [
{
test: {
name: 'unit',
include: ['test/unit/*.{test,spec}.ts'],
environment: 'node',
},
},
{
test: {
name: 'e2e',
include: ['test/e2e/*.{test,spec}.ts'],
environment: 'node',
},
},
await defineVitestProject({
test: {
name: 'nuxt',
include: ['test/nuxt/*.{test,spec}.ts'],
environment: 'nuxt',
},
}),
],
},
})
```
::tip
When importing `@nuxt/test-utils` in your vitest config, It is necessary to have `"type": "module"` specified in your `package.json` or rename your vitest config file appropriately.
> i.e., `vitest.config.m{ts,js}`.
::
::tip
It is possible to set environment variables for testing by using the `.env.test` file.
::
### Using a Nuxt Runtime Environment
Using [Vitest projects](https://vitest.dev/guide/projects.html#test-projects), you have fine-grained control over which tests run in which environment:
- **Unit tests**: Place regular unit tests in `test/unit/` - these run in a Node environment for speed
- **Nuxt tests**: Place tests that rely on the Nuxt runtime environment in `test/nuxt/` - these will run within a Nuxt runtime environment
#### Alternative: Simple Setup
If you prefer a simpler setup and want all tests to run in the Nuxt environment, you can use the basic configuration:
```ts twoslash
import { defineVitestConfig } from '@nuxt/test-utils/config'
import { fileURLToPath } from 'node:url'
export default defineVitestConfig({
test: {
environment: 'nuxt',
// you can optionally set Nuxt-specific environment options
// environmentOptions: {
// nuxt: {
// rootDir: fileURLToPath(new URL('./playground', import.meta.url)),
// domEnvironment: 'happy-dom', // 'happy-dom' (default) or 'jsdom'
// overrides: {
// // other Nuxt config you want to pass
// }
// }
// }
},
})
```
If you're using the simple setup with `environment: 'nuxt'` by default, you can opt _out_ of the [Nuxt environment](https://vitest.dev/guide/environment.html#test-environment) per test file as needed.
```ts twoslash
// @vitest-environment node
import { test } from 'vitest'
test('my test', () => {
// ... test without Nuxt environment!
})
```
::warning
This approach is not recommended as it creates a hybrid environment where Nuxt Vite plugins run but the Nuxt entry and `nuxtApp` are not initialized. This can lead to hard-to-debug errors.
::
### Organizing Your Tests
With the project-based setup, you might organize your tests as follows:
```bash [Directory structure]
test/
├── e2e/
│ └── ssr.test.ts
├── nuxt/
│ ├── components.test.ts
│ └── composables.test.ts
├── unit/
│ └── utils.test.ts
```
You can of course opt for any test structure, but keeping the Nuxt runtime environment separated from Nuxt end-to-end tests is important for test stability.
#### TypeScript Support in Tests
By default, test files in `test/nuxt/` and `tests/nuxt/` directories are included in the [Nuxt app TypeScript context](/docs/4.x/guide/concepts/typescript#how-nuxt-uses-project-references). That means they will recognise Nuxt aliases (like `~/`, `@/`, `#imports`) and TypeScript will be aware of auto-imports that work in your Nuxt app.
::tip
This matches the recommended structure where only tests that need the Nuxt runtime environment are placed in these directories. Unit tests in other directories like `test/unit/` can be added manually if needed.
::
##### Adding other test directories
If you have tests in other directories that you will be running in the Nuxt Vitest environment, you can include them in the Nuxt app TypeScript context by adding them to your configuration:
<!-- @case-police-ignore tsConfig -->
```ts [nuxt.config.ts]
export default defineNuxtConfig({
typescript: {
tsConfig: {
include: [
// this path is relative to the generated .nuxt/tsconfig.json
'../test/other-nuxt-context/**/*',
],
},
},
})
```
::important
Unit tests should not depend on Nuxt runtime features like auto-imports or composables. Only add TypeScript path alias support if your tests import from your source files (e.g., `~/utils/helpers`), not for Nuxt-specific features.
::
#### Running Tests
With the project setup, you can run different test suites:
```bash
# Run all tests
npx vitest
# Run only unit tests
npx vitest --project unit
# Run only Nuxt tests
npx vitest --project nuxt
# Run tests in watch mode
npx vitest --watch
```
::warning
When you run your tests within the Nuxt environment, they will be running in a [`happy-dom`](https://github.com/capricorn86/happy-dom) or [`jsdom`](https://github.com/jsdom/jsdom) environment. Before your tests run, a global Nuxt app will be initialized (including, for example, running any plugins or code you've defined in your `app.vue`).
This means you should take particular care not to mutate the global state in your tests (or, if you need to, to reset it afterwards).
::
### 🎭 Built-In Mocks
`@nuxt/test-utils` provides some built-in mocks for the DOM environment.
#### `intersectionObserver`
Default `true`, creates a dummy class without any functionality for the IntersectionObserver API
#### `indexedDB`
Default `false`, uses [`fake-indexeddb`](https://github.com/dumbmatter/fakeIndexedDB) to create a functional mock of the IndexedDB API
These can be configured in the `environmentOptions` section of your `vitest.config.ts` file:
```ts twoslash
import { defineVitestConfig } from '@nuxt/test-utils/config'
export default defineVitestConfig({
test: {
environmentOptions: {
nuxt: {
mock: {
intersectionObserver: true,
indexedDb: true,
},
},
},
},
})
```
### 🛠️ Helpers
`@nuxt/test-utils` provides a number of helpers to make testing Nuxt apps easier.
#### `mountSuspended`
`mountSuspended` allows you to mount any Vue component within the Nuxt environment, allowing async setup and access to injections from your Nuxt plugins.
::note
Under the hood, `mountSuspended` wraps `mount` from `@vue/test-utils`, so you can check out [the Vue Test Utils documentation](https://test-utils.vuejs.org/guide/) for more on the options you can pass, and how to use this utility.
::
For example:
```ts twoslash
// @noErrors
import { expect, it } from 'vitest'
import type { Component } from 'vue'
declare module '#components' {
export const SomeComponent: Component
}
// ---cut---
// tests/components/SomeComponents.nuxt.spec.ts
import { mountSuspended } from '@nuxt/test-utils/runtime'
import { SomeComponent } from '#components'
it('can mount some component', async () => {
const component = await mountSuspended(SomeComponent)
expect(component.text()).toMatchInlineSnapshot(
'"This is an auto-imported component"',
)
})
```
```ts twoslash
// @noErrors
import { expect, it } from 'vitest'
// ---cut---
// tests/components/SomeComponents.nuxt.spec.ts
import { mountSuspended } from '@nuxt/test-utils/runtime'
import App from '~/app.vue'
// tests/App.nuxt.spec.ts
it('can also mount an app', async () => {
const component = await mountSuspended(App, { route: '/test' })
expect(component.html()).toMatchInlineSnapshot(`
"<div>This is an auto-imported component</div>
<div> I am a global component </div>
<div>/</div>
<a href="/test"> Test link </a>"
`)
})
```
The options object accepts `@vue/test-utils` mount options and the following properties:
- `route`: the initial route, or `false` to skip the initial route change (default `/`).
#### `renderSuspended`
`renderSuspended` allows you to render any Vue component within the Nuxt environment using `@testing-library/vue`, allowing async setup and access to injections from your Nuxt plugins.
This should be used together with utilities from Testing Library, e.g. `screen` and `fireEvent`. Install [@testing-library/vue](https://testing-library.com/docs/vue-testing-library/intro/) in your project to use these.
Additionally, Testing Library also relies on testing globals for cleanup. You should turn these on in your [Vitest config](https://vitest.dev/config/globals).
The passed in component will be rendered inside a `<div id="test-wrapper"></div>`.
Examples:
```ts twoslash
// @noErrors
import { expect, it } from 'vitest'
import type { Component } from 'vue'
declare module '#components' {
export const SomeComponent: Component
}
// ---cut---
// tests/components/SomeComponents.nuxt.spec.ts
import { renderSuspended } from '@nuxt/test-utils/runtime'
import { SomeComponent } from '#components'
import { screen } from '@testing-library/vue'
it('can render some component', async () => {
await renderSuspended(SomeComponent)
expect(screen.getByText('This is an auto-imported component')).toBeDefined()
})
```
```ts twoslash
// @noErrors
import { expect, it } from 'vitest'
// ---cut---
// tests/App.nuxt.spec.ts
import { renderSuspended } from '@nuxt/test-utils/runtime'
import App from '~/app.vue'
it('can also render an app', async () => {
const html = await renderSuspended(App, { route: '/test' })
expect(html).toMatchInlineSnapshot(`
"<div id="test-wrapper">
<div>This is an auto-imported component</div>
<div> I am a global component </div>
<div>Index page</div><a href="/test"> Test link </a>
</div>"
`)
})
```
The options object accepts `@testing-library/vue` render options and the following properties:
- `route`: the initial route, or `false` to skip the initial route change (default `/`).
#### `mockNuxtImport`
`mockNuxtImport` allows you to mock Nuxt's auto import functionality. For example, to mock `useState`, you can do so like this:
```ts twoslash
import { mockNuxtImport } from '@nuxt/test-utils/runtime'
mockNuxtImport('useState', () => {
return () => {
return { value: 'mocked storage' }
}
})
// your tests here
```
You can explicitly type the mock for type safety, and use the original implementation passed to the factory function when mocking complex functionality.
```ts twoslash [test/nuxt/import.test.ts]
import { mockNuxtImport } from '@nuxt/test-utils/runtime'
mockNuxtImport<typeof useState>('useState', (original) => {
return (...args) => {
return { ...original('some-key'), value: 'mocked state' }
}
})
// or specify the target to mock
mockNuxtImport(useState, (original) => {
return (...args) => {
return { ...original('some-key'), value: 'mocked state' }
}
})
// your tests here
```
::note
`mockNuxtImport` can only be used once per mocked import per test file. It is actually a macro that gets transformed to `vi.mock` and `vi.mock` is hoisted, as described [in the Vitest docs](https://vitest.dev/api/vi#vi-mock).
::
If you need to mock a Nuxt import and provide different implementations between tests, you can do it by creating and exposing your mocks using [`vi.hoisted`](https://vitest.dev/api/vi#vi-hoisted), and then use those mocks in `mockNuxtImport`. You then have access to the mocked imports, and can change the implementation between tests. Be careful to [restore mocks](https://vitest.dev/api/mock#mockrestore) before or after each test to undo mock state changes between runs.
```ts twoslash
import { vi } from 'vitest'
import { mockNuxtImport } from '@nuxt/test-utils/runtime'
const { useStateMock } = vi.hoisted(() => {
return {
useStateMock: vi.fn(() => {
return { value: 'mocked storage' }
}),
}
})
mockNuxtImport('useState', () => {
return useStateMock
})
// Then, inside a test
useStateMock.mockImplementation(() => {
return { value: 'something else' }
})
```
If you need to mock behavior only inside a test, you can also use the following approach.
```ts twoslash
import { beforeEach, vi } from 'vitest'
import { mockNuxtImport } from '@nuxt/test-utils/runtime'
mockNuxtImport(useRoute, original => vi.fn(original))
beforeEach(() => {
vi.resetAllMocks()
})
// Then, inside a test
const useRouteOriginal = vi.mocked(useRoute).getMockImplementation()!
vi.mocked(useRoute).mockImplementation(
(...args) => ({ ...useRouteOriginal(...args), path: '/mocked' }),
)
```
#### `mockComponent`
`mockComponent` allows you to mock Nuxt's component.
The first argument can be the component name in PascalCase, or the relative path of the component.
The second argument is a factory function that returns the mocked component.
For example, to mock `MyComponent`, you can:
```ts twoslash
import { mockComponent } from '@nuxt/test-utils/runtime'
mockComponent('MyComponent', {
props: {
value: String,
},
setup (props) {
// ...
},
})
// relative path or alias also works
mockComponent('~/components/my-component.vue', () => {
// or a factory function
return defineComponent({
setup (props) {
// ...
},
})
})
// or you can use SFC for redirecting to a mock component
mockComponent('MyComponent', () => import('./MockComponent.vue'))
// your tests here
```
> **Note**: You can't reference local variables in the factory function since they are hoisted. If you need to access Vue APIs or other variables, you need to import them in your factory function.
```ts twoslash
import { mockComponent } from '@nuxt/test-utils/runtime'
mockComponent('MyComponent', async () => {
const { ref, h } = await import('vue')
return defineComponent({
setup (props) {
const counter = ref(0)
return () => h('div', null, counter.value)
},
})
})
```
#### `registerEndpoint`
`registerEndpoint` allows you create Nitro endpoint that returns mocked data. It can come in handy if you want to test a component that makes requests to API to display some data.
The first argument is the endpoint name (e.g. `/test/`).
The second argument is a factory function that returns the mocked data.
For example, to mock `/test/` endpoint, you can do:
```ts twoslash
import { registerEndpoint } from '@nuxt/test-utils/runtime'
registerEndpoint('/test/', () => ({
test: 'test-field',
}))
```
By default, your request will be made using the `GET` method. You may use another method by setting an object as the second argument instead of a function.
```ts twoslash
import { registerEndpoint } from '@nuxt/test-utils/runtime'
registerEndpoint('/test/', {
method: 'POST',
handler: () => ({ test: 'test-field' }),
})
```
This object accepts the following properties:
- `handler`: the event handler function
- `method`: (optional) HTTP method to match (e.g., 'GET', 'POST')
- `once`: (optional) if true, the handler will only be used for the first matching request and then automatically removed
> **Note**: If your requests in a component go to an external API, you can use `baseURL` and then make it empty using [Nuxt Environment Override Config](/docs/4.x/getting-started/configuration#environment-overrides) (`$test`) so all your requests will go to Nitro server.
#### Conflict with End-To-End Testing
`@nuxt/test-utils/runtime` and `@nuxt/test-utils/e2e` need to run in different testing environments and so can't be used in the same file.
If you would like to use both the end-to-end and unit testing functionality of `@nuxt/test-utils`, you can split your tests into separate files. You then either specify a test environment per-file with the special `// @vitest-environment nuxt` comment, or name your runtime unit test files with the `.nuxt.spec.ts` extension.
`app.nuxt.spec.ts`
```ts twoslash
import { mockNuxtImport } from '@nuxt/test-utils/runtime'
mockNuxtImport('useState', () => {
return () => {
return { value: 'mocked storage' }
}
})
```
`app.e2e.spec.ts`
```ts twoslash
import { $fetch, setup } from '@nuxt/test-utils/e2e'
await setup({
setupTimeout: 10000,
})
// ...
```
### Using `@vue/test-utils`
If you prefer to use `@vue/test-utils` on its own for unit testing in Nuxt, and you are only testing components which do not rely on Nuxt composables, auto-imports or context, you can follow these steps to set it up.
1. Install the needed dependencies
::code-group{sync="pm"}
```bash [npm]
npm i --save-dev vitest @vue/test-utils happy-dom @vitejs/plugin-vue
```
```bash [yarn]
yarn add --dev vitest @vue/test-utils happy-dom @vitejs/plugin-vue
```
```bash [pnpm]
pnpm add -D vitest @vue/test-utils happy-dom @vitejs/plugin-vue
```
```bash [bun]
bun add --dev vitest @vue/test-utils happy-dom @vitejs/plugin-vue
```
::
2. Create a `vitest.config.ts` with the following content:
```ts
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
test: {
environment: 'happy-dom',
},
})
```
3. Add a new command for test in your `package.json`
```json
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
...
"test": "vitest"
},
```
4. Create a simple `<HelloWorld>` component `app/components/HelloWorld.vue` with the following content:
```vue
<template>
<p>Hello world</p>
</template>
```
5. Create a simple unit test for this newly created component `~/components/HelloWorld.spec.ts`
```ts twoslash
import { describe, expect, it } from 'vitest'
import { mount } from '@vue/test-utils'
import HelloWorld from './HelloWorld.vue'
describe('HelloWorld', () => {
it('component renders Hello world properly', () => {
const wrapper = mount(HelloWorld)
expect(wrapper.text()).toContain('Hello world')
})
})
```
6. Run vitest command
::code-group{sync="pm"}
```bash [npm]
npm run test
```
```bash [yarn]
yarn test
```
```bash [pnpm]
pnpm run test
```
```bash [bun]
bun run test
```
::
Congratulations, you're all set to start unit testing with `@vue/test-utils` in Nuxt! Happy testing!
## End-To-End Testing
For end-to-end testing, we support [Vitest](https://github.com/vitest-dev/vitest), [Jest](https://jestjs.io), [Cucumber](https://cucumber.io/) and [Playwright](https://playwright.dev/) as test runners.
### Setup
In each `describe` block where you are taking advantage of the `@nuxt/test-utils/e2e` helper methods, you will need to set up the test context before beginning.
```ts twoslash [test/my-test.spec.ts]
import { describe, test } from 'vitest'
import { $fetch, setup } from '@nuxt/test-utils/e2e'
describe('My test', async () => {
await setup({
// test context options
})
test('my test', () => {
// ...
})
})
```
Behind the scenes, `setup` performs a number of tasks in `beforeAll`, `beforeEach`, `afterEach` and `afterAll` to set up the Nuxt test environment correctly.
Please use the options below for the `setup` method.
#### Nuxt Config
- `rootDir`: Path to a directory with a Nuxt app to be put under test.
- Type: `string`
- Default: `'.'`
- `configFile`: Name of the configuration file.
- Type: `string`
- Default: `'nuxt.config'`
<!--
- `config`: Object with configuration overrides.
- Type: `NuxtConfig`
- Default: `{}` -->
#### Timings
- `setupTimeout`: The amount of time (in milliseconds) to allow for `setupTest` to complete its work (which could include building or generating files for a Nuxt application, depending on the options that are passed).
- Type: `number`
- Default: `120000` or `240000` on windows
- `teardownTimeout`: The amount of time (in milliseconds) to allow tearing down the test environment, such as closing the browser.
- Type: `number`
- Default: `30000`
#### Features
- `build`: Whether to run a separate build step.
- Type: `boolean`
- Default: `true` (`false` if `browser` or `server` is disabled, or if a `host` is provided)
- `server`: Whether to launch a server to respond to requests in the test suite.
- Type: `boolean`
- Default: `true` (`false` if a `host` is provided)
- `port`: If provided, set the launched test server port to the value.
- Type: `number | undefined`
- Default: `undefined`
- `host`: If provided, a URL to use as the test target instead of building and running a new server. Useful for running "real" end-to-end tests against a deployed version of your application, or against an already running local server (which may provide a significant reduction in test execution timings). See the [target host end-to-end example below](/docs/4.x/getting-started/testing#target-host-end-to-end-example).
- Type: `string`
- Default: `undefined`
- `browser`: Under the hood, Nuxt test utils uses [`playwright`](https://playwright.dev) to carry out browser testing. If this option is set, a browser will be launched and can be controlled in the subsequent test suite.
- Type: `boolean`
- Default: `false`
- `browserOptions`
- Type: `object` with the following properties
- `type`: The type of browser to launch - either `chromium`, `firefox` or `webkit`
- `launch`: `object` of options that will be passed to playwright when launching the browser. See [full API reference](https://playwright.dev/docs/api/class-browsertype#browser-type-launch).
- `runner`: Specify the runner for the test suite. Currently, [Vitest](https://vitest.dev) is recommended.
- Type: `'vitest' | 'jest' | 'cucumber'`
- Default: `'vitest'`
##### Target `host` end-to-end example
A common use-case for end-to-end testing is running the tests against a deployed application running in the same environment typically used for Production.
For local development or automated deploy pipelines, testing against a separate local server can be more efficient and is typically faster than allowing the test framework to rebuild between tests.
To utilize a separate target host for end-to-end tests, simply provide the `host` property of the `setup` function with the desired URL.
```ts
import { createPage, setup } from '@nuxt/test-utils/e2e'
import { describe, expect, it } from 'vitest'
describe('login page', async () => {
await setup({
host: 'http://localhost:8787',
})
it('displays the email and password fields', async () => {
const page = await createPage('/login')
expect(await page.getByTestId('email').isVisible()).toBe(true)
expect(await page.getByTestId('password').isVisible()).toBe(true)
})
})
```
### APIs
#### `$fetch(url)`
Get the HTML of a server-rendered page.
```ts twoslash
import { $fetch } from '@nuxt/test-utils/e2e'
const html = await $fetch('/')
```
#### `fetch(url)`
Get the response of a server-rendered page.
```ts twoslash
import { fetch } from '@nuxt/test-utils/e2e'
const res = await fetch('/')
const { body, headers } = res
```
#### `url(path)`
Get the full URL for a given page (including the port the test server is running on.)
```ts twoslash
import { url } from '@nuxt/test-utils/e2e'
const pageUrl = url('/page')
// 'http://localhost:6840/page'
```
### Testing in a Browser
We provide built-in support using Playwright within `@nuxt/test-utils`, either programmatically or via the Playwright test runner.
#### `createPage(url)`
Within `vitest`, `jest` or `cucumber`, you can create a configured Playwright browser instance with `createPage`, and (optionally) point it at a path from the running server. You can find out more about the API methods available from [in the Playwright documentation](https://playwright.dev/docs/api/class-page).
```ts twoslash
import { createPage } from '@nuxt/test-utils/e2e'
const page = await createPage('/page')
// you can access all the Playwright APIs from the `page` variable
```
#### Testing with Playwright Test Runner
We also provide first-class support for testing Nuxt within [the Playwright test runner](https://playwright.dev/docs/intro).
::code-group{sync="pm"}
```bash [npm]
npm i --save-dev @playwright/test @nuxt/test-utils
```
```bash [yarn]
yarn add --dev @playwright/test @nuxt/test-utils
```
```bash [pnpm]
pnpm add -D @playwright/test @nuxt/test-utils
```
```bash [bun]
bun add --dev @playwright/test @nuxt/test-utils
```
```bash [deno]
deno add --dev npm:@playwright/test npm:@nuxt/test-utils
```
::
You can provide global Nuxt configuration, with the same configuration details as the `setup()` function mentioned earlier in this section.
```ts [playwright.config.ts]
import { fileURLToPath } from 'node:url'
import { defineConfig, devices } from '@playwright/test'
import type { ConfigOptions } from '@nuxt/test-utils/playwright'
export default defineConfig<ConfigOptions>({
use: {
nuxt: {
rootDir: fileURLToPath(new URL('.', import.meta.url)),
},
},
// ...
})
```
::read-more{title="See full example config" to="https://github.com/nuxt/test-utils/blob/main/examples/app-playwright/playwright.config.ts" target="_blank"}
::
Your test file should then use `expect` and `test` directly from `@nuxt/test-utils/playwright`:
```ts [tests/example.test.ts]
import { expect, test } from '@nuxt/test-utils/playwright'
test('test', async ({ page, goto }) => {
await goto('/', { waitUntil: 'hydration' })
await expect(page.getByRole('heading')).toHaveText('Welcome to Playwright!')
})
```
You can alternatively configure your Nuxt server directly within your test file:
```ts [tests/example.test.ts]
import { expect, test } from '@nuxt/test-utils/playwright'
test.use({
nuxt: {
rootDir: fileURLToPath(new URL('..', import.meta.url)),
},
})
test('test', async ({ page, goto }) => {
await goto('/', { waitUntil: 'hydration' })
await expect(page.getByRole('heading')).toHaveText('Welcome to Playwright!')
})
```

View File

@@ -0,0 +1,526 @@
---
title: Upgrade Guide
description: 'Learn how to upgrade to the latest Nuxt version.'
navigation.icon: i-lucide-circle-arrow-up
---
## Upgrading Nuxt
### Latest release
To upgrade Nuxt to the [latest release](https://github.com/nuxt/nuxt/releases), use the `nuxt upgrade` command.
::code-group{sync="pm"}
```bash [npm]
npx nuxt upgrade
```
```bash [yarn]
yarn nuxt upgrade
```
```bash [pnpm]
pnpm nuxt upgrade
```
```bash [bun]
bun x nuxt upgrade
```
```bash [deno]
deno x nuxt upgrade
```
::
### Nightly Release Channel
To use the latest Nuxt build and test features before their release, read about the [nightly release channel](/docs/4.x/guide/going-further/nightly-release-channel) guide.
## Testing Nuxt 5
Nuxt 5 is **currently in development**. Until the release, it is possible to test many of Nuxt 5's breaking changes from Nuxt version 4.2+.
### Opting in to Nuxt 5
First, upgrade Nuxt to the [latest release](https://github.com/nuxt/nuxt/releases).
Then you can set your `future.compatibilityVersion` to match Nuxt 5 behavior:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
future: {
compatibilityVersion: 5,
},
})
```
When you set your `future.compatibilityVersion` to `5`, defaults throughout your Nuxt configuration will change to opt in to Nuxt v5 behavior, including:
- **Vite Environment API**: Uses the new [Vite Environment API](/docs/4.x/getting-started/upgrade#migration-to-vite-environment-api) for improved build configuration
- **Normalized Page Names**: Page component names will [match their route names](/docs/4.x/getting-started/upgrade#normalized-page-component-names) for consistent `<KeepAlive>` behavior
- **`clearNuxtState` resets to defaults**: `clearNuxtState` will [reset state to its initial value](/docs/4.x/getting-started/upgrade#respect-defaults-when-clearing-usestate) instead of setting it to `undefined`
- **Non-async `callHook`**: [`callHook` may return `void`](/docs/4.x/getting-started/upgrade#non-async-callhook) instead of always returning a `Promise`
- **Comment node placeholders**: Client-only components use [comment nodes instead of `<div>`](/docs/4.x/getting-started/upgrade#client-only-comment-placeholders) as SSR placeholders, fixing a scoped styles hydration issue
- Other Nuxt 5 improvements and changes as they become available
::note
This section is subject to change until the final release, so please check back here regularly if you are testing Nuxt 5 using `future.compatibilityVersion: 5`.
::
Breaking or significant changes will be noted below along with migration steps for backward/forward compatibility.
### Migration to Vite Environment API
🚦 **Impact Level**: Medium
#### What Changed
Nuxt 5 migrates to Vite 6's new [Environment API](https://vite.dev/guide/api-environment), which formalizes the concept of environments and provides better control over configuration per environment.
Previously, Nuxt used separate client and server Vite configurations. Now, Nuxt uses a shared Vite configuration with environment-specific plugins that use the `applyToEnvironment()` method to target specific environments.
::note
The Vite Environment API is always enabled in Nuxt 5. The `experimental.viteEnvironmentApi` option has been removed.
::
**Key changes:**
1. **Deprecated environment-specific `extendViteConfig()`**: The `server` and `client` options in `extendViteConfig()` are deprecated and will show warnings when used.
2. **Changed plugin registration**: Vite plugins registered with `addVitePlugin()` and only targeting one environment (by passing `server: false` or `client: false`) will not have their `config` or `configResolved` hooks called.
3. **Shared configuration**: The `vite:extendConfig` and `vite:configResolved` hooks now work with a shared configuration rather than separate client/server configs.
#### Reasons for Change
The Vite Environment API provides:
- Better consistency between development and production builds
- More granular control over environment-specific configuration
- Improved performance and plugin architecture
- Support for custom environments beyond just client and server
#### Migration Steps
##### 1. Migrate to use Vite plugins
We would recommend you use a Vite plugin instead of `extendViteConfig`, `vite:configResolved` and `vite:extendConfig`.
```ts
// Before
extendViteConfig((config) => {
config.optimizeDeps.include.push('my-package')
}, { server: false })
nuxt.hook('vite:extendConfig' /* or vite:configResolved */, (config, { isClient }) => {
if (isClient) {
config.optimizeDeps.include.push('my-package')
}
})
// After
addVitePlugin(() => ({
name: 'my-plugin',
config (config) {
// you can set global vite configuration here
},
configResolved (config) {
// you can access the fully resolved vite configuration here
},
configEnvironment (name, config) {
// you can set environment-specific vite configuration here
if (name === 'client') {
config.optimizeDeps ||= {}
config.optimizeDeps.include ||= []
config.optimizeDeps.include.push('my-package')
}
},
applyToEnvironment (environment) {
return environment.name === 'client'
},
}))
```
##### 2. Migrate Vite plugins to use environments
Instead of using `addVitePlugin` with `server: false` or `client: false`, you can instead use the new `applyToEnvironment` hook within your plugin.
```ts
// Before
addVitePlugin(() => ({
name: 'my-plugin',
config (config) {
config.optimizeDeps.include.push('my-package')
},
}), { client: false })
// After
addVitePlugin(() => ({
name: 'my-plugin',
config (config) {
// you can set global vite configuration here
},
configResolved (config) {
// you can access the fully resolved vite configuration here
},
configEnvironment (name, config) {
// you can set environment-specific vite configuration here
if (name === 'client') {
config.optimizeDeps ||= {}
config.optimizeDeps.include ||= []
config.optimizeDeps.include.push('my-package')
}
},
applyToEnvironment (environment) {
return environment.name === 'client'
},
}))
```
::read-more{to="https://vite.dev/guide/api-environment" target="_blank"}
Learn more about Vite's Environment API
::
### Migration to Vite 8
🚦 **Impact Level**: Medium
#### What Changed
Nuxt 5 upgrades from Vite 7 to [Vite 8](https://main.vite.dev/guide/migration), which replaces esbuild and Rollup with [Rolldown](https://rolldown.rs) as the underlying bundler. This brings significantly faster builds but includes several breaking changes.
::note
Unlike the Vite Environment API migration, this change cannot be opted into early with `future.compatibilityVersion: 5`. If you want to test Vite 8 compatibility ahead of time, you can add a `"vite": "^8.0.0-beta.15"` resolution override in your `package.json`.
::
Most of the migration is handled by Nuxt internally, but there are some user-facing changes to be aware of:
- **`vite.esbuild` and `vite.optimizeDeps.esbuildOptions`** are deprecated in favour of `vite.oxc` and `vite.optimizeDeps.rolldownOptions`. Vite 8 converts these automatically for now, but they will be removed in the future.
- **`build.rollupOptions`** is deprecated in favour of `build.rolldownOptions`.
- **CommonJS interop behaviour** has changed. If you import CJS modules, review the [Vite 8 migration guide](https://main.vite.dev/guide/migration#consistent-commonjs-interop) for details.
::read-more{to="https://main.vite.dev/guide/migration" target="_blank"}
See the full Vite 8 migration guide for all breaking changes and migration steps.
::
### Migration to Nitro v3
🚦 **Impact Level**: Significant
#### What Changed
Nuxt 5 upgrades to [Nitro v3](https://nitro.build/blog/v3-beta), which is a major rewrite of the server engine. Nitro v3 is built on [srvx](https://srvx.h3.dev) and [h3 v2](https://h3.dev), adopting Web standard `Request`/`Response` APIs throughout. This brings performance improvements and a more consistent API, but includes several breaking changes to server-side code.
::important
We are still working on Nitro v3 integration so you should expect further changes, as well as additional work to make migration more straightforward.
::
::read-more{to="https://nitro.build/blog/v3-beta" target="_blank"}
Read the Nitro v3 beta announcement for a full overview.
::
::read-more{to="https://nitro.build/docs/migration" target="_blank"}
See the full Nitro v3 migration guide for all breaking changes.
::
The sections below highlight changes that are most relevant to Nuxt application developers and module authors.
#### Package and Import Path Changes
The `nitropack` package has been renamed to `nitro`. All import paths have changed:
| Before | After |
|---|---|
| `nitropack` | `nitro` |
| `nitropack/types` | `nitro/types` |
| `nitropack/runtime` | `nitro` |
| `h3` (for server utilities) | `nitro/h3` |
Auto-imports within server routes (`defineEventHandler`, `getQuery`, `readBody`, `useRuntimeConfig`, etc.) continue to work without changes.
If you have explicit imports in server code, update them:
```diff
- import { defineEventHandler, getQuery } from 'h3'
+ import { defineEventHandler, getQuery } from 'nitro/h3'
```
**For module authors**, type augmentations must target the new module path:
```diff
- declare module 'nitropack/types' {
+ declare module 'nitro/types' {
interface NitroRouteRules {
myModule?: { /* ... */ }
}
}
```
#### Error Handling: `status`/`statusText` replace `statusCode`/`statusMessage`
h3 v2 renames the error properties to align with Web standards:
```diff
createError({
- statusCode: 404,
- statusMessage: 'Not Found',
+ status: 404,
+ statusText: 'Not Found',
})
```
In server routes, the error class is now `HTTPError` (replacing `createError` from `h3`):
```diff
- import { createError } from 'h3'
+ import { HTTPError } from 'nitro/h3'
export default defineEventHandler(() => {
- throw createError({ statusCode: 400, statusMessage: 'Bad request' })
+ throw new HTTPError({ status: 400, statusText: 'Bad request' })
})
```
::note
In the Vue part of your app (the `app/` directory), Nuxt's `createError` composable continues to work and is the recommended way to throw errors.
::
#### Server Event API Changes (h3 v2)
The `H3Event` object now uses Web standard APIs:
**Request properties:**
```diff
- event.path // string
+ event.url.pathname // URL object - use .pathname, .search, .hash
- event.method // string
+ event.req.method // via Web Request object
- event.node.req.headers // Node.js IncomingHttpHeaders
+ event.req.headers // Web Headers API (.get(), .set(), .has())
```
**Response properties:**
```diff
- event.node.res.statusCode = 200
+ event.res.status = 200
- event.node.res.statusMessage = 'OK'
+ event.res.statusText = 'OK'
- setResponseHeader(event, 'x-custom', 'value')
+ event.res.headers.set('x-custom', 'value')
- appendResponseHeader(event, 'set-cookie', cookie)
+ event.res.headers.append('set-cookie', cookie)
```
#### `useRuntimeConfig()` No Longer Accepts `event`
In Nitro v3, `useRuntimeConfig()` no longer requires (or accepts) an `event` argument in server routes:
```diff
export default defineEventHandler((event) => {
- const config = useRuntimeConfig(event)
+ const config = useRuntimeConfig()
})
```
#### Route Rules: `statusCode` Renamed to `status`
If you define redirect route rules, the property name has changed:
```diff
export default defineNuxtConfig({
routeRules: {
'/old-page': {
- redirect: { to: '/new-page', statusCode: 302 },
+ redirect: { to: '/new-page', status: 302 },
},
},
})
```
#### For Module Authors: Additional Changes
- **Nitro plugin imports**: Use `import { definePlugin } from 'nitro'` for explicit imports (auto-imports still work).
- **Runtime hooks**: `nitroApp.hooks.hook('beforeResponse', ...)` and `nitroApp.hooks.hook('afterResponse', ...)` have been replaced by `nitroApp.hooks.hook('response', ...)`.
- **`getRouteRules()` from `nitro/app`**: On the server, the Nitro helper changed from `getRouteRules(event)` to `getRouteRules(method, pathname)`, which returns `{ routeRules }`.
### Removal of `experimental.externalVue`
🚦 **Impact Level**: Minimal
#### What Changed
The `experimental.externalVue` option has been removed. Vue compiler dependencies (`@babel/parser`, `@vue/compiler-core`, `@vue/compiler-dom`, `@vue/compiler-ssr`, `estree-walker`) are now always replaced with mock proxies in the server bundle when `vue.runtimeCompiler` is not enabled.
#### Reasons for Change
With the migration to Nitro v3, all dependencies are bundled into the server output by default (unlike Nitro v2, which externalized `node_modules`). The `externalVue` option was originally designed to keep Vue as an external dependency, which was needed to avoid multiple copies of Vue from being bundled, but since Nitro v3 bundles everything regardless, the option became a no-op.
Vue's server builds include the full compiler toolchain, pulling `@babel/parser` (465KB) and other compiler packages into the server bundle unnecessarily. These compiler packages are only needed when `vue.runtimeCompiler` is enabled for runtime template compilation.
By always mocking these compiler dependencies, the default server bundle size is reduced by approximately 860KB (~59%).
#### Migration Steps
If you previously set `experimental.externalVue` explicitly, you should now remove it.
```diff
export default defineNuxtConfig({
experimental: {
- externalVue: false,
},
})
```
::note
If you use `vue.runtimeCompiler: true`, the real compiler packages are still included as before.
::
### `@vitejs/plugin-vue-jsx` Is Now Optional
🚦 **Impact Level**: Minimal
#### What Changed
`@vitejs/plugin-vue-jsx` is no longer installed by default with `@nuxt/vite-builder`. It is now an optional peer dependency that is loaded on demand only when a `.jsx` or `.tsx` file is encountered during the build.
If your project uses JSX/TSX components, Nuxt will automatically detect this and prompt you to install the package.
#### Reasons for Change
The `@vitejs/plugin-vue-jsx` plugin pulls in a significant dependency tree (Babel, `@vue/babel-plugin-jsx`, etc.) that is unnecessary for projects that don't use JSX. Making it optional reduces the default install size and speeds up dependency resolution for the majority of Nuxt projects.
#### Migration Steps
If your project uses `.jsx` or `.tsx` files, add `@vitejs/plugin-vue-jsx` as a dev dependency:
::code-group{sync="pm"}
```bash [npm]
npm install -D @vitejs/plugin-vue-jsx
```
```bash [yarn]
yarn add -D @vitejs/plugin-vue-jsx
```
```bash [pnpm]
pnpm add -D @vitejs/plugin-vue-jsx
```
```bash [bun]
bun add -D @vitejs/plugin-vue-jsx
```
::
Alternatively, Nuxt will prompt you to install it automatically the first time a JSX/TSX file is processed during development.
If your project does not use JSX, no changes are needed.
### Removal of Legacy `_renderResponse` Support
🚦 **Impact Level**: Minimal
#### What Changed
`ssrContext._renderResponse` is no longer checked as a fallback. Only the internal `ssrContext['~renderResponse']` (set by Nuxt's own router composable) is used.
#### Reasons for Change
The `_renderResponse` property on `ssrContext` was kept as a backward-compatibility fallback after [#33896](https://github.com/nuxt/nuxt/pull/33896) migrated the internal API to `~renderResponse`. The TODO comments indicated it should be removed in Nuxt v5.
#### Migration Steps
If you were setting `ssrContext._renderResponse` directly (which was never a public API), use `ssrContext['~renderResponse']` instead. The Nuxt router composable already uses the new property, so no changes are needed if you're going through `navigateTo` or route middleware.
### Non-Async `callHook`
🚦 **Impact Level**: Minimal
#### What Changed
With the upgrade to [hookable v6](https://github.com/unjs/hookable), `callHook` may now return `void` instead of always returning `Promise<void>`. This is a significant performance improvement that avoids unnecessary `Promise` allocations when there are no registered hooks or all hooks are synchronous.
By default (with `compatibilityVersion: 4`), Nuxt wraps `callHook` with `Promise.resolve()` so that existing `.then()` and `.catch()` chaining continues to work. With `compatibilityVersion: 5`, this wrapper is removed.
::tip
This affects both build-time Nuxt hooks (used by Nuxt modules) and runtime Nuxt hooks (which you might use in your application code).
::
#### Reasons for Change
Hookable v6's `callHook` is 20-40x faster because it avoids creating a `Promise` when one is not needed. This benefits applications with many hook call sites.
#### Migration Steps
If you or your modules use `callHook` with `.then()` or `.catch()` chaining, switch to `await`:
```diff
- nuxtApp.callHook('my:hook', data).then(() => { ... })
+ await nuxtApp.callHook('my:hook', data)
```
```diff
- nuxtApp.hooks.callHook('my:hook', data).catch(err => { ... })
+ try { await nuxtApp.hooks.callHook('my:hook', data) } catch (err) { ... }
```
::tip
You can test this feature early by setting `future.compatibilityVersion: 5` (see [Testing Nuxt 5](/docs/4.x/getting-started/upgrade#testing-nuxt-5)) or by enabling it explicitly with `experimental.asyncCallHook: false`.
::
Alternatively, you can ensure `callHook` always returns a `Promise` with:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
asyncCallHook: true,
},
})
```
### Client-Only Comment Placeholders
🚦 **Impact Level**: Minimal
#### What Changed
With `compatibilityVersion: 5`, client-only components (`.client.vue` files and `createClientOnly()` wrappers) now render an HTML comment (`<!--placeholder-->`) on the server instead of an empty `<div>` element.
#### Reasons for Change
When the placeholder `<div>` and the actual component root share the same tag name, Vue's runtime skips re-applying `setScopeId` during hydration. This causes scoped styles to be missing after the component mounts. Using a comment node avoids the tag name collision entirely.
#### Migration Steps
If you rely on the placeholder `<div>` to inherit attributes (`class`, `style`, etc.) for layout purposes (e.g., reserving space to prevent layout shift), wrap the component in `<ClientOnly>` with a `#fallback` slot instead:
```diff
- <MyComponent class="placeholder" style="min-height: 200px" />
+ <ClientOnly>
+ <MyComponent />
+ <template #fallback>
+ <div class="placeholder" style="min-height: 200px"></div>
+ </template>
+ </ClientOnly>
```
::tip
You can test this feature early by setting `future.compatibilityVersion: 5` (see [Testing Nuxt 5](/docs/4.x/getting-started/upgrade#testing-nuxt-5)) or by enabling it explicitly with `experimental.clientNodePlaceholder: true`.
::
Alternatively, you can revert to the previous `<div>` placeholder behavior with:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
clientNodePlaceholder: false,
},
})
```

View File

@@ -0,0 +1,3 @@
title: Directory Structure
titleTemplate: '%s · Nuxt Directory Structure'
icon: i-vscode-icons-default-folder

View File

@@ -0,0 +1,20 @@
---
title: ".nuxt"
description: "Nuxt uses the .nuxt/ directory in development to generate your Vue application."
head.title: ".nuxt/"
navigation.icon: i-vscode-icons-folder-type-temp
---
::important
This directory should be added to your [`.gitignore`](/docs/4.x/directory-structure/gitignore) file to avoid pushing the dev build output to your repository.
::
This directory is interesting if you want to learn more about the files Nuxt generates based on your directory structure.
Nuxt also provides a Virtual File System (VFS) for modules to add templates to this directory without writing them to disk.
You can explore the generated files by opening the [Nuxt DevTools](https://devtools.nuxt.com) in development mode and navigating to the **Virtual Files** tab.
::warning
You should not touch any files inside since the whole directory will be re-created when running [`nuxt dev`](/docs/4.x/api/commands/dev).
::

View File

@@ -0,0 +1,18 @@
---
title: ".output"
description: "Nuxt creates the .output/ directory when building your application for production."
head.title: ".output/"
navigation.icon: i-vscode-icons-folder-type-package
---
::important
This directory should be added to your [`.gitignore`](/docs/4.x/directory-structure/gitignore) file to avoid pushing the build output to your repository.
::
Use this directory to deploy your Nuxt application to production.
:read-more{to="/docs/4.x/getting-started/deployment"}
::warning
You should not touch any files inside since the whole directory will be re-created when running [`nuxt build`](/docs/4.x/api/commands/build).
::

View File

@@ -0,0 +1,5 @@
title: app
titleTemplate: '%s · Nuxt Directory Structure'
head.title: "app/"
defaultOpen: true
icon: i-vscode-icons-folder-type-app

View File

@@ -0,0 +1,16 @@
---
title: "assets"
description: "The assets/ directory is used to add all the website's assets that the build tool will process."
head.title: "assets/"
navigation.icon: i-vscode-icons-folder-type-asset
---
The directory usually contains the following types of files:
- Stylesheets (CSS, SASS, etc.)
- Fonts
- Images that won't be served from the [`public/`](/docs/4.x/directory-structure/public) directory.
If you want to serve assets from the server, we recommend taking a look at the [`public/`](/docs/4.x/directory-structure/public) directory.
:read-more{to="/docs/4.x/getting-started/assets"}

View File

@@ -0,0 +1,639 @@
---
title: "components"
head.title: "components/"
description: "The components/ directory is where you put all your Vue components."
navigation.icon: i-vscode-icons-folder-type-component
---
Nuxt automatically imports any components in this directory (along with components that are registered by any modules you may be using).
```bash [Directory Structure]
-| components/
---| AppHeader.vue
---| AppFooter.vue
```
```html [app/app.vue]
<template>
<div>
<AppHeader />
<NuxtPage />
<AppFooter />
</div>
</template>
```
## Component Names
If you have a component in nested directories such as:
```bash [Directory Structure]
-| components/
---| base/
-----| foo/
-------| Button.vue
```
... then the component's name will be based on its own path directory and filename, with duplicate segments being removed. Therefore, the component's name will be:
```html
<BaseFooButton />
```
::note
For clarity, we recommend that the component's filename matches its name. So, in the example above, you could rename `Button.vue` to be `BaseFooButton.vue`.
::
If you want to auto-import components based only on its name, not path, then you need to set `pathPrefix` option to `false` using extended form of the configuration object:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
components: [
{
path: '~/components',
pathPrefix: false, // [!code ++]
},
],
})
```
This registers the components using the same strategy as used in Nuxt 2. For example, `~/components/Some/MyComponent.vue` will be usable as `<MyComponent>` and not `<SomeMyComponent>`.
## Dynamic Components
If you want to use the Vue `<component :is="someComputedComponent">`{lang=vue} syntax, you need to use the `resolveComponent` helper provided by Vue or import the component directly from `#components` and pass it into `is` prop.
For example:
```vue [app/pages/index.vue]
<script setup lang="ts">
import { SomeComponent } from '#components'
const MyButton = resolveComponent('MyButton')
</script>
<template>
<component :is="clickable ? MyButton : 'div'" />
<component :is="SomeComponent" />
</template>
```
::important
If you are using `resolveComponent` to handle dynamic components, make sure not to insert anything but the name of the component, which must be a literal string and not be or contain a variable. The string is statically analyzed at the compilation step.
::
:video-accordion{title="Watch Daniel Roe's short video about resolveComponent()" videoId="4kq8E5IUM2U"}
Alternatively, though not recommended, you can register all your components globally, which will create async chunks for all your components and make them available throughout your application.
```diff
export default defineNuxtConfig({
components: {
+ global: true,
+ dirs: ['~/components']
},
})
```
You can also selectively register some components globally by placing them in a `~/components/global` directory, or by using a `.global.vue` suffix in the filename. As noted above, each global component is rendered in a separate chunk, so be careful not to overuse this feature.
::note
The `global` option can also be set per component directory.
::
## Dynamic Imports
To dynamically import a component (also known as lazy-loading a component) all you need to do is add the `Lazy` prefix to the component's name. This is particularly useful if the component is not always needed.
By using the `Lazy` prefix you can delay loading the component code until the right moment, which can be helpful for optimizing your JavaScript bundle size.
```vue [app/pages/index.vue]
<script setup lang="ts">
const show = ref(false)
</script>
<template>
<div>
<h1>Mountains</h1>
<LazyMountainsList v-if="show" />
<button
v-if="!show"
@click="show = true"
>
Show List
</button>
</div>
</template>
```
## Delayed (or Lazy) Hydration
Lazy components are great for controlling the chunk sizes in your app, but they don't always enhance runtime performance, as they still load eagerly unless conditionally rendered. In real-world applications, some pages may include a lot of content and a lot of components, and most of the time not all of them need to be interactive as soon as the page is loaded. Having them all load eagerly can negatively impact performance.
In order to optimize your app, you may want to delay the hydration of some components until they're visible, or until the browser is done with more important tasks.
Nuxt supports this using lazy (or delayed) hydration, allowing you to control when components become interactive.
### Hydration Strategies
Nuxt provides a range of built-in hydration strategies. Only one strategy can be used per lazy component.
::note
Any prop change on a lazily hydrated component will trigger hydration immediately. (e.g., changing a prop on a component with `hydrate-never` will cause it to hydrate)
::
::warning
Currently Nuxt's built-in lazy hydration only works in single-file components (SFCs), and requires you to define the prop in the template (rather than spreading an object of props via `v-bind`). It also does not work with direct imports from `#components`.
::
#### `hydrate-on-visible`
Hydrates the component when it becomes visible in the viewport.
```vue [app/pages/index.vue]
<template>
<div>
<LazyMyComponent hydrate-on-visible />
</div>
</template>
```
::read-more{to="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/IntersectionObserver" title="IntersectionObserver options"}
Read more about the options for `hydrate-on-visible`.
::
::note
Under the hood, this uses Vue's built-in [`hydrateOnVisible` strategy](https://vuejs.org/guide/components/async#hydrate-on-visible).
::
#### `hydrate-on-idle`
Hydrates the component when the browser is idle. This is suitable if you need the component to load as soon as possible, but not block the critical rendering path.
You can also pass a number which serves as a max timeout.
```vue [app/pages/index.vue]
<template>
<div>
<LazyMyComponent hydrate-on-idle />
</div>
</template>
```
::note
Under the hood, this uses Vue's built-in [`hydrateOnIdle` strategy](https://vuejs.org/guide/components/async#hydrate-on-idle).
::
#### `hydrate-on-interaction`
Hydrates the component after a specified interaction (e.g., click, mouseover).
```vue [app/pages/index.vue]
<template>
<div>
<LazyMyComponent hydrate-on-interaction="mouseover" />
</div>
</template>
```
If you do not pass an event or list of events, it defaults to hydrating on `pointerenter`, `click` and `focus`.
::note
Under the hood, this uses Vue's built-in [`hydrateOnInteraction` strategy](https://vuejs.org/guide/components/async#hydrate-on-interaction).
::
#### `hydrate-on-media-query`
Hydrates the component when the window matches a media query.
```vue [app/pages/index.vue]
<template>
<div>
<LazyMyComponent hydrate-on-media-query="(max-width: 768px)" />
</div>
</template>
```
::note
Under the hood, this uses Vue's built-in [`hydrateOnMediaQuery` strategy](https://vuejs.org/guide/components/async#hydrate-on-media-query).
::
#### `hydrate-after`
Hydrates the component after a specified delay (in milliseconds).
```vue [app/pages/index.vue]
<template>
<div>
<LazyMyComponent :hydrate-after="2000" />
</div>
</template>
```
#### `hydrate-when`
Hydrates the component based on a boolean condition.
```vue [app/pages/index.vue]
<template>
<div>
<LazyMyComponent :hydrate-when="isReady" />
</div>
</template>
<script setup lang="ts">
const isReady = ref(false)
function myFunction () {
// trigger custom hydration strategy...
isReady.value = true
}
</script>
```
#### `hydrate-never`
Never hydrates the component.
```vue [app/pages/index.vue]
<template>
<div>
<LazyMyComponent hydrate-never />
</div>
</template>
```
### Listening to Hydration Events
All delayed hydration components emit a `@hydrated` event when they are hydrated.
```vue [app/pages/index.vue]
<template>
<div>
<LazyMyComponent
hydrate-on-visible
@hydrated="onHydrate"
/>
</div>
</template>
<script setup lang="ts">
function onHydrate () {
console.log('Component has been hydrated!')
}
</script>
```
### Caveats and Best Practices
Delayed hydration can offer performance benefits, but it's essential to use it correctly:
1. **Prioritize In-Viewport Content:** Avoid delayed hydration for critical, above-the-fold content. It's best suited for content that isn't immediately needed.
2. **Conditional Rendering:** When using `v-if="false"` on a lazy component, you might not need delayed hydration. You can just use a normal lazy component.
3. **Shared State:** Be mindful of shared state (`v-model`) across multiple components. Updating the model in one component can trigger hydration in all components bound to that model.
4. **Use Each Strategy's Intended Use Case:** Each strategy is optimized for a specific purpose.
* `hydrate-when` is best for components that might not always need to be hydrated.
* `hydrate-after` is for components that can wait a specific amount of time.
* `hydrate-on-idle` is for components that can be hydrated when the browser is idle.
5. **Avoid `hydrate-never` on interactive components:** If a component requires user interaction, it should not be set to never hydrate.
## Direct Imports
You can also explicitly import components from `#components` if you want or need to bypass Nuxt's auto-importing functionality.
```vue [app/pages/index.vue]
<script setup lang="ts">
import { LazyMountainsList, NuxtLink } from '#components'
const show = ref(false)
</script>
<template>
<div>
<h1>Mountains</h1>
<LazyMountainsList v-if="show" />
<button
v-if="!show"
@click="show = true"
>
Show List
</button>
<NuxtLink to="/">Home</NuxtLink>
</div>
</template>
```
## Custom Directories
By default, only the `~/components` directory is scanned. If you want to add other directories, or change how the components are scanned within a subfolder of this directory, you can add additional directories to the configuration:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
components: [
// ~/calendar-module/components/event/Update.vue => <EventUpdate />
{ path: '~/calendar-module/components' },
// ~/user-module/components/account/UserDeleteDialog.vue => <UserDeleteDialog />
{ path: '~/user-module/components', pathPrefix: false },
// ~/components/special-components/Btn.vue => <SpecialBtn />
{ path: '~/components/special-components', prefix: 'Special' },
// It's important that this comes last if you have overrides you wish to apply
// to sub-directories of `~/components`.
//
// ~/components/Btn.vue => <Btn />
// ~/components/base/Btn.vue => <BaseBtn />
'~/components',
],
})
```
::note
Any nested directories need to be added first as they are scanned in order.
::
## npm Packages
If you want to auto-import components from an npm package, you can use [`addComponent`](/docs/4.x/api/kit/components#addcomponent) in a [local module](/docs/4.x/directory-structure/modules) to register them.
::code-group
```ts twoslash [~/modules/register-component.ts]
import { addComponent, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup () {
// import { MyComponent as MyAutoImportedComponent } from 'my-npm-package'
addComponent({
name: 'MyAutoImportedComponent',
export: 'MyComponent',
filePath: 'my-npm-package',
})
},
})
```
```vue [app/app.vue]
<template>
<div>
<!-- the component uses the name we specified and is auto-imported -->
<MyAutoImportedComponent />
</div>
</template>
```
::
## Component Extensions
By default, any file with an extension specified in the [extensions key of `nuxt.config.ts`](/docs/4.x/api/nuxt-config#extensions) is treated as a component.
If you need to restrict the file extensions that should be registered as components, you can use the extended form of the components directory declaration and its `extensions` key:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
components: [
{
path: '~/components',
extensions: ['.vue'], // [!code ++]
},
],
})
```
## Client Components
If a component is meant to be rendered only client-side, you can add the `.client` suffix to your component.
```bash [Directory Structure]
| components/
--| Comments.client.vue
```
```vue [app/pages/example.vue]
<template>
<div>
<!-- this component will only be rendered on client side -->
<Comments />
</div>
</template>
```
::note
This feature only works with Nuxt auto-imports and `#components` imports. Explicitly importing these components from their real paths does not convert them into client-only components.
::
::important
`.client` components are rendered only after being mounted. To access the rendered template using `onMounted()`, add `await nextTick()` in the callback of the `onMounted()` hook.
::
::read-more{to="/docs/4.x/api/components/client-only"}
You can also achieve a similar result with the `<ClientOnly>` component.
::
## Server Components
Server components allow server-rendering individual components within your client-side apps. It's possible to use server components within Nuxt, even if you are generating a static site. That makes it possible to build complex sites that mix dynamic components, server-rendered HTML and even static chunks of markup.
Server components can either be used on their own or paired with a [client component](/docs/4.x/directory-structure/app/components#paired-with-a-client-component).
:video-accordion{title="Watch Learn Vue video about Nuxt Server Components" videoId="u1yyXe86xJM"}
::tip{icon="i-lucide-newspaper" to="https://roe.dev/blog/nuxt-server-components" target="_blank"}
Read Daniel Roe's guide to Nuxt Server Components.
::
### Standalone server components
Standalone server components will always be rendered on the server, also known as Islands components.
When their props update, this will result in a network request that will update the rendered HTML in-place.
Server components are currently experimental and in order to use them, you need to enable the 'component islands' feature in your nuxt.config:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
componentIslands: true,
},
})
```
Now you can register server-only components with the `.server` suffix and use them anywhere in your application automatically.
```bash [Directory Structure]
-| components/
---| HighlightedMarkdown.server.vue
```
```vue [app/pages/example.vue]
<template>
<div>
<!--
this will automatically be rendered on the server, meaning your markdown parsing + highlighting
libraries are not included in your client bundle.
-->
<HighlightedMarkdown markdown="# Headline" />
</div>
</template>
```
Server-only components use [`<NuxtIsland>`](/docs/4.x/api/components/nuxt-island) under the hood, meaning that `lazy` prop and `#fallback` slot are both passed down to it.
::warning
Server components (and islands) must have a single root element. (HTML comments are considered elements as well.)
::
::warning
Props are passed to server components via URL query parameters, and are therefore limited by the possible length of a URL, so be careful not to pass enormous amounts of data to server components via props.
::
::warning
Be careful when nesting islands within other islands as each island adds some extra overhead.
::
::warning
Most features for server-only components and island components, such as slots and client components, are only available for single file components.
::
#### Client components within server components
::note
This feature needs `experimental.componentIslands.selectiveClient` within your configuration to be true.
::
You can partially hydrate a component by setting a `nuxt-client` attribute on the component you wish to be loaded client-side.
```vue [app/components/ServerWithClient.vue]
<template>
<div>
<HighlightedMarkdown markdown="# Headline" />
<!-- Counter will be loaded and hydrated client-side -->
<Counter
nuxt-client
:count="5"
/>
</div>
</template>
```
::note
This only works within a server component. Slots for client components are working only with `experimental.componentIsland.selectiveClient` set to `'deep'` and since they are rendered server-side, they are not interactive once client-side.
::
#### Server Component Context
When rendering a server-only or island component, `<NuxtIsland>` makes a fetch request which comes back with a `NuxtIslandResponse`. (This is an internal request if rendered on the server, or a request that you can see in the network tab if it's rendering on client-side navigation.)
This means:
* A new Vue app will be created server-side to create the `NuxtIslandResponse`.
* A new 'island context' will be created while rendering the component.
* You can't access the 'island context' from the rest of your app and you can't access the context of the rest of your app from the island component. In other words, the server component or island is _isolated_ from the rest of your app.
* Your plugins will run again when rendering the island, unless they have `env: { islands: false }` set (which you can do in an object-syntax plugin).
::important
Route middleware does not run when rendering island components. Middleware is a routing concept that applies to pages, not components, and is not designed to control component rendering.
::
::important
`useRoute()` and other `vue-router` composables do not track the current page route inside a server (island) component. The island is rendered in its own isolated Vue app keyed only on its props (and any explicit context), which is what keeps islands cacheable independently of the page they are rendered on. Inside an island, `useRoute()` will reflect the island's own request, not the page the user is on.
If an island needs information about the current route, pass it in explicitly &mdash; either as props from the parent component, or via the `context` prop on `<NuxtIsland>` (read inside the island from `nuxtApp.ssrContext.islandContext`).
::
Within an island component, you can access its island context through `nuxtApp.ssrContext.islandContext`. Note that while island components are still marked as experimental, the format of this context may change.
::note
Slots can be interactive and are wrapped within a `<div>` with `display: contents;`
::
### Paired with a Client component
In this case, the `.server` + `.client` components are two 'halves' of a component and can be used in advanced use cases for separate implementations of a component on server and client side.
```bash [Directory Structure]
-| components/
---| Comments.client.vue
---| Comments.server.vue
```
```vue [app/pages/example.vue]
<template>
<div>
<!-- this component will render Comments.server on the server then Comments.client once mounted in the browser -->
<Comments />
</div>
</template>
```
## Built-In Nuxt Components
There are a number of components that Nuxt provides, including `<ClientOnly>` and `<DevOnly>`. You can read more about them in the API documentation.
::read-more{to="/docs/4.x/api"}
::
## Library Authors
Making Vue component libraries with automatic tree-shaking and component registration is super easy. ✨
You can use the [`addComponentsDir`](/docs/4.x/api/kit/components#addcomponentsdir) method provided from the `@nuxt/kit` to register your components directory in your Nuxt module.
Imagine a directory structure like this:
```bash [Directory Structure]
-| node_modules/
---| awesome-ui/
-----| components/
-------| Alert.vue
-------| Button.vue
-----| nuxt.ts
-| pages/
---| index.vue
-| nuxt.config.ts
```
Then in `awesome-ui/nuxt.ts` you can use the `addComponentsDir` hook:
```ts twoslash
import { addComponentsDir, createResolver, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup () {
const resolver = createResolver(import.meta.url)
// Add ./components dir to the list
addComponentsDir({
path: resolver.resolve('./components'),
prefix: 'awesome',
})
},
})
```
That's it! Now in your project, you can import your UI library as a Nuxt module in your `nuxt.config` file:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
modules: ['awesome-ui/nuxt'],
})
```
... and directly use the module components (prefixed with `awesome-`) in our `app/pages/index.vue`:
```vue
<template>
<div>
My <AwesomeButton>UI button</AwesomeButton>!
<awesome-alert>Here's an alert!</awesome-alert>
</div>
</template>
```
It will automatically import the components only if used and also support HMR when updating your components in `node_modules/awesome-ui/components/`.
:link-example{to="/docs/4.x/examples/features/auto-imports"}

View File

@@ -0,0 +1,121 @@
---
title: 'composables'
head.title: 'composables/'
description: Use the composables/ directory to auto-import your Vue composables into your application.
navigation.icon: i-vscode-icons-folder-type-src
---
## Usage
**Method 1:** Using named export
```ts [app/composables/useFoo.ts]
export const useFoo = () => {
return useState('foo', () => 'bar')
}
```
**Method 2:** Using default export
```ts [app/composables/use-foo.ts or composables/useFoo.ts]
// It will be available as useFoo() (camelCase of file name without extension)
export default function () {
return useState('foo', () => 'bar')
}
```
**Usage:** You can now use auto imported composable in `.js`, `.ts` and `.vue` files
```vue [app/app.vue]
<script setup lang="ts">
const foo = useFoo()
</script>
<template>
<div>
{{ foo }}
</div>
</template>
```
::note
The `app/composables/` directory in Nuxt does not provide any additional reactivity capabilities to your code. Instead, any reactivity within composables is achieved using Vue's Composition API mechanisms, such as ref and reactive. Note that reactive code is also not limited to the boundaries of the `app/composables/` directory. You are free to employ reactivity features wherever they're needed in your application.
::
:read-more{to="/docs/4.x/guide/concepts/auto-imports"}
:link-example{to="/docs/4.x/examples/features/auto-imports"}
## Types
Under the hood, Nuxt auto generates the file `.nuxt/imports.d.ts` to declare the types.
Be aware that you have to run [`nuxt prepare`](/docs/4.x/api/commands/prepare), [`nuxt dev`](/docs/4.x/api/commands/dev) or [`nuxt build`](/docs/4.x/api/commands/build) in order to let Nuxt generate the types.
::note
If you create a composable without having the dev server running, TypeScript will throw an error, such as `Cannot find name 'useBar'.`
::
## Examples
### Nested Composables
You can use a composable within another composable using auto imports:
```ts [app/composables/test.ts]
export const useFoo = () => {
const nuxtApp = useNuxtApp()
const bar = useBar()
}
```
### Access plugin injections
You can access [plugin injections](/docs/4.x/directory-structure/app/plugins#providing-helpers) from composables:
```ts [app/composables/test.ts]
export const useHello = () => {
const nuxtApp = useNuxtApp()
return nuxtApp.$hello
}
```
## How Files Are Scanned
Nuxt only scans files at the top level of the [`app/composables/` directory](/docs/4.x/directory-structure/app/composables), e.g.:
```bash [Directory Structure]
-| composables/
---| index.ts // scanned
---| useFoo.ts // scanned
---| nested/
-----| utils.ts // not scanned
```
Only `app/composables/index.ts` and `app/composables/useFoo.ts` would be searched for imports.
To get auto imports working for nested modules, you could either re-export them (recommended) or configure the scanner to include nested directories:
**Example:** Re-export the composables you need from the `app/composables/index.ts` file:
```ts [app/composables/index.ts]
// Enables auto import for this export
export { utils } from './nested/utils.ts'
```
**Example:** Scan nested directories inside the `app/composables/` folder:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
imports: {
dirs: [
// Scan top-level composables
'~/composables',
// ... or scan composables nested one level deep with a specific name and file extension
'~/composables/*/index.{ts,js,mjs,mts}',
// ... or scan all composables within given directory
'~/composables/**',
],
},
})
```

View File

@@ -0,0 +1,275 @@
---
title: "layouts"
head.title: "layouts/"
description: "Nuxt provides a layouts framework to extract common UI patterns into reusable layouts."
navigation.icon: i-vscode-icons-folder-type-view
---
::tip{icon="i-lucide-rocket" }
For best performance, components placed in this directory will be automatically loaded via asynchronous import when used.
::
## Enable Layouts
Layouts are enabled by adding [`<NuxtLayout>`](/docs/4.x/api/components/nuxt-layout) to your [`app.vue`](/docs/4.x/directory-structure/app/app):
```vue [app/app.vue]
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
```
To use a layout:
- Set a `layout` property in your page with [definePageMeta](/docs/4.x/api/utils/define-page-meta).
- Set the `name` prop of `<NuxtLayout>`.
- Set the `appLayout` property in route rules.
::note
The layout name is normalized to kebab-case, so `someLayout` becomes `some-layout`.
::
::note
If no layout is specified, `app/layouts/default.vue` will be used.
::
::important
If you only have a single layout in your application, we recommend using [`app.vue`](/docs/4.x/directory-structure/app/app) instead.
::
::important
Unlike other components, your layouts must have a single root element to allow Nuxt to apply transitions between layout changes - and this root element cannot be a `<slot />`.
::
## Default Layout
Add a `~/layouts/default.vue`:
```vue [app/layouts/default.vue]
<template>
<div>
<p>Some default layout content shared across all pages</p>
<slot />
</div>
</template>
```
In a layout file, the content of the page will be displayed in the `<slot />` component.
## Named Layout
```bash [Directory Structure]
-| layouts/
---| default.vue
---| custom.vue
```
Then you can use the `custom` layout in your page:
```vue twoslash [pages/about.vue]
<script setup lang="ts">
declare module 'nuxt/app' {
interface NuxtLayouts {
'custom': unknown
}
}
// ---cut---
definePageMeta({
layout: 'custom',
})
</script>
```
::read-more{to="/docs/4.x/directory-structure/app/pages#page-metadata"}
Learn more about `definePageMeta`.
::
You can directly override the default layout for all pages using the `name` property of [`<NuxtLayout>`](/docs/4.x/api/components/nuxt-layout):
```vue [app/app.vue]
<script setup lang="ts">
// You might choose this based on an API call or logged-in status
const layout = 'custom'
</script>
<template>
<NuxtLayout :name="layout">
<NuxtPage />
</NuxtLayout>
</template>
```
If you have a layout in nested directories, the layout's name will be based on its own path directory and filename, with duplicate segments being removed.
File | Layout Name
-- | --
`~/layouts/desktop/default.vue` | `desktop-default`
`~/layouts/desktop-base/base.vue` | `desktop-base`
`~/layouts/desktop/index.vue` | `desktop`
For clarity, we recommend that the layout's filename matches its name:
File | Layout Name
-- | --
`~/layouts/desktop/DesktopDefault.vue` | `desktop-default`
`~/layouts/desktop-base/DesktopBase.vue` | `desktop-base`
`~/layouts/desktop/Desktop.vue` | `desktop`
:link-example{to="/docs/4.x/examples/features/layouts"}
## Changing the Layout Dynamically
You can also use the [`setPageLayout`](/docs/4.x/api/utils/set-page-layout) helper to change the layout dynamically:
```vue twoslash [app/pages/index.vue]
<script setup lang="ts">
declare module 'nuxt/app' {
interface NuxtLayouts {
'custom': unknown
}
}
// ---cut---
function enableCustomLayout () {
setPageLayout('custom')
}
definePageMeta({
layout: false,
})
</script>
<template>
<div>
<button @click="enableCustomLayout">
Update layout
</button>
</div>
</template>
```
You can also set layouts for specific routes using the `appLayout` property in route rules:
```ts [nuxt.config.ts]
export default defineNuxtConfig({
routeRules: {
// Set layout for specific route
'/admin': { appLayout: 'admin' },
// Set layout for multiple routes
'/dashboard/**': { appLayout: 'dashboard' },
// Disable layout for a route
'/landing': { appLayout: false },
},
})
```
::tip
This is useful when you want to manage layouts centrally in your configuration rather than in each page file, or when you need to apply layouts to routes that don't have corresponding page components (such as catchall pages which might match many paths).
::
:link-example{to="/docs/4.x/examples/features/layouts"}
## Passing Props to Layouts :badge[+4.4]{color="primary" class="align-middle"}
You can pass props to layouts in several ways.
### Via `definePageMeta`
Use the object syntax for the `layout` property to pass props directly from your page:
::code-group
```vue [app/pages/dashboard.vue]
<script setup lang="ts">
definePageMeta({
layout: {
name: 'panel',
props: {
sidebar: true,
title: 'Dashboard',
},
},
})
</script>
```
```vue [app/layouts/panel.vue]
<script setup lang="ts">
const props = defineProps<{
sidebar?: boolean
title?: string
}>()
</script>
<template>
<div>
<aside v-if="sidebar">
Sidebar
</aside>
<main>
<h1>{{ title }}</h1>
<slot />
</main>
</div>
</template>
```
::
::tip
Props are fully typed based on your layout's `defineProps`. You'll get autocomplete and type-checking in your editor.
::
### Via `setPageLayout`
You can also pass props when changing the layout dynamically with [`setPageLayout`](/docs/4.x/api/utils/set-page-layout):
```ts
setPageLayout('panel', { sidebar: true, title: 'Dashboard' })
```
## Overriding a Layout on a Per-page Basis
If you are using pages, you can take full control by setting `layout: false` and then using the `<NuxtLayout>` component within the page.
::code-group
```vue [app/pages/index.vue]
<script setup lang="ts">
definePageMeta({
layout: false,
})
</script>
<template>
<div>
<NuxtLayout name="custom">
<template #header>
Some header template content.
</template>
The rest of the page
</NuxtLayout>
</div>
</template>
```
```vue [app/layouts/custom.vue]
<template>
<div>
<header>
<slot name="header">
Default header content
</slot>
</header>
<main>
<slot />
</main>
</div>
</template>
```
::
::important
If you use `<NuxtLayout>` within your pages, make sure it is not the root element (or [disable layout/page transitions](/docs/4.x/getting-started/transitions#disable-transitions)).
::

View File

@@ -0,0 +1,253 @@
---
title: "middleware"
description: "Nuxt provides middleware to run code before navigating to a particular route."
head.title: "middleware/"
navigation.icon: i-vscode-icons-folder-type-middleware
---
Nuxt provides a customizable **route middleware** framework you can use throughout your application, ideal for extracting code that you want to run before navigating to a particular route.
There are three kinds of route middleware:
1. Anonymous (or inline) route middleware are defined directly within the page.
2. Named route middleware, placed in the `app/middleware/` and automatically loaded via asynchronous import when used on a page.
3. Global route middleware, placed in the `app/middleware/` with a `.global` suffix and is run on every route change.
The first two kinds of route middleware can be defined in [`definePageMeta`](/docs/4.x/api/utils/define-page-meta).
::note
Name of middleware are normalized to kebab-case: `myMiddleware` becomes `my-middleware`.
::
::note
Route middleware run within the Vue part of your Nuxt app. Despite the similar name, they are completely different from [server middleware](/docs/4.x/directory-structure/server#server-middleware), which are run in the Nitro server part of your app.
::
:video-accordion{title="Watch a video from Vue School on all 3 kinds of middleware" videoId="761471577" platform="vimeo"}
## Usage
Route middleware are navigation guards that receive the current route and the next route as arguments.
```ts twoslash [middleware/my-middleware.ts]
export default defineNuxtRouteMiddleware((to, from) => {
if (to.params.id === '1') {
return abortNavigation()
}
// In a real app you would probably not redirect every route to `/`
// however it is important to check `to.path` before redirecting or you
// might get an infinite redirect loop
if (to.path !== '/') {
return navigateTo('/')
}
})
```
Nuxt provides two globally available helpers that can be returned directly from the middleware.
1. [`navigateTo`](/docs/4.x/api/utils/navigate-to) - Redirects to the given route
2. [`abortNavigation`](/docs/4.x/api/utils/abort-navigation) - Aborts the navigation, with an optional error message.
Unlike [navigation guards](https://router.vuejs.org/guide/advanced/navigation-guards#Global-Before-Guards) from `vue-router`, a third `next()` argument is not passed, and **redirect or route cancellation is handled by returning a value from the middleware**.
Possible return values are:
* nothing (a simple `return` or no return at all) - does not block navigation and will move to the next middleware function, if any, or complete the route navigation
* `return navigateTo('/')` - redirects to the given path and will set the redirect code to [`302` Found](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/302) if the redirect happens on the server side
* `return navigateTo('/', { redirectCode: 301 })` - redirects to the given path and will set the redirect code to [`301` Moved Permanently](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/301) if the redirect happens on the server side
* `return abortNavigation()` - stops the current navigation
* `return abortNavigation(error)` - rejects the current navigation with an error
:read-more{to="/docs/4.x/api/utils/navigate-to"}
:read-more{to="/docs/4.x/api/utils/abort-navigation"}
::important
We recommend using the helper functions above for performing redirects or stopping navigation. Other possible return values described in [the vue-router docs](https://router.vuejs.org/guide/advanced/navigation-guards#Global-Before-Guards) may work but there may be breaking changes in future.
::
## Middleware Order
Middleware runs in the following order:
1. Global Middleware
2. Page defined middleware order (if there are multiple middleware declared with the array syntax)
For example, assuming you have the following middleware and component:
```bash [app/middleware/ directory]
-| middleware/
---| analytics.global.ts
---| setup.global.ts
---| auth.ts
```
```vue twoslash [pages/profile.vue]
<script setup lang="ts">
definePageMeta({
middleware: [
function (to, from) {
// Custom inline middleware
},
'auth',
],
})
</script>
```
You can expect the middleware to be run in the following order:
1. `analytics.global.ts`
2. `setup.global.ts`
3. Custom inline middleware
4. `auth.ts`
### Ordering Global Middleware
By default, global middleware is executed alphabetically based on the filename.
However, there may be times you want to define a specific order. For example, in the last scenario, `setup.global.ts` may need to run before `analytics.global.ts`. In that case, we recommend prefixing global middleware with 'alphabetical' numbering.
```bash [Directory structure]
-| middleware/
---| 01.setup.global.ts
---| 02.analytics.global.ts
---| auth.ts
```
::note
In case you're new to 'alphabetical' numbering, remember that filenames are sorted as strings, not as numeric values. For example, `10.new.global.ts` would come before `2.new.global.ts`. This is why the example prefixes single digit numbers with `0`.
::
## When Middleware Runs
If your site is server-rendered or generated, middleware for the initial page will be executed both when the page is rendered and then again on the client. This might be needed if your middleware needs a browser environment, such as if you have a generated site, aggressively cache responses, or want to read a value from local storage.
However, if you want to avoid this behaviour you can do so:
```ts twoslash [middleware/example.ts]
export default defineNuxtRouteMiddleware((to) => {
// skip middleware on server
if (import.meta.server) {
return
}
// skip middleware on client side entirely
if (import.meta.client) {
return
}
// or only skip middleware on initial client load
const nuxtApp = useNuxtApp()
if (import.meta.client && nuxtApp.isHydrating && nuxtApp.payload.serverRendered) {
return
}
})
```
This is true even if you throw an error in your middleware on the server, and an error page is rendered. The middleware will still run again in the browser.
::note
Rendering an error page is an entirely separate page load, meaning any registered middleware will run again. You can use [`useError`](/docs/4.x/getting-started/error-handling#useerror) in middleware to check if an error is being handled.
::
## Accessing Route in Middleware
Always use the `to` and `from` parameters in your middleware to access the next and previous routes. Avoid using the [`useRoute()`](/docs/4.x/api/composables/use-route) composable in this context altogether.
There is **no concept of a "current route" in middleware**, as middleware can abort a navigation or redirect to a different route. The `useRoute()` composable will always be inaccurate in this context.
::warning
Sometimes, you might call a composable that uses `useRoute()` internally, which can trigger this warning even if there is no direct call in your middleware.
This leads to the **same issue as above**, so you should structure your functions to accept the route as an argument instead when they are used in middleware.
::
::code-group
```ts twoslash [middleware/access-route.ts]
// @errors: 2304
export default defineNuxtRouteMiddleware((to) => {
// passing the route to the function to avoid calling `useRoute()` in middleware
doSomethingWithRoute(to)
// ❌ this will output a warning and is NOT recommended
callsRouteInternally()
})
```
```ts twoslash [utils/handle-route.ts]
// providing the route as an argument so that it can be used in middleware correctly
export function doSomethingWithRoute (route = useRoute()) {
// ...
}
```
```ts twoslash [utils/dont-do-this.ts]
// ❌ this function is not suitable for use in middleware
export function callsRouteInternally () {
const route = useRoute()
// ...
}
```
::
## Adding Middleware Dynamically
It is possible to add global or named route middleware manually using the [`addRouteMiddleware()`](/docs/4.x/api/utils/add-route-middleware) helper function, such as from within a plugin.
```ts twoslash
export default defineNuxtPlugin(() => {
addRouteMiddleware('global-test', () => {
console.log('this global middleware was added in a plugin and will be run on every route change')
}, { global: true })
addRouteMiddleware('named-test', () => {
console.log('this named middleware was added in a plugin and would override any existing middleware of the same name')
})
})
```
## Example
```bash [Directory Structure]
-| middleware/
---| auth.ts
```
In your page file, you can reference this route middleware:
```vue twoslash
<script setup lang="ts">
definePageMeta({
middleware: ['auth'],
// or middleware: 'auth'
})
</script>
```
Now, before navigation to that page can complete, the `auth` route middleware will be run.
:link-example{to="/docs/4.x/examples/routing/middleware"}
## Setting Middleware at Build Time
Instead of using `definePageMeta` on each page, you can add named route middleware within the `pages:extend` hook.
```ts twoslash [nuxt.config.ts]
import type { NuxtPage } from 'nuxt/schema'
export default defineNuxtConfig({
hooks: {
'pages:extend' (pages) {
function setMiddleware (pages: NuxtPage[]) {
for (const page of pages) {
if (/* some condition */ Math.random() > 0.5) {
page.meta ||= {}
// Note that this will override any middleware set in `definePageMeta` in the page
page.meta.middleware = ['named']
}
if (page.children) {
setMiddleware(page.children)
}
}
}
setMiddleware(pages)
},
},
})
```

View File

@@ -0,0 +1,467 @@
---
title: "pages"
description: "Nuxt provides file-based routing to create routes within your web application."
head.title: "pages/"
navigation.icon: i-vscode-icons-folder-type-view
---
::note
To reduce your application's bundle size, this directory is **optional**, meaning that [`vue-router`](https://router.vuejs.org) won't be included if you only use [`app.vue`](/docs/4.x/directory-structure/app/app). To force the pages system, set `pages: true` in `nuxt.config` or have a [`router.options.ts`](/docs/4.x/guide/recipes/custom-routing#using-routeroptions).
::
## Usage
Pages are Vue components and can have any [valid extension](/docs/4.x/api/nuxt-config#extensions) that Nuxt supports (by default `.vue`, `.js`, `.jsx`, `.mjs`, `.ts` or `.tsx`).
Nuxt will automatically create a route for every page in your `~/pages/` directory.
::code-group
```vue [app/pages/index.vue]
<template>
<h1>Index page</h1>
</template>
```
```ts twoslash [pages/index.ts]
// https://vuejs.org/guide/extras/render-function.html
export default defineComponent({
render () {
return h('h1', 'Index page')
},
})
```
```tsx twoslash [pages/index.tsx]
// https://nuxt.com/docs/4.x/examples/advanced/jsx
// https://vuejs.org/guide/extras/render-function.html#jsx-tsx
export default defineComponent({
render () {
return <h1>Index page</h1>
},
})
```
::
The `app/pages/index.vue` file will be mapped to the `/` route of your application.
If you are using [`app.vue`](/docs/4.x/directory-structure/app/app), make sure to use the [`<NuxtPage/>`](/docs/4.x/api/components/nuxt-page) component to display the current page:
```vue [app/app.vue]
<template>
<div>
<!-- Markup shared across all pages, ex: NavBar -->
<NuxtPage />
</div>
</template>
```
Pages **must have a single root element** to allow [route transitions](/docs/4.x/getting-started/transitions) between pages. HTML comments are considered elements as well.
This means that when the route is server-rendered, or statically generated, you will be able to see its contents correctly, but when you navigate towards that route during client-side navigation the transition between routes will fail and you'll see that the route will not be rendered.
Here are some examples to illustrate what a page with a single root element looks like:
::code-group
```vue [app/pages/working.vue]
<template>
<div>
<!-- This page correctly has only one single root element -->
Page content
</div>
</template>
```
```vue [app/pages/bad-1.vue]
<template>
<!-- This page will not render when route changes during client side navigation, because of this comment -->
<div>Page content</div>
</template>
```
```vue [app/pages/bad-2.vue]
<template>
<div>This page</div>
<div>Has more than one root element</div>
<div>And will not render when route changes during client side navigation</div>
</template>
```
::
## Dynamic Routes
If you place anything within square brackets, it will be turned into a [dynamic route](https://router.vuejs.org/guide/essentials/dynamic-matching) parameter. You can mix and match multiple parameters and even non-dynamic text within a file name or directory.
If you want a parameter to be _optional_, you must enclose it in double square brackets - for example, `~/pages/[[slug]]/index.vue` or `~/pages/[[slug]].vue` will match both `/` and `/test`.
```bash [Directory Structure]
-| pages/
---| index.vue
---| users-[group]/
-----| [id].vue
```
Given the example above, you can access group/id within your component via the `$route` object:
```vue [app/pages/users-[group\\]/[id\\].vue]
<template>
<p>{{ $route.params.group }} - {{ $route.params.id }}</p>
</template>
```
Navigating to `/users-admins/123` would render:
```html
<p>admins - 123</p>
```
If you want to access the route using Composition API, there is a global [`useRoute`](/docs/4.x/api/composables/use-route) function that will allow you to access the route just like `this.$route` in the Options API.
```vue twoslash
<script setup lang="ts">
const route = useRoute()
if (route.params.group === 'admins' && !route.params.id) {
console.log('Warning! Make sure user is authenticated!')
}
</script>
```
::note
Named parent routes will take priority over nested dynamic routes. For the `/foo/hello` route, `~/pages/foo.vue` will take priority over `~/pages/foo/[slug].vue`. :br Use `~/pages/foo/index.vue` and `~/pages/foo/[slug].vue` to match `/foo` and `/foo/hello` with different pages,.
::
:video-accordion{title="Watch a video from Vue School on dynamic routes" videoId="754465699" platform="vimeo"}
## Catch-all Route
If you need a catch-all route, you create it by using a file named like `[...slug].vue`. This will match _all_ routes under that path.
```vue [app/pages/[...slug\\].vue]
<template>
<p>{{ $route.params.slug }}</p>
</template>
```
Navigating to `/hello/world` would render:
```html
<p>["hello", "world"]</p>
```
## Nested Routes
It is possible to display [nested routes](https://router.vuejs.org/guide/essentials/nested-routes) with `<NuxtPage>`.
Example:
```bash [Directory Structure]
-| pages/
---| parent/
-----| child.vue
---| parent.vue
```
This file tree will generate these routes:
```js
[
{
path: '/parent',
component: '~/pages/parent.vue',
name: 'parent',
children: [
{
path: 'child',
component: '~/pages/parent/child.vue',
name: 'parent-child',
},
],
},
]
```
To display the `child.vue` component, you have to insert the `<NuxtPage>` component inside `app/pages/parent.vue`:
```vue {}[pages/parent.vue]
<template>
<div>
<h1>I am the parent view</h1>
<NuxtPage :foobar="123" />
</div>
</template>
```
```vue {}[pages/parent/child.vue]
<script setup lang="ts">
const props = defineProps({
foobar: String,
})
console.log(props.foobar)
</script>
```
### Child Route Keys
If you want more control over when the `<NuxtPage>` component is re-rendered (for example, for transitions), you can either pass a string or function via the `pageKey` prop, or you can define a `key` value via `definePageMeta`:
```vue {}[pages/parent.vue]
<template>
<div>
<h1>I am the parent view</h1>
<NuxtPage :page-key="route => route.fullPath" />
</div>
</template>
```
Or alternatively:
```vue twoslash {}[pages/parent/child.vue]
<script setup lang="ts">
definePageMeta({
key: route => route.fullPath,
})
</script>
```
:link-example{to="/docs/4.x/examples/routing/pages"}
## Route Groups
In some cases, you may want to group a set of routes together in a way which doesn't affect file-based routing. For this purpose, you can put files in a folder which is wrapped in parentheses - `(` and `)`.
For example:
```bash [Directory structure]
-| pages/
---| index.vue
---| (marketing)/
-----| about.vue
-----| contact.vue
```
This will produce `/`, `/about` and `/contact` pages in your app. The `marketing` group is ignored for purposes of your URL structure.
### Accessing Route Groups
Route groups are automatically available in the route metadata as `route.meta.groups`.
This allows you to access the group information in your components for conditional logic, styling, or other purposes.
```vue {}[pages/(marketing)/about.vue]
<script setup lang="ts">
const route = useRoute()
console.log(route.meta.groups) // Output: ['marketing']
</script>
<template>
<div>
<p v-if="route.meta.groups?.includes('marketing')">
This is a marketing page
</p>
</div>
</template>
```
## Page Metadata
You might want to define metadata for each route in your app. You can do this using the `definePageMeta` macro, which will work both in `<script>` and in `<script setup>`:
```vue twoslash
<script setup lang="ts">
definePageMeta({
title: 'My home page',
})
</script>
```
This data can then be accessed throughout the rest of your app from the `route.meta` object.
```vue twoslash
<script setup lang="ts">
const route = useRoute()
console.log(route.meta.title) // My home page
</script>
```
If you are using nested routes, the page metadata from all these routes will be merged into a single object. For more on route meta, see the [vue-router docs](https://router.vuejs.org/guide/advanced/meta).
Much like `defineEmits` or `defineProps` (see [Vue docs](https://vuejs.org/api/sfc-script-setup#defineprops-defineemits)), `definePageMeta` is a **compiler macro**. It will be compiled away so you cannot reference it within your component. Instead, the metadata passed to it will be hoisted out of the component.
Therefore, the page meta object cannot reference the component. However, it can reference imported bindings, as well as locally defined **pure functions**.
::warning
Make sure not to reference any reactive data or functions that cause side effects. This can lead to unexpected behavior.
::
```vue
<script setup lang="ts">
import { someData } from '~/utils/example'
function validateIdParam (route) {
return route.params.id && !Number.isNaN(Number(route.params.id))
}
const title = ref('')
definePageMeta({
validate: validateIdParam,
someData,
title, // do not do this, the ref will be hoisted out of the component
})
</script>
```
### Special Metadata
Of course, you are welcome to define metadata for your own use throughout your app. But some metadata defined with `definePageMeta` has a particular purpose:
#### `alias`
You can define page aliases. They allow you to access the same page from different paths. It can be either a string or an array of strings as defined [in the vue-router documentation](https://router.vuejs.org/guide/essentials/redirect-and-alias#Alias).
#### `keepalive`
Nuxt will automatically wrap your page in [the Vue `<KeepAlive>` component](https://vuejs.org/guide/built-ins/keep-alive#keepalive) if you set `keepalive: true` in your `definePageMeta`. This might be useful to do, for example, in a parent route that has dynamic child routes, if you want to preserve page state across route changes.
When your goal is to preserve state for parent routes use this syntax: `<NuxtPage keepalive />`. You can also set props to be passed to `<KeepAlive>` (see [a full list](https://vuejs.org/api/built-in-components#keepalive)).
You can set a default value for this property [in your `nuxt.config`](/docs/4.x/api/nuxt-config#keepalive).
#### `key`
[See above](/docs/4.x/directory-structure/app/pages#child-route-keys).
#### `layout`
You can define the layout used to render the route. This can be either false (to disable any layout), a string or a ref/computed, if you want to make it reactive in some way. [More about layouts](/docs/4.x/directory-structure/app/layouts).
#### `layoutTransition` and `pageTransition`
You can define transition properties for the `<transition>` component that wraps your pages and layouts, or pass `false` to disable the `<transition>` wrapper for that route. You can see [a list of options that can be passed](https://vuejs.org/api/built-in-components#transition) or read [more about how transitions work](https://vuejs.org/guide/built-ins/transition#transition).
You can set default values for these properties [in your `nuxt.config`](/docs/4.x/api/nuxt-config#layouttransition).
#### `middleware`
You can define middleware to apply before loading this page. It will be merged with all the other middleware used in any matching parent/child routes. It can be a string, a function (an anonymous/inlined middleware function following [the global before guard pattern](https://router.vuejs.org/guide/advanced/navigation-guards#Global-Before-Guards)), or an array of strings/functions. [More about named middleware](/docs/4.x/directory-structure/app/middleware).
#### `name`
You may define a name for this page's route.
#### `path`
You may define a path matcher, if you have a more complex pattern than can be expressed with the file name. See [the `vue-router` docs](https://router.vuejs.org/guide/essentials/route-matching-syntax#Custom-regex-in-params) for more information.
#### `props`
Allows accessing the route `params` as props passed to the page component. See [the `vue-router` docs](https://router.vuejs.org/guide/essentials/passing-props) for more information.
### Typing Custom Metadata
If you add custom metadata for your pages, you may wish to do so in a type-safe way. It is possible to augment the type of the object accepted by `definePageMeta`:
```ts [index.d.ts]
declare module '#app' {
interface PageMeta {
pageType?: string
}
}
// It is always important to ensure you import/export something when augmenting a type
export {}
```
## Navigation
To navigate between pages of your app, you should use the [`<NuxtLink>`](/docs/4.x/api/components/nuxt-link) component.
This component is included with Nuxt and therefore you don't have to import it as you do with other components.
A simple link to the `index.vue` page in your `app/pages` folder:
```vue
<template>
<NuxtLink to="/">Home page</NuxtLink>
</template>
```
::read-more{to="/docs/4.x/api/components/nuxt-link"}
Learn more about `<NuxtLink>` usage.
::
## Programmatic Navigation
Nuxt allows programmatic navigation through the `navigateTo()` utility method. Using this utility method, you will be able to programmatically navigate the user in your app. This is great for taking input from the user and navigating them dynamically throughout your application. In this example, we have a simple method called `navigate()` that gets called when the user submits a search form.
::note
Make sure to always `await` on `navigateTo` or chain its result by returning from functions.
::
```vue twoslash
<script setup lang="ts">
const name = ref('')
const type = ref(1)
function navigate () {
return navigateTo({
path: '/search',
query: {
name: name.value,
type: type.value,
},
})
}
</script>
```
## Client-Only Pages
You can define a page as [client only](/docs/4.x/directory-structure/app/components#client-components) by giving it a `.client.vue` suffix. None of the content of this page will be rendered on the server.
## Server-Only Pages
You can define a page as [server only](/docs/4.x/directory-structure/app/components#server-components) by giving it a `.server.vue` suffix. While you will be able to navigate to the page using client-side navigation, controlled by `vue-router`, it will be rendered with a server component automatically, meaning the code required to render the page will not be in your client-side bundle.
::warning
Server-only pages must have a single root element. (HTML comments are considered elements as well.)
::
## Custom Routing
As your app gets bigger and more complex, your routing might require more flexibility. For this reason, Nuxt directly exposes the router, routes and router options for customization in different ways.
:read-more{to="/docs/4.x/guide/recipes/custom-routing"}
## Multiple Pages Directories
By default, all your pages should be in one `app/pages` directory at the root of your project.
However, you can use [Nuxt Layers](/docs/4.x/getting-started/layers) to create groupings of your app's pages:
```bash [Directory Structure]
-| some-app/
---| nuxt.config.ts
---| pages/
-----| app-page.vue
-| nuxt.config.ts
```
```ts twoslash [some-app/nuxt.config.ts]
// some-app/nuxt.config.ts
export default defineNuxtConfig({
})
```
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
extends: ['./some-app'],
})
```
:read-more{to="/docs/4.x/guide/going-further/layers"}

View File

@@ -0,0 +1,298 @@
---
title: "plugins"
description: "Nuxt has a plugins system to use Vue plugins and more at the creation of your Vue application."
head.title: "plugins/"
navigation.icon: i-vscode-icons-folder-type-plugin
---
Nuxt automatically reads the files in the `app/plugins/` directory and loads them at the creation of the Vue application.
::note
All plugins inside are auto-registered, you don't need to add them to your `nuxt.config` separately.
::
::note
You can use `.server` or `.client` suffix in the file name to load a plugin only on the server or client side.
::
## Registered Plugins
Only files at the top level of the directory (or index files within any subdirectories) will be auto-registered as plugins.
```bash [Directory structure]
-| plugins/
---| foo.ts // scanned
---| bar/
-----| baz.ts // not scanned
-----| foz.vue // not scanned
-----| index.ts // currently scanned but deprecated
```
Only `foo.ts` and `bar/index.ts` would be registered.
To add plugins in subdirectories, you can use the [`app/plugins`](/docs/4.x/api/nuxt-config#plugins-1) option in `nuxt.config.ts`:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
plugins: [
'~/plugins/bar/baz',
'~/plugins/bar/foz',
],
})
```
## Creating Plugins
The only argument passed to a plugin is [`nuxtApp`](/docs/4.x/api/composables/use-nuxt-app).
```ts twoslash [plugins/hello.ts]
export default defineNuxtPlugin((nuxtApp) => {
// Doing something with nuxtApp
})
```
### Object Syntax Plugins
It is also possible to define a plugin using an object syntax, for more advanced use cases. For example:
```ts twoslash [plugins/hello.ts]
export default defineNuxtPlugin({
name: 'my-plugin',
enforce: 'pre', // or 'post'
async setup (nuxtApp) {
// this is the equivalent of a normal functional plugin
},
hooks: {
// You can directly register Nuxt app runtime hooks here
'app:created' () {
const nuxtApp = useNuxtApp()
// do something in the hook
},
},
env: {
// Set this value to `false` if you don't want the plugin to run when rendering server-only or island components.
islands: true,
},
})
```
:video-accordion{title="Watch a video from Alexander Lichter about the Object Syntax for Nuxt plugins" videoId="2aXZyXB1QGQ"}
::note
If you are using the object-syntax, the properties are statically analyzed to produce a more optimized build. So you should not define them at runtime. :br
For example, setting `enforce: import.meta.server ? 'pre' : 'post'` would defeat any future optimization Nuxt is able to do for your plugins.
Nuxt does statically pre-load any hook listeners when using object-syntax, allowing you to define hooks without needing to worry about order of plugin registration.
::
## Registration Order
You can control the order in which plugins are registered by prefixing with 'alphabetical' numbering to the file names.
```bash [Directory structure]
plugins/
| - 01.myPlugin.ts
| - 02.myOtherPlugin.ts
```
In this example, `02.myOtherPlugin.ts` will be able to access anything that was injected by `01.myPlugin.ts`.
This is useful in situations where you have a plugin that depends on another plugin.
::note
In case you're new to 'alphabetical' numbering, remember that filenames are sorted as strings, not as numeric values. For example, `10.myPlugin.ts` would come before `2.myOtherPlugin.ts`. This is why the example prefixes single digit numbers with `0`.
::
## Loading Strategy
### Parallel Plugins
By default, Nuxt loads plugins sequentially. You can define a plugin as `parallel` so Nuxt won't wait until the end of the plugin's execution before loading the next plugin.
```ts twoslash [plugins/my-plugin.ts]
export default defineNuxtPlugin({
name: 'my-plugin',
parallel: true,
async setup (nuxtApp) {
// the next plugin will be executed immediately
},
})
```
### Plugins With Dependencies
If a plugin needs to wait for another plugin before it runs, you can add the plugin's name to the `dependsOn` array.
```ts twoslash [plugins/depending-on-my-plugin.ts]
export default defineNuxtPlugin({
name: 'depends-on-my-plugin',
dependsOn: ['my-plugin'],
async setup (nuxtApp) {
// this plugin will wait for the end of `my-plugin`'s execution before it runs
},
})
```
## Using Composables
You can use [composables](/docs/4.x/directory-structure/app/composables) as well as [utils](/docs/4.x/directory-structure/app/utils) within Nuxt plugins:
```ts [app/plugins/hello.ts]
export default defineNuxtPlugin((nuxtApp) => {
const foo = useFoo()
})
```
However, keep in mind there are some limitations and differences:
::important
**If a composable depends on another plugin registered later, it might not work.** :br
Plugins are called in order sequentially and before everything else. You might use a composable that depends on another plugin which has not been called yet.
::
::important
**If a composable depends on the Vue.js lifecycle, it won't work.** :br
Normally, Vue.js composables are bound to the current component instance while plugins are only bound to [`nuxtApp`](/docs/4.x/api/composables/use-nuxt-app) instance.
::
## Providing Helpers
If you would like to provide a helper on the [`NuxtApp`](/docs/4.x/api/composables/use-nuxt-app) instance, return it from the plugin under a `provide` key.
::code-group
```ts twoslash [plugins/hello.ts]
export default defineNuxtPlugin(() => {
return {
provide: {
hello: (msg: string) => `Hello ${msg}!`,
},
}
})
```
```ts twoslash [plugins/hello-object-syntax.ts]
export default defineNuxtPlugin({
name: 'hello',
setup () {
return {
provide: {
hello: (msg: string) => `Hello ${msg}!`,
},
}
},
})
```
::
You can then use the helper in your components:
```vue [app/components/Hello.vue]
<script setup lang="ts">
// alternatively, you can also use it here
const { $hello } = useNuxtApp()
</script>
<template>
<div>
{{ $hello('world') }}
</div>
</template>
```
::important
Note that we highly recommend using [`composables`](/docs/4.x/directory-structure/app/composables) instead of providing helpers to avoid polluting the global namespace and keep your main bundle entry small.
::
::warning
**If your plugin provides a `ref` or `computed`, it will not be unwrapped in a component `<template>`.** :br
This is due to how Vue works with refs that aren't top-level to the template. You can read more about it [in the Vue documentation](https://vuejs.org/guide/essentials/reactivity-fundamentals#caveat-when-unwrapping-in-templates).
::
## Typing Plugins
If you return your helpers from the plugin, they will be typed automatically; you'll find them typed for the return of `useNuxtApp()` and within your templates.
::note
If you need to use a provided helper _within_ another plugin, you can call [`useNuxtApp()`](/docs/4.x/api/composables/use-nuxt-app) to get the typed version. But in general, this should be avoided unless you are certain of the plugins' order.
::
For advanced use-cases, you can declare the type of injected properties like this:
```ts [index.d.ts]
declare module '#app' {
interface NuxtApp {
$hello (msg: string): string
}
}
declare module 'vue' {
interface ComponentCustomProperties {
$hello (msg: string): string
}
}
export {}
```
## Vue Plugins
If you want to use Vue plugins, like [vue-gtag](https://github.com/MatteoGabriele/vue-gtag) to add Google Analytics tags, you can use a Nuxt plugin to do so.
First, install the Vue plugin dependency:
::code-group{sync="pm"}
```bash [npm]
npm install --save-dev vue-gtag-next
```
```bash [yarn]
yarn add --dev vue-gtag-next
```
```bash [pnpm]
pnpm add -D vue-gtag-next
```
```bash [bun]
bun add -D vue-gtag-next
```
```bash [deno]
deno add -D npm:vue-gtag-next
```
::
Then create a plugin file:
```ts [app/plugins/vue-gtag.client.ts]
import VueGtag, { trackRouter } from 'vue-gtag-next'
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(VueGtag, {
property: {
id: 'GA_MEASUREMENT_ID',
},
})
trackRouter(useRouter())
})
```
## Vue Directives
Similarly, you can register a custom Vue directive in a plugin.
```ts twoslash [plugins/my-directive.ts]
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.directive('focus', {
mounted (el) {
el.focus()
},
getSSRProps (binding, vnode) {
// you can provide SSR-specific props here
return {}
},
})
})
```
::warning
If you register a Vue directive, you _must_ register it on both client and server side unless you are only using it when rendering one side. If the directive only makes sense from a client side, you can always move it to `~/plugins/my-directive.client.ts` and provide a 'stub' directive for the server in `~/plugins/my-directive.server.ts`.
::
:read-more{icon="i-simple-icons-vuedotjs" title="Custom Directives on Vue Docs" to="https://vuejs.org/guide/reusability/custom-directives.html" target="_blank"}

View File

@@ -0,0 +1,49 @@
---
title: 'utils'
head.title: 'utils/'
description: Use the utils/ directory to auto-import your utility functions throughout your application.
navigation.icon: i-vscode-icons-folder-type-tools
---
The main purpose of the [`app/utils/` directory](/docs/4.x/directory-structure/app/utils) is to allow a semantic distinction between your Vue composables and other auto-imported utility functions.
## Usage
**Method 1:** Using named export
```ts twoslash [utils/index.ts]
export const { format: formatNumber } = Intl.NumberFormat('en-GB', {
notation: 'compact',
maximumFractionDigits: 1,
})
```
**Method 2:** Using default export
```ts twoslash [utils/random-entry.ts or utils/randomEntry.ts]
// It will be available as randomEntry() (camelCase of file name without extension)
export default function (arr: Array<any>) {
return arr[Math.floor(Math.random() * arr.length)]
}
```
You can now use auto imported utility functions in `.js`, `.ts` and `.vue` files
```vue [app/app.vue]
<template>
<p>{{ formatNumber(1234) }}</p>
</template>
```
:read-more{to="/docs/4.x/guide/concepts/auto-imports"}
:link-example{to="/docs/4.x/examples/features/auto-imports"}
::tip
The way `app/utils/` auto-imports work and are scanned is identical to the [`app/composables/`](/docs/4.x/directory-structure/app/composables) directory.
::
::important
These utils are only available within the Vue part of your app. :br
Only `server/utils` are auto-imported in the [`server/`](/docs/4.x/directory-structure/server#server-utilities) directory.
::

View File

@@ -0,0 +1,177 @@
---
title: app.config.ts
head.title: 'app.config.ts'
description: Expose reactive configuration within your application with the App Config file.
navigation.icon: i-vscode-icons-file-type-light-config
---
Nuxt provides an `app/app.config.ts` config file to expose reactive configuration within your application with the ability to update it at runtime within lifecycle or using a nuxt plugin and editing it with HMR (hot-module-replacement).
You can easily provide runtime app configuration using `app.config.ts` file. It can have either of `.ts`, `.js`, or `.mjs` extensions.
```ts twoslash [app/app.config.ts]
export default defineAppConfig({
foo: 'bar',
})
```
::caution
Do not put any secret values inside `app.config` file. It is exposed to the user client bundle.
::
::note
When configuring a custom [`srcDir`](/docs/4.x/api/nuxt-config#srcdir), make sure to place the `app.config` file at the root of the new `srcDir` path.
::
## Usage
To expose config and environment variables to the rest of your app, you will need to define configuration in `app.config` file.
```ts twoslash [app/app.config.ts]
export default defineAppConfig({
theme: {
primaryColor: '#ababab',
},
})
```
We can now universally access `theme` both when server-rendering the page and in the browser using [`useAppConfig`](/docs/4.x/api/composables/use-app-config) composable.
```vue [app/pages/index.vue]
<script setup lang="ts">
const appConfig = useAppConfig()
console.log(appConfig.theme)
</script>
```
The [`updateAppConfig`](/docs/4.x/api/utils/update-app-config) utility can be used to update the `app.config` at runtime.
```vue [app/pages/index.vue]
<script setup>
const appConfig = useAppConfig() // { foo: 'bar' }
const newAppConfig = { foo: 'baz' }
updateAppConfig(newAppConfig)
console.log(appConfig) // { foo: 'baz' }
</script>
```
::read-more{to="/docs/4.x/api/utils/update-app-config"}
Read more about the `updateAppConfig` utility.
::
## Typing App Config
Nuxt tries to automatically generate a TypeScript interface from provided app config so you won't have to type it yourself.
However, there are some cases where you might want to type it yourself. There are two possible things you might want to type.
### App Config Input
`AppConfigInput` might be used by module authors who are declaring what valid _input_ options are when setting app config. This will not affect the type of `useAppConfig()`.
```ts [index.d.ts]
declare module 'nuxt/schema' {
interface AppConfigInput {
/** Theme configuration */
theme?: {
/** Primary app color */
primaryColor?: string
}
}
}
// It is always important to ensure you import/export something when augmenting a type
export {}
```
### App Config Output
If you want to type the result of calling [`useAppConfig()`](/docs/4.x/api/composables/use-app-config), then you will want to extend `AppConfig`.
::warning
Be careful when typing `AppConfig` as you will overwrite the types Nuxt infers from your actually defined app config.
::
```ts [index.d.ts]
declare module 'nuxt/schema' {
interface AppConfig {
// This will entirely replace the existing inferred `theme` property
theme: {
// You might want to type this value to add more specific types than Nuxt can infer,
// such as string literal types
primaryColor?: 'red' | 'blue'
}
}
}
// It is always important to ensure you import/export something when augmenting a type
export {}
```
## Merging Strategy
Nuxt uses a custom merging strategy for the `AppConfig` within [the layers](/docs/4.x/getting-started/layers) of your application.
This strategy is implemented using a [Function Merger](https://github.com/unjs/defu#function-merger), which allows defining a custom merging strategy for every key in `app.config` that has an array as value.
::note
The function merger can only be used in the extended layers and not the main `app.config` in project.
::
Here's an example of how you can use:
::code-group
```ts twoslash [layer/app/app.config.ts]
export default defineAppConfig({
// Default array value
array: ['hello'],
})
```
```ts twoslash [app/app.config.ts]
export default defineAppConfig({
// Overwrite default array value by using a merger function
array: () => ['bonjour'],
})
```
::
## Known Limitations
As of Nuxt v3.3, the `app.config.ts` file is shared with Nitro, which results in the following limitations:
1. You cannot import Vue components directly in `app.config.ts`.
2. Some auto-imports are not available in the Nitro context.
These limitations occur because Nitro processes the app config without full Vue component support.
While it's possible to use Vite plugins in the Nitro config as a workaround, this approach is not recommended:
```ts [nuxt.config.ts]
export default defineNuxtConfig({
nitro: {
vite: {
plugins: [vue()],
},
},
})
```
::warning
Using this workaround may lead to unexpected behavior and bugs. The Vue plugin is one of many that are not available in the Nitro context.
::
Related issues:
- [Issue #19858](https://github.com/nuxt/nuxt/issues/19858)
- [Issue #19854](https://github.com/nuxt/nuxt/issues/19854)
::note
Nitro v3 will resolve these limitations by removing support for the app config.
You can track the progress in [this pull request](https://github.com/nitrojs/nitro/pull/2521).
::

View File

@@ -0,0 +1,72 @@
---
title: "app.vue"
description: "The app.vue file is the main component of your Nuxt application."
head.title: "app.vue"
navigation.icon: i-vscode-icons-file-type-vue
---
::tip
If you have a `app/pages/` directory, the `app.vue` file is optional. Nuxt will automatically include a default `app.vue`, but you can still add your own to customize the structure and content as needed.
::
## Usage
### Minimal Usage
With Nuxt, the [`app/pages/`](/docs/4.x/directory-structure/app/pages) directory is optional. If it is not present, Nuxt will not include the [vue-router](https://router.vuejs.org) dependency. This is useful when building a landing page or an application that does not require routing.
```vue [app/app.vue]
<template>
<h1>Hello World!</h1>
</template>
```
:link-example{to="/docs/4.x/examples/hello-world"}
### Usage with Pages
When you have a [`app/pages/`](/docs/4.x/directory-structure/app/pages) directory, you need to use the [`<NuxtPage>`](/docs/4.x/api/components/nuxt-page) component to display the current page:
```vue [app/app.vue]
<template>
<NuxtPage />
</template>
```
You can also define the common structure of your application directly in `app.vue`. This is useful when you want to include global elements such as a header or footer:
```vue [app/app.vue]
<template>
<header>
Header content
</header>
<NuxtPage />
<footer>
Footer content
</footer>
</template>
```
::note
Remember that `app.vue` acts as the main component of your Nuxt application. Anything you add to it (JS and CSS) will be global and included in every page.
::
::read-more{to="/docs/4.x/directory-structure/app/pages"}
Learn more about how to structure your pages using the `app/pages/` directory.
::
### Usage with Layouts
When your application requires different layouts for different pages, you can use the `app/layouts/` directory with the [`<NuxtLayout>`](/docs/4.x/api/components/nuxt-layout) component. This allows you to define multiple layouts and apply them per page.
```vue [app/app.vue]
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
```
::read-more{to="/docs/4.x/directory-structure/app/layouts"}
Learn more about how to structure your layouts using the `app/layouts/` directory.
::

View File

@@ -0,0 +1,53 @@
---
title: "error.vue"
description: "The error.vue file is the error page in your Nuxt application."
head.title: "error.vue"
navigation.icon: i-vscode-icons-file-type-vue
---
During the lifespan of your application, some errors may appear unexpectedly at runtime. In such case, we can use the `error.vue` file to override the default error files and display the error nicely.
```vue [error.vue]
<script setup lang="ts">
import type { NuxtError } from '#app'
const props = defineProps<{ error: NuxtError }>()
</script>
<template>
<div>
<h1>{{ error.status }}</h1>
<NuxtLink to="/">Go back home</NuxtLink>
</div>
</template>
```
::note
Although it is called an 'error page' it's not a route and shouldn't be placed in your `~/pages` directory. For the same reason, you shouldn't use `definePageMeta` within this page. That being said, you can still use layouts in the error file, by utilizing the [`NuxtLayout`](/docs/4.x/api/components/nuxt-layout) component and specifying the name of the layout.
::
The error page has a single prop - `error` which contains an error for you to handle.
The `error` object provides the following fields:
```ts
interface NuxtError {
status: number
fatal: boolean
unhandled: boolean
statusText?: string
data?: unknown
cause?: unknown
}
```
If you have an error with custom fields they will be lost; you should assign them to `data` instead:
```ts
throw createError({
status: 404,
statusText: 'Page Not Found',
data: {
myCustomField: true,
},
})
```

View File

@@ -0,0 +1,67 @@
---
title: 'content'
head.title: 'content/'
description: Use the content/ directory to create a file-based CMS for your application.
navigation.icon: i-vscode-icons-folder-type-log
---
[Nuxt Content](https://content.nuxt.com) reads the `content/` directory in your project and parses `.md`, `.yml`, `.csv` and `.json` files to create a file-based CMS for your application.
- Render your content with built-in components.
- Query your content with a MongoDB-like API.
- Use your Vue components in Markdown files with the MDC syntax.
- Automatically generate your navigation.
::read-more{to="https://content.nuxt.com" target="_blank"}
Learn more in **Nuxt Content** documentation.
::
## Enable Nuxt Content
Install the `@nuxt/content` module in your project as well as adding it to your `nuxt.config.ts` with one command:
```bash [Terminal]
npx nuxt module add content
```
## Create Content
Place your markdown files inside the `content/` directory:
```md [content/index.md]
# Hello Content
```
The module automatically loads and parses them.
## Render Content
To render content pages, add a [catch-all route](/docs/4.x/directory-structure/app/pages/#catch-all-route) using the [`<ContentRenderer>`](https://content.nuxt.com/docs/components/content-renderer) component:
```vue [app/pages/[...slug\\].vue]
<script lang="ts" setup>
const route = useRoute()
const { data: page } = await useAsyncData(route.path, () => {
return queryCollection('content').path(route.path).first()
})
</script>
<template>
<div>
<header><!-- ... --></header>
<ContentRenderer
v-if="page"
:value="page"
/>
<footer><!-- ... --></footer>
</div>
</template>
```
## Documentation
::tip{ icon="i-lucide-book" }
Head over to <https://content.nuxt.com> to learn more about the Content module features, such as how to build queries and use Vue components in your Markdown files with the MDC syntax.
::

View File

@@ -0,0 +1,87 @@
---
title: 'layers'
head.title: 'layers/'
description: Use the layers/ directory to organize and auto-register local layers within your application.
navigation.icon: i-vscode-icons-folder-type-nuxt
---
The `layers/` directory allows you to organize and share reusable code, components, composables, and configurations across your Nuxt application. Any layers within your project in the `layers/` directory will be automatically registered.
::note
The `layers/` directory auto-registration is available in Nuxt v3.12.0+.
::
::tip{icon="i-lucide-lightbulb"}
Layers are ideal for organizing large codebases with **Domain-Driven Design (DDD)**, creating reusable **UI libraries** or **themes**, sharing **configuration presets** across projects, and separating concerns like **admin panels** or **feature modules**.
::
## Structure
Each subdirectory within `layers/` is treated as a separate layer. A layer can contain the same structure as a standard Nuxt application.
::important
Every layer **must have** a `nuxt.config.ts` file to be recognized as a valid layer, even if it's empty.
::
```bash [Directory structure]
-| layers/
---| base/
-----| nuxt.config.ts
-----| app/
-------| components/
---------| BaseButton.vue
-------| composables/
---------| useBase.ts
-----| server/
-------| api/
---------| hello.ts
---| admin/
-----| nuxt.config.ts
-----| app/
-------| pages/
---------| admin.vue
-------| layouts/
---------| admin.vue
```
## Automatic Aliases
Named layer aliases to the `srcDir` of each layer are automatically created. You can access a layer using the `#layers/[name]` alias:
```ts
// Access the base layer
import something from '#layers/base/path/to/file'
// Access the admin layer
import { useAdmin } from '#layers/admin/composables/useAdmin'
```
::note
Named layer aliases were introduced in Nuxt v3.16.0.
::
## Layer Content
Each layer can include:
- [`nuxt.config.ts`](/docs/4.x/directory-structure/nuxt-config) - Layer-specific configuration that will be merged with the main config
- [`app.config.ts`](/docs/4.x/directory-structure/app/app-config) - Reactive application configuration
- [`app/components/`](/docs/4.x/directory-structure/app/components) - Vue components (auto-imported)
- [`app/composables/`](/docs/4.x/directory-structure/app/composables) - Vue composables (auto-imported)
- [`app/utils/`](/docs/4.x/directory-structure/app/utils) - Utility functions (auto-imported)
- [`app/pages/`](/docs/4.x/directory-structure/app/pages) - Application pages
- [`app/layouts/`](/docs/4.x/directory-structure/app/layouts) - Application layouts
- [`app/middleware/`](/docs/4.x/directory-structure/app/middleware) - Route middleware
- [`app/plugins/`](/docs/4.x/directory-structure/app/plugins) - Nuxt plugins
- [`server/`](/docs/4.x/directory-structure/server) - Server routes, middleware, and utilities
- [`shared/`](/docs/4.x/directory-structure/shared) - Shared code between app and server
## Priority Order
When multiple layers define the same resource (component, composable, page, etc.), the layer with **higher priority wins**. Layers are sorted alphabetically, with later letters having higher priority (Z > A).
To control the order, prefix directories with numbers: `1.base/`, `2.features/`, `3.admin/`.
:read-more{to="/docs/4.x/getting-started/layers#layer-priority"}
:video-accordion{title="Watch a video from Learn Vue about Nuxt Layers" videoId="lnFCM7c9f7I"}

View File

@@ -0,0 +1,78 @@
---
title: 'modules'
head.title: 'modules/'
description: Use the modules/ directory to automatically register local modules within your application.
navigation.icon: i-vscode-icons-folder-type-nuxt
---
It is a good place to place any local modules you develop while building your application.
The auto-registered files patterns are:
- `modules/*/index.ts`
- `modules/*.ts`
You don't need to add those local modules to your [`nuxt.config.ts`](/docs/4.x/directory-structure/nuxt-config) separately.
::code-group
```ts twoslash [modules/hello/index.ts]
// `nuxt/kit` is a helper subpath import you can use when defining local modules
// that means you do not need to add `@nuxt/kit` to your project's dependencies
import { addComponentsDir, addServerHandler, createResolver, defineNuxtModule } from 'nuxt/kit'
export default defineNuxtModule({
meta: {
name: 'hello',
},
setup () {
const resolver = createResolver(import.meta.url)
// Add an API route
addServerHandler({
route: '/api/hello',
handler: resolver.resolve('./runtime/api-route'),
})
// Add components
addComponentsDir({
path: resolver.resolve('./runtime/app/components'),
pathPrefix: true, // Prefix your exports to avoid conflicts with user code or other modules
})
},
})
```
```ts twoslash [modules/hello/runtime/api-route.ts]
import { defineEventHandler } from 'nitro/h3'
export default defineEventHandler(() => {
return { hello: 'world' }
})
```
::
When starting Nuxt, the `hello` module will be registered and the `/api/hello` route will be available.
::note
Note that all components, pages, composables and other files that would be normally placed in your `app/` directory need to be in `modules/your-module/runtime/app/`. This ensures they can be type-checked properly.
::
Modules are executed in the following sequence:
- First, the modules defined in [`nuxt.config.ts`](/docs/4.x/api/nuxt-config#modules-1) are loaded.
- Then, modules found in the `modules/` directory are executed, and they load in alphabetical order.
You can change the order of local module by adding a number to the front of each directory name:
```bash [Directory structure]
modules/
1.first-module/
index.ts
2.second-module.ts
```
:read-more{to="/docs/4.x/guide/modules"}
::tip{icon="i-lucide-video" to="https://vueschool.io/lessons/creating-your-first-module-from-scratch?friend=nuxt" target="_blank"}
Watch Vue School video about Nuxt private modules.
::

View File

@@ -0,0 +1,12 @@
---
title: "node_modules"
description: "The package manager stores the dependencies of your project in the node_modules/ directory."
head.title: "node_modules/"
navigation.icon: i-vscode-icons-folder-type-node
---
The package manager ([`npm`](https://docs.npmjs.com/cli/commands/npm/) or [`yarn`](https://yarnpkg.com) or [`pnpm`](https://pnpm.io/cli/install) or [`bun`](https://bun.com/package-manager) or [`deno`](https://docs.deno.com/runtime/getting_started/installation/)) creates this directory to store the dependencies of your project.
::important
This directory should be added to your [`.gitignore`](/docs/4.x/directory-structure/gitignore) file to avoid pushing the dependencies to your repository.
::

View File

@@ -0,0 +1,27 @@
---
title: "public"
description: "The public/ directory is used to serve your website's static assets."
head.title: "public/"
navigation.icon: i-vscode-icons-folder-type-public
---
Files contained within the `public/` directory are served at the root and are not modified by the build process. This is suitable for files that have to keep their names (e.g. `robots.txt`) _or_ likely won't change (e.g. `favicon.ico`).
```bash [Directory structure]
-| public/
---| favicon.ico
---| og-image.png
---| robots.txt
```
```vue [app/app.vue]
<script setup lang="ts">
useSeoMeta({
ogImage: '/og-image.png',
})
</script>
```
::tip{to="https://v2.nuxt.com/docs/directory-structure/static/" target="_blank"}
This is known as the [`static/`] directory in Nuxt 2.
::

View File

@@ -0,0 +1,561 @@
---
title: server
head.title: 'server/'
description: The server/ directory is used to register API and server handlers to your application.
navigation.icon: i-vscode-icons-folder-type-server
---
Nuxt automatically scans files inside these directories to register API and server handlers with Hot Module Replacement (HMR) support.
```bash [Directory structure]
-| server/
---| api/
-----| hello.ts # /api/hello
---| routes/
-----| bonjour.ts # /bonjour
---| middleware/
-----| log.ts # log all requests
```
Each file should export a default function defined with `defineEventHandler()` or `eventHandler()` (alias).
The handler can directly return JSON data, a `Promise`, or a `Response` object.
```ts twoslash [server/api/hello.ts]
import { defineEventHandler } from 'nitro/h3'
export default defineEventHandler((event) => {
return {
hello: 'world',
}
})
```
You can now universally call this API in your pages and components:
```vue [app/pages/index.vue]
<script setup lang="ts">
const { data } = await useFetch('/api/hello')
</script>
<template>
<pre>{{ data }}</pre>
</template>
```
## Server Routes
Files inside the `~~/server/api` are automatically prefixed with `/api` in their route.
:video-accordion{title="Watch a video from Vue School on API routes" videoId="761468863" platform="vimeo"}
To add server routes without `/api` prefix, put them into `~~/server/routes` directory.
**Example:**
```ts [server/routes/hello.ts]
export default defineEventHandler(() => 'Hello World!')
```
Given the example above, the `/hello` route will be accessible at <http://localhost:3000/hello>.
::note
Note that currently server routes do not support the full functionality of dynamic routes as [pages](/docs/4.x/directory-structure/app/pages#dynamic-routes) do.
::
## Server Middleware
Nuxt will automatically read in any file in the `~~/server/middleware` to create server middleware for your project.
Middleware handlers will run on every request before any other server route to add or check headers, log requests, or extend the event's request object.
::note
Middleware handlers should not return anything (nor close or respond to the request) and only inspect or extend the request context or throw an error.
::
**Examples:**
```ts [server/middleware/log.ts]
export default defineEventHandler((event) => {
console.log('New request: ' + getRequestURL(event))
})
```
```ts [server/middleware/auth.ts]
export default defineEventHandler((event) => {
event.context.auth = { user: 123 }
})
```
## Server Plugins
Nuxt will automatically read any files in the `~~/server/plugins` directory and register them as Nitro plugins. This allows extending Nitro's runtime behavior and hooking into lifecycle events.
**Example:**
```ts [server/plugins/nitroPlugin.ts]
import { definePlugin } from 'nitro'
export default definePlugin((nitroApp) => {
console.log('Nitro plugin', nitroApp)
})
```
:read-more{to="https://nitro.build/guide/plugins" title="Nitro Plugins" target="_blank"}
## Server Utilities
Server routes are powered by [h3js/h3](https://github.com/h3js/h3) which comes with a handy set of helpers.
:read-more{to="https://www.jsdocs.io/package/h3#package-index-functions" title="Available H3 Request Helpers" target="_blank"}
You can add more helpers yourself inside the `~~/server/utils` directory.
For example, you can define a custom handler utility that wraps the original handler and performs additional operations before returning the final response.
**Example:**
```ts [server/utils/handler.ts]
export const defineWrappedResponseHandler = <T extends EventHandlerRequest, D> (
handler: EventHandler<T, D>,
): EventHandler<T, D> =>
defineEventHandler<T>(async (event) => {
try {
// do something before the route handler
const response = await handler(event)
// do something after the route handler
return { response }
} catch (err) {
// Error handling
return { err }
}
})
```
```ts [server/api/hello.get.ts]
export default defineWrappedResponseHandler(event => 'hello world')
```
## Server Alias
You can use the `#server` alias to import files from anywhere within the `server/` directory, regardless of the importing file's location.
```ts [server/api/users/[id]/profile.ts]
// Instead of relative paths like this:
// import { formatUser } from '../../../utils/formatUser'
// Use the #server alias:
import { formatUser } from '#server/utils/formatUser'
```
This alias ensures consistent imports across your server code, especially useful in deeply nested route handlers.
::note
The `#server` alias can only be used within the `server/` directory. Importing from `#server` in client code will result in an error.
::
## Server Types
Auto-imports and other types are different for the `server/` directory, as it is running in a different context from the `app/` directory.
By default, Nuxt 4 generates a [`tsconfig.json`](/docs/4.x/directory-structure/tsconfig) which includes a project reference covering the `server/` folder which ensures accurate typings.
## Recipes
### Route Parameters
Server routes can use dynamic parameters within brackets in the file name like `/api/hello/[name].ts` and be accessed via `event.context.params`.
```ts [server/api/hello/[name\\].ts]
export default defineEventHandler((event) => {
const name = getRouterParam(event, 'name')
return `Hello, ${name}!`
})
```
::tip{to="https://h3.dev/examples/validate-data#validate-params"}
Alternatively, use `getValidatedRouterParams` with a schema validator such as Zod for runtime and type safety.
::
You can now universally call this API on `/api/hello/nuxt` and get `Hello, nuxt!`.
### Matching HTTP Method
Handle file names can be suffixed with `.get`, `.post`, `.put`, `.delete`, ... to match request's [HTTP Method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods).
```ts [server/api/test.get.ts]
export default defineEventHandler(() => 'Test get handler')
```
```ts [server/api/test.post.ts]
export default defineEventHandler(() => 'Test post handler')
```
Given the example above, fetching `/test` with:
- **GET** method: Returns `Test get handler`
- **POST** method: Returns `Test post handler`
- Any other method: Returns 405 error
You can also use `index.[method].ts` inside a directory for structuring your code differently, this is useful to create API namespaces.
::code-group
```ts [server/api/foo/index.get.ts]
export default defineEventHandler((event) => {
// handle GET requests for the `api/foo` endpoint
})
```
```ts [server/api/foo/index.post.ts]
export default defineEventHandler((event) => {
// handle POST requests for the `api/foo` endpoint
})
```
```ts [server/api/foo/bar.get.ts]
export default defineEventHandler((event) => {
// handle GET requests for the `api/foo/bar` endpoint
})
```
::
### Catch-all Route
Catch-all routes are helpful for fallback route handling.
For example, creating a file named `~~/server/api/foo/[...].ts` will register a catch-all route for all requests that do not match any route handler, such as `/api/foo/bar/baz`.
```ts [server/api/foo/[...\\].ts]
export default defineEventHandler((event) => {
// event.context.path to get the route path: '/api/foo/bar/baz'
// event.context.params._ to get the route segment: 'bar/baz'
return `Default foo handler`
})
```
You can set a name for the catch-all route by using `~~/server/api/foo/[...slug].ts` and access it via `event.context.params.slug`.
```ts [server/api/foo/[...slug\\].ts]
export default defineEventHandler((event) => {
// event.context.params.slug to get the route segment: 'bar/baz'
return `Default foo handler`
})
```
### Body Handling
```ts [server/api/submit.post.ts]
export default defineEventHandler(async (event) => {
const body = await readBody(event)
return { body }
})
```
::tip{to="https://unjs.io/blog/2023-08-15-h3-towards-the-edge-of-the-web/#runtime-type-safe-request-utils"}
Alternatively, use `readValidatedBody` with a schema validator such as Zod for runtime and type safety.
::
You can now universally call this API using:
```vue [app/app.vue]
<script setup lang="ts">
async function submit () {
const { body } = await $fetch('/api/submit', {
method: 'post',
body: { test: 123 },
})
}
</script>
```
::note
We are using `submit.post.ts` in the filename only to match requests with `POST` method that can accept the request body. When using `readBody` within a GET request, `readBody` will throw a `405 Method Not Allowed` HTTP error.
::
### Query Parameters
Sample query `/api/query?foo=bar&baz=qux`
```ts [server/api/query.get.ts]
export default defineEventHandler((event) => {
const query = getQuery(event)
return { a: query.foo, b: query.baz }
})
```
::tip{to="https://unjs.io/blog/2023-08-15-h3-towards-the-edge-of-the-web#runtime-type-safe-request-utils"}
Alternatively, use `getValidatedQuery` with a schema validator such as Zod for runtime and type safety.
::
### Error Handling
If no errors are thrown, a status code of `200 OK` will be returned.
Any uncaught errors will return a `500 Internal Server Error` HTTP Error.
To return other error codes, throw an exception with [`createError`](/docs/4.x/api/utils/create-error):
```ts [server/api/validation/[id\\].ts]
export default defineEventHandler((event) => {
const id = Number.parseInt(event.context.params.id) as number
if (!Number.isInteger(id)) {
throw createError({
status: 400,
statusText: 'ID should be an integer',
})
}
return 'All good'
})
```
### Status Codes
To return other status codes, use the [`setResponseStatus`](/docs/4.x/api/utils/set-response-status) utility.
For example, to return `202 Accepted`
```ts [server/api/validation/[id\\].ts]
export default defineEventHandler((event) => {
setResponseStatus(event, 202)
})
```
### Runtime Config
::code-group
```ts [server/api/foo.ts]
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig()
const repo = await $fetch('https://api.github.com/repos/nuxt/nuxt', {
headers: {
Authorization: `token ${config.githubToken}`,
},
})
return repo
})
```
```ts [nuxt.config.ts]
export default defineNuxtConfig({
runtimeConfig: {
githubToken: '',
},
})
```
```ini [.env]
NUXT_GITHUB_TOKEN='<my-super-token>'
```
::
::note
Giving the `event` as argument to `useRuntimeConfig` is optional, but it is recommended to pass it to get the runtime config overwritten by [environment variables](/docs/4.x/guide/going-further/runtime-config#environment-variables) at runtime for server routes.
::
### Request Cookies
```ts [server/api/cookies.ts]
export default defineEventHandler((event) => {
const cookies = parseCookies(event)
return { cookies }
})
```
### Forwarding Context & Headers
By default, neither the headers from the incoming request nor the request context are forwarded when
making fetch requests in server routes. You can use `event.$fetch` to forward the request context and headers when making fetch requests in server routes.
```ts [server/api/forward.ts]
export default defineEventHandler((event) => {
return event.$fetch('/api/forwarded')
})
```
::note
Headers that are **not meant to be forwarded** will **not be included** in the request. These headers include, for example:
`transfer-encoding`, `connection`, `keep-alive`, `upgrade`, `expect`, `host`, `accept`
::
### Awaiting Promises After Response
When handling server requests, you might need to perform asynchronous tasks that shouldn't block the response to the client (for example, caching and logging). You can use `event.waitUntil` to await a promise in the background without delaying the response.
The `event.waitUntil` method accepts a promise that will be awaited before the handler terminates, ensuring the task is completed even if the server would otherwise terminate the handler right after the response is sent. This integrates with runtime providers to leverage their native capabilities for handling asynchronous operations after the response is sent.
```ts [server/api/background-task.ts]
const timeConsumingBackgroundTask = async () => {
await new Promise(resolve => setTimeout(resolve, 1000))
}
export default eventHandler((event) => {
// schedule a background task without blocking the response
event.waitUntil(timeConsumingBackgroundTask())
// immediately send the response to the client
return 'done'
})
```
## Advanced Usage
### Nitro Config
You can use `nitro` key in `nuxt.config` to directly set [Nitro configuration](https://nitro.build/config).
::warning
This is an advanced option. Custom config can affect production deployments, as the configuration interface might change over time when Nitro is upgraded in semver-minor versions of Nuxt.
::
```ts [nuxt.config.ts]
export default defineNuxtConfig({
// https://nitro.build/config
nitro: {},
})
```
:read-more{to="/docs/4.x/guide/concepts/server-engine"}
### Nested Router
```ts [server/api/hello/[...slug\\].ts]
import { createRouter, defineEventHandler, useBase } from 'h3'
const router = createRouter()
router.get('/test', defineEventHandler(() => 'Hello World'))
export default useBase('/api/hello', router.handler)
```
### Sending Streams
::tip
This is an experimental feature and is available in all environments.
::
```ts [server/api/foo.get.ts]
import fs from 'node:fs'
import { sendStream } from 'h3'
export default defineEventHandler((event) => {
return sendStream(event, fs.createReadStream('/path/to/file'))
})
```
### Sending Redirect
```ts [server/api/foo.get.ts]
export default defineEventHandler(async (event) => {
await sendRedirect(event, '/path/redirect/to', 302)
})
```
### Legacy Handler or Middleware
```ts [server/api/legacy.ts]
export default fromNodeMiddleware((req, res) => {
res.end('Legacy handler')
})
```
::important
Legacy support is possible using [h3js/h3](https://github.com/h3js/h3), but it is advised to avoid legacy handlers as much as you can.
::
```ts [server/middleware/legacy.ts]
export default fromNodeMiddleware((req, res, next) => {
console.log('Legacy middleware')
next()
})
```
::warning
Never combine `next()` callback with a legacy middleware that is `async` or returns a `Promise`.
::
### Server Storage
Nitro provides a cross-platform [storage layer](https://nitro.build/guide/storage). In order to configure additional storage mount points, you can use `nitro.storage`, or [server plugins](/docs/4.x/directory-structure/server#server-plugins).
**Example of adding a Redis storage:**
Using `nitro.storage`:
```ts [nuxt.config.ts]
export default defineNuxtConfig({
nitro: {
storage: {
redis: {
driver: 'redis',
/* redis connector options */
port: 6379, // Redis port
host: '127.0.0.1', // Redis host
username: '', // needs Redis >= 6
password: '',
db: 0, // Defaults to 0
tls: {}, // tls/ssl
},
},
},
})
```
Then in your API handler:
```ts [server/api/storage/test.ts]
export default defineEventHandler(async (event) => {
// List all keys with
const keys = await useStorage('redis').getKeys()
// Set a key with
await useStorage('redis').setItem('foo', 'bar')
// Remove a key with
await useStorage('redis').removeItem('foo')
return {}
})
```
::read-more{to="https://nitro.build/guide/storage" target="_blank"}
Read more about Nitro Storage Layer.
::
Alternatively, you can create a storage mount point using a server plugin and runtime config:
::code-group
```ts [server/plugins/storage.ts]
import { definePlugin } from 'nitro'
import redisDriver from 'unstorage/drivers/redis'
export default definePlugin(() => {
const storage = useStorage()
// Dynamically pass in credentials from runtime configuration, or other sources
const driver = redisDriver({
base: 'redis',
host: useRuntimeConfig().redis.host,
port: useRuntimeConfig().redis.port,
/* other redis connector options */
})
// Mount driver
storage.mount('redis', driver)
})
```
``` ts [nuxt.config.ts]
export default defineNuxtConfig({
runtimeConfig: {
redis: { // Default values
host: '',
port: 0,
/* other redis connector options */
},
},
})
```
::

View File

@@ -0,0 +1,98 @@
---
title: 'shared'
head.title: 'shared/'
description: 'Use the shared/ directory to share functionality between the Vue app and the Nitro server.'
navigation.icon: i-vscode-icons-folder-type-shared
---
The `shared/` directory allows you to share code that can be used in both the Vue app and the Nitro server.
::note
The `shared/` directory is available in Nuxt v3.14+.
::
::important
Code in the `shared/` directory cannot import any Vue or Nitro code.
::
:video-accordion{title="Watch a video from Vue School on sharing utils and types between app and server" videoId="nnAR-MO3q5M"}
## Usage
**Method 1:** Named export
```ts twoslash [shared/utils/capitalize.ts]
export const capitalize = (input: string) => {
return input[0] ? input[0].toUpperCase() + input.slice(1) : ''
}
```
**Method 2:** Default export
```ts twoslash [shared/utils/capitalize.ts]
export default function (input: string) {
return input[0] ? input[0].toUpperCase() + input.slice(1) : ''
}
```
You can now use [auto-imported](/docs/4.x/directory-structure/shared) utilities in your Nuxt app and `server/` directory.
```vue [app/app.vue]
<script setup lang="ts">
const hello = capitalize('hello')
</script>
<template>
<div>
{{ hello }}
</div>
</template>
```
```ts [server/api/hello.get.ts]
export default defineEventHandler((event) => {
return {
hello: capitalize('hello'),
}
})
```
## How Files Are Scanned
Only files in the `shared/utils/` and `shared/types/` directories will be auto-imported. Files nested within subdirectories of these directories will not be auto-imported unless you add these directories to `imports.dirs` and `nitro.imports.dirs`.
::tip
The way `shared/utils` and `shared/types` auto-imports work and are scanned is identical to the [`app/composables/`](/docs/4.x/directory-structure/app/composables) and [`app/utils/`](/docs/4.x/directory-structure/app/utils) directories.
::
:read-more{to="/docs/4.x/directory-structure/app/composables#how-files-are-scanned"}
```bash [Directory Structure]
-| shared/
---| capitalize.ts # Not auto-imported
---| formatters
-----| lower.ts # Not auto-imported
---| utils/
-----| lower.ts # Auto-imported
-----| formatters
-------| upper.ts # Not auto-imported
---| types/
-----| bar.ts # Auto-imported
```
Any other files you create in the `shared/` folder must be manually imported using the `#shared` alias (automatically configured by Nuxt):
```ts
// For files directly in the shared directory
import capitalize from '#shared/capitalize'
// For files in nested directories
import lower from '#shared/formatters/lower'
// For files nested in a folder within utils
import upper from '#shared/utils/formatters/upper'
```
This alias ensures consistent imports across your application, regardless of the importing file's location.
:read-more{to="/docs/4.x/guide/concepts/auto-imports"}

View File

@@ -0,0 +1,79 @@
---
title: ".env"
description: "A .env file specifies your build/dev-time environment variables."
head.title: ".env"
navigation.icon: i-vscode-icons-file-type-dotenv
---
::important
This file should be added to your [`.gitignore`](/docs/4.x/directory-structure/gitignore) file to avoid pushing secrets to your repository.
::
## Dev, Build and Generate Time
Nuxt CLI has built-in [dotenv](https://github.com/motdotla/dotenv) support in development mode and when running [`nuxt build`](/docs/4.x/api/commands/build) and [`nuxt generate`](/docs/4.x/api/commands/generate).
In addition to any process environment variables, if you have a `.env` file in your project root directory, it will be automatically loaded **at dev, build and generate time**. Any environment variables set there will be accessible within your `nuxt.config` file and modules.
```ini [.env]
MY_ENV_VARIABLE=hello
```
::note
Note that removing a variable from `.env` or removing the `.env` file entirely will not unset values that have already been set.
::
## Custom File
If you want to use a different file - for example, to use `.env.local` or `.env.production` - you can do so by passing the `--dotenv` flag when using the Nuxt CLI.
```bash [Terminal]
npx nuxt dev --dotenv .env.local
```
When updating `.env` in development mode, the Nuxt instance is automatically restarted to apply new values to the `process.env`.
::important
In your application code, you should use [Runtime Config](/docs/4.x/guide/going-further/runtime-config) instead of plain env variables.
::
## Production
**After your server is built**, you are responsible for setting environment variables when you run the server.
Your `.env` files will not be read at this point. How you do this is different for every environment.
This design decision was made to ensure compatibility across various deployment environments, some of which may not have a traditional file system available, such as serverless platforms or edge networks like Cloudflare Workers.
Since `.env` files are not used in production, you must explicitly set environment variables using the tools and methods provided by your hosting environment. Here are some common approaches:
* You can pass the environment variables as arguments using the terminal:
`$ NODE_ENV=production DATABASE_HOST=mydatabaseconnectionstring node .output/server/index.mjs`
* You can set environment variables in shell configuration files like `.bashrc` or `.profile`.
* Many cloud service providers, such as Vercel, Netlify, and AWS, provide interfaces for setting environment variables via their dashboards, CLI tools or configuration files.
::important
`runtimeConfig` [won't pick up environment variables that don't start with `NUXT_` in production] (https://nuxt.com/docs/4.x/guide/going-further/runtime-config#environment-variables).
::
## Production Preview
For local production preview purpose, we recommend using [`nuxt preview`](/docs/4.x/api/commands/preview) since using this command, the `.env` file will be loaded into `process.env` for convenience. Note that this command requires dependencies to be installed in the package directory.
Or you could pass the environment variables as arguments using the terminal. For example, on Linux or macOS:
```bash [Terminal]
NODE_ENV=production DATABASE_HOST=mydatabaseconnectionstring node .output/server/index.mjs
```
Note that for a purely static site, it is not possible to set runtime configuration config after your project is prerendered.
:read-more{to="/docs/4.x/guide/going-further/runtime-config"}
::note
If you want to use environment variables set at build time but do not care about updating these down the line (or only need to update them reactively _within_ your app) then `appConfig` may be a better choice. You can define `appConfig` both within your `nuxt.config` (using environment variables) and also within an `~/app.config.ts` file in your project.
:read-more{to="/docs/4.x/directory-structure/app/app-config"}
::

View File

@@ -0,0 +1,37 @@
---
title: ".gitignore"
description: "A .gitignore file specifies intentionally untracked files that git should ignore."
head.title: ".gitignore"
navigation.icon: i-vscode-icons-file-type-git
---
A `.gitignore` file specifies intentionally untracked files that git should ignore.
:read-more{icon="i-simple-icons-git" title="the git documentation" to="https://git-scm.com/docs/gitignore" target="_blank"}
We recommend having a `.gitignore` file that has **at least** the following entries present:
```bash [.gitignore]
# Nuxt dev/build outputs
.output
.data
.nuxt
.nitro
.cache
dist
# Node dependencies
node_modules
# Logs
logs
*.log
# Misc
.DS_Store
# Local env files
.env
.env.*
!.env.example
```

View File

@@ -0,0 +1,37 @@
---
title: .nuxtignore
head.title: '.nuxtignore'
description: The .nuxtignore file lets Nuxt ignore files in your projects root directory during the build phase.
navigation.icon: i-vscode-icons-file-type-nuxt
---
The `.nuxtignore` file tells Nuxt to ignore files in your projects root directory ([`rootDir`](/docs/4.x/api/nuxt-config#rootdir)) during the build phase.
It is subject to the same specification as [`.gitignore`](/docs/4.x/directory-structure/gitignore) and `.eslintignore` files, in which each line is a glob pattern indicating which files should be ignored.
::tip
You can also configure [`ignoreOptions`](/docs/4.x/api/nuxt-config#ignoreoptions), [`ignorePrefix`](/docs/4.x/api/nuxt-config#ignoreprefix) and [`ignore`](/docs/4.x/api/nuxt-config#ignore) in your `nuxt.config` file.
::
## Usage
```bash [.nuxtignore]
# ignore layout foo.vue
app/layouts/foo.vue
# ignore layout files whose name ends with -ignore.vue
app/layouts/*-ignore.vue
# ignore page bar.vue
app/pages/bar.vue
# ignore page inside ignore folder
app/pages/ignore/*.vue
# ignore route middleware files under foo folder except foo/bar.js
app/middleware/foo/*.js
!app/middleware/foo/bar.js
```
::read-more{icon="i-simple-icons-git" title="the git documentation" to="https://git-scm.com/docs/gitignore" target="_blank"}
More details about the spec are in the **gitignore documentation**.
::

View File

@@ -0,0 +1,57 @@
---
title: ".nuxtrc"
description: "The .nuxtrc file allows you to define nuxt configurations in a flat syntax."
head.title: ".nuxtrc"
navigation.icon: i-vscode-icons-file-type-nuxt
---
The `.nuxtrc` file can be used to configure Nuxt with a flat syntax. It is based on [`unjs/rc9`](https://github.com/unjs/rc9).
::tip
For more advanced configurations, use [`nuxt.config`](/docs/4.x/directory-structure/nuxt-config).
::
## Usage
```bash [.nuxtrc]
# Disable SSR
ssr=false
# Configuration for `@nuxt/devtools`
devtools.enabled=true
# Add Nuxt modules
modules[]=@nuxt/image
modules[]=nuxt-security
# Module setups (automatically added by Nuxt)
setups.@nuxt/test-utils="3.23.0"
```
If present, the properties in the `nuxt.config` file will overwrite the properties in `.nuxtrc` file.
::note
Nuxt automatically adds a `setups` section to track module installation and upgrade state. This is used internally for [module lifecycle hooks](/docs/4.x/api/kit/modules#using-lifecycle-hooks-for-module-installation-and-upgrade) and should not be modified manually.
::
::read-more{to="/docs/4.x/api/configuration/nuxt-config"}
Discover all the available options in the **Nuxt configuration** documentation.
::
## Global `.nuxtrc` File
You can also create a global `.nuxtrc` file in your home directory to apply configurations globally.
- On macOS/Linux, this file is located at:
```md
~/.nuxtrc
```
- On Windows, it is located at:
```md
C:\Users\{username}\.nuxtrc
```
This global `.nuxtrc` file allows you to define default settings that apply to all Nuxt projects on your system. However, project-level `.nuxtrc` files will override these global settings, and `nuxt.config` will take precedence over both.

View File

@@ -0,0 +1,34 @@
---
title: "nuxt.config.ts"
description: "Nuxt can be easily configured with a single nuxt.config file."
head.title: "nuxt.config.ts"
navigation.icon: i-vscode-icons-file-type-nuxt
---
The `nuxt.config` file extension can either be `.js`, `.ts` or `.mjs`.
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
// My Nuxt config
})
```
::tip
`defineNuxtConfig` helper is globally available without import.
::
You can explicitly import `defineNuxtConfig` from `nuxt/config` if you prefer:
```ts twoslash [nuxt.config.ts]
import { defineNuxtConfig } from 'nuxt/config'
export default defineNuxtConfig({
// My Nuxt config
})
```
::read-more{to="/docs/4.x/api/configuration/nuxt-config"}
Discover all the available options in the **Nuxt configuration** documentation.
::
To ensure your configuration is up to date, Nuxt will make a full restart when detecting changes in the main configuration file, the [`.env`](/docs/4.x/directory-structure/env), [`.nuxtignore`](/docs/4.x/directory-structure/nuxtignore) and [`.nuxtrc`](/docs/4.x/directory-structure/nuxtrc) dotfiles.

View File

@@ -0,0 +1,32 @@
---
title: package.json
head.title: package.json
description: The package.json file contains all the dependencies and scripts for your application.
navigation.icon: i-vscode-icons-file-type-npm
---
The minimal `package.json` of your Nuxt application should looks like:
```json [package.json]
{
"name": "nuxt-app",
"private": true,
"type": "module",
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare"
},
"dependencies": {
"nuxt": "latest",
"vue": "latest",
"vue-router": "latest"
}
}
```
::read-more{icon="i-simple-icons-npm" to="https://docs.npmjs.com/cli/configuring-npm/package-json/" target="_blank"}
Read more about the `package.json` file.
::

View File

@@ -0,0 +1,70 @@
---
title: "tsconfig.json"
description: "Learn how Nuxt manages TypeScript configuration across different parts of your project."
head.title: "tsconfig.json"
navigation.icon: i-vscode-icons-file-type-tsconfig
---
Nuxt [automatically generates](/docs/4.x/guide/concepts/typescript#auto-generated-types) multiple TypeScript configuration files (`.nuxt/tsconfig.app.json`, `.nuxt/tsconfig.server.json`, `.nuxt/tsconfig.node.json` and `.nuxt/tsconfig.shared.json`) that include recommended basic TypeScript configuration for your project, references to [auto-imports](/docs/4.x/guide/concepts/auto-imports), [API route types](/docs/4.x/guide/concepts/server-engine#typed-api-routes), path aliases, and more.
Your Nuxt project should include the following `tsconfig.json` file at the root of the project:
```json [tsconfig.json]
{
"files": [],
"references": [
{
"path": "./.nuxt/tsconfig.app.json"
},
{
"path": "./.nuxt/tsconfig.server.json"
},
{
"path": "./.nuxt/tsconfig.shared.json"
},
{
"path": "./.nuxt/tsconfig.node.json"
}
]
}
```
::warning
We do not recommend modifying the contents of this file directly, as doing so could overwrite important settings that Nuxt or other modules rely on. Instead, extend it via `nuxt.config.ts`.
::
::read-more{to="/docs/4.x/guide/concepts/typescript#project-references"}
Read more about the different type contexts of a Nuxt project here.
::
## Extending TypeScript Configuration
You can customize the TypeScript configuration of your Nuxt project for each context (`app`, `shared`, `node`, and `server`) in the `nuxt.config.ts` file.
<!-- @case-police-ignore tsConfig -->
```ts twoslash [nuxt.config.ts]
// @errors: 2353
export default defineNuxtConfig({
typescript: {
// customize tsconfig.app.json
tsConfig: {
// ...
},
// customize tsconfig.shared.json
sharedTsConfig: {
// ...
},
// customize tsconfig.node.json
nodeTsConfig: {
// ...
},
},
nitro: {
typescript: {
// customize tsconfig.server.json
tsConfig: {
// ...
},
},
},
})
```

View File

@@ -0,0 +1,65 @@
---
title: 'Nuxt Directory Structure'
description: 'Learn about the directory structure of a Nuxt application and how to use it.'
navigation: false
---
Nuxt applications have a specific directory structure that is used to organize the code. This structure is designed to be easy to understand and to be used in a consistent way.
## Root Directory
The root directory of a Nuxt application is the directory that contains the `nuxt.config.ts` file. This file is used to configure the Nuxt application.
## App Directory
The `app/` directory is the main directory of the Nuxt application. It contains the following subdirectories:
- [`assets/`](/docs/4.x/directory-structure/app/assets): website's assets that the build tool (Vite or webpack) will process
- [`components/`](/docs/4.x/directory-structure/app/components): Vue components of the application
- [`composables/`](/docs/4.x/directory-structure/app/composables): add your Vue composables
- [`layouts/`](/docs/4.x/directory-structure/app/layouts): Vue components that wrap around your pages and avoid re-rendering between pages
- [`middleware/`](/docs/4.x/directory-structure/app/middleware): run code before navigating to a particular route
- [`pages/`](/docs/4.x/directory-structure/app/pages): file-based routing to create routes within your web application
- [`plugins/`](/docs/4.x/directory-structure/app/plugins): use Vue plugins and more at the creation of your Nuxt application
- [`utils/`](/docs/4.x/directory-structure/app/utils): add functions throughout your application that can be used in your components, composables, and pages.
This directory also includes specific files:
- [`app.config.ts`](/docs/4.x/directory-structure/app/app-config): a reactive configuration within your application
- [`app.vue`](/docs/4.x/directory-structure/app/app): the root component of your Nuxt application
- [`error.vue`](/docs/4.x/directory-structure/app/error): the error page of your Nuxt application
## Public Directory
The [`public/`](/docs/4.x/directory-structure/public) directory is the directory that contains the public files of the Nuxt application. Files contained within this directory are served at the root and are not modified by the build process.
This is suitable for files that have to keep their names (e.g. `robots.txt`) _or_ likely won't change (e.g. `favicon.ico`).
## Server Directory
The [`server/`](/docs/4.x/directory-structure/server) directory is the directory that contains the server-side code of the Nuxt application. It contains the following subdirectories:
- [`api/`](/docs/4.x/directory-structure/server#server-routes): contains the API routes of the application.
- [`routes/`](/docs/4.x/directory-structure/server#server-routes): contains the server routes of the application (e.g. dynamic `/sitemap.xml`).
- [`middleware/`](/docs/4.x/directory-structure/server#server-middleware): run code before a server route is processed
- [`plugins/`](/docs/4.x/directory-structure/server#server-plugins): use plugins and more at the creation of the Nuxt server
- [`utils/`](/docs/4.x/directory-structure/server#server-utilities): add functions throughout your application that can be used in your server code.
## Shared Directory
The [`shared/`](/docs/4.x/directory-structure/shared) directory is the directory that contains the shared code of the Nuxt application and Nuxt server. This code can be used in both the Vue app and the Nitro server.
## Content Directory
The [`content/`](/docs/4.x/directory-structure/content) directory is enabled by the [Nuxt Content](https://content.nuxt.com) module. It is used to create a file-based CMS for your application using Markdown files.
## Modules Directory
The [`modules/`](/docs/4.x/directory-structure/modules) directory is the directory that contains the local modules of the Nuxt application. Modules are used to extend the functionality of the Nuxt application.
## Layers Directory
The [`layers/`](/docs/4.x/directory-structure/layers) directory allows you to organize and share reusable code, components, composables, and configurations. Layers within this directory are automatically registered in your project.
## Nuxt Files
- [`nuxt.config.ts`](/docs/4.x/directory-structure/nuxt-config) file is the main configuration file for the Nuxt application.
- [`.nuxtrc`](/docs/4.x/directory-structure/nuxtrc) file is another syntax for configuring the Nuxt application (useful for global configurations).
- [`.nuxtignore`](/docs/4.x/directory-structure/nuxtignore) file is used to ignore files in the root directory during the build phase.

View File

@@ -0,0 +1,2 @@
title: 'Guide'
icon: i-lucide-book-open

View File

@@ -0,0 +1,28 @@
---
title: 'Nuxt Guide'
titleTemplate: '%s'
description: 'Learn how Nuxt works with in-depth guides.'
navigation: false
surround: false
---
::card-group{class="sm:grid-cols-1"}
::card{icon="i-lucide-medal" title="Key Concepts" to="/docs/4.x/guide/concepts"}
Discover the main concepts behind Nuxt, from auto-import, hybrid rendering to its TypeScript support.
::
::card{icon="i-lucide-square-check" title="Best Practices" to="/docs/4.x/guide/best-practices"}
Learn about best practices when developing with Nuxt.
::
::card{icon="i-lucide-bot" title="Working with AI" to="/docs/4.x/guide/ai"}
Integrate AI tools into your Nuxt workflow with MCP Server and LLMs.txt.
::
::card{icon="i-lucide-box" title="Module Author Guide" to="/docs/4.x/guide/modules"}
Learn how to create Nuxt modules to integrate, enhance or extend any Nuxt application.
::
::card{icon="i-lucide-book-open" title="Recipes" to="/docs/4.x/guide/recipes"}
Find solutions to common problems and learn how to implement them in your Nuxt project.
::
::card{icon="i-lucide-star" title="Going Further" to="/docs/4.x/guide/going-further"}
Master Nuxt with advanced concepts like experimental features, hooks, and more.
::
::

View File

@@ -0,0 +1,3 @@
title: Key Concepts
titleTemplate: '%s · Nuxt Concepts'
icon: i-lucide-medal

View File

@@ -0,0 +1,236 @@
---
title: 'Rendering Modes'
description: 'Learn about the different rendering modes available in Nuxt.'
---
Nuxt supports different rendering modes, [universal rendering](/docs/4.x/guide/concepts/rendering#universal-rendering), [client-side rendering](/docs/4.x/guide/concepts/rendering#client-side-rendering) but also offers [hybrid-rendering](/docs/4.x/guide/concepts/rendering#hybrid-rendering) and the possibility to render your application on [CDN Edge Servers](/docs/4.x/guide/concepts/rendering#edge-side-rendering).
Both the browser and server can interpret JavaScript code to turn Vue.js components into HTML elements. This step is called **rendering**. Nuxt supports both **universal** and **client-side** rendering. The two approaches have benefits and downsides that we will cover.
By default, Nuxt uses **universal rendering** to provide better user experience, performance and to optimize search engine indexing, but you can switch rendering modes in [one line of configuration](/docs/4.x/api/nuxt-config#ssr).
## Universal Rendering
This step is similar to traditional **server-side rendering** performed by PHP or Ruby applications. When the browser requests a URL with universal rendering enabled, Nuxt runs the JavaScript (Vue.js) code in a server environment and returns a fully rendered HTML page to the browser. Nuxt may also return a fully rendered HTML page from a cache if the page was generated in advance. Users immediately get the entirety of the initial content of the application, contrary to client-side rendering.
Once the HTML document has been downloaded, the browser interprets this and Vue.js takes control of the document. The same JavaScript code that once ran on the server runs on the client (browser) **again** in the background now enabling interactivity (hence **Universal rendering**) by binding its listeners to the HTML. This is called **Hydration**. When hydration is complete, the page can enjoy benefits such as dynamic interfaces and page transitions.
Universal rendering allows a Nuxt application to provide quick page load times while preserving the benefits of client-side rendering. Furthermore, as the content is already present in the HTML document, crawlers can index it without overhead.
![Users can access the static content when the HTML document is loaded. Hydration then allows page's interactivity](/assets/docs/concepts/rendering/ssr.svg)
**What's server-rendered and what's client-rendered?**
It is normal to ask which parts of a Vue file runs on the server and/or the client in universal rendering mode.
```vue [app/app.vue]
<script setup lang="ts">
const counter = ref(0) // executes in server and client environments
const handleClick = () => {
counter.value++ // executes only in a client environment
}
</script>
<template>
<div>
<p>Count: {{ counter }}</p>
<button @click="handleClick">
Increment
</button>
</div>
</template>
```
On the initial request, the `counter` ref is initialized in the server since it is rendered inside the `<p>` tag. The contents of `handleClick` is never executed here. During hydration in the browser, the `counter` ref is re-initialized. The `handleClick` finally binds itself to the button; Therefore it is reasonable to deduce that the body of `handleClick` will always run in a browser environment.
[Middlewares](/docs/4.x/directory-structure/app/middleware) and [pages](/docs/4.x/directory-structure/app/pages) run in the server and on the client during hydration. [Plugins](/docs/4.x/directory-structure/app/plugins) can be rendered on the server or client or both. [Components](/docs/4.x/directory-structure/app/components) can be forced to run on the client only as well. [Composables](/docs/4.x/directory-structure/app/composables) and [utilities](/docs/4.x/directory-structure/app/utils) are rendered based on the context of their usage.
**Benefits of server-side rendering:**
- **Performance**: Users can get immediate access to the page's content because browsers can display static content much faster than JavaScript-generated content. At the same time, Nuxt preserves the interactivity of a web application during the hydration process.
- **Search Engine Optimization**: Universal rendering delivers the entire HTML content of the page to the browser as a classic server application. Web crawlers can directly index the page's content, which makes Universal rendering a great choice for any content that you want to index quickly.
**Downsides of server-side rendering:**
- **Development constraints:** Server and browser environments don't provide the same APIs, and it can be tricky to write code that can run on both sides seamlessly. Fortunately, Nuxt provides guidelines and specific variables to help you determine where a piece of code is executed.
- **Cost:** A server needs to be running in order to render pages on the fly. This adds a monthly cost like any traditional server. However, the server calls are highly reduced thanks to universal rendering with the browser taking over on client-side navigation. A cost reduction is possible by leveraging [edge-side-rendering](/docs/4.x/guide/concepts/rendering#edge-side-rendering).
Universal rendering is very versatile and can fit almost any use case, and is especially appropriate for any content-oriented websites: **blogs, marketing websites, portfolios, e-commerce sites, and marketplaces.**
::tip
For more examples about writing Vue code without hydration mismatch, see [the Vue docs](https://vuejs.org/guide/scaling-up/ssr#hydration-mismatch).
::
::important
When importing a library that relies on browser APIs and has side effects, make sure the component importing it is only called client-side. Bundlers do not treeshake imports of modules containing side effects.
::
## Client-Side Rendering
Out of the box, a traditional Vue.js application is rendered in the browser (or **client**). Then, Vue.js generates HTML elements after the browser downloads and parses all the JavaScript code containing the instructions to create the current interface.
![Users have to wait for the browser to download, parse and execute the JavaScript before seeing the page's content](/assets/docs/concepts/rendering/csr.svg)
**Benefits of client-side rendering:**
- **Development speed**: When working entirely on the client-side, we don't have to worry about the server compatibility of the code, for example, by using browser-only APIs like the `window` object.
- **Cheaper:** Running a server adds a cost of infrastructure as you would need to run on a platform that supports JavaScript. We can host client-only applications on any static server with HTML, CSS, and JavaScript files.
- **Offline:** Because code entirely runs in the browser, it can nicely keep working while the internet is unavailable.
**Downsides of client-side rendering:**
- **Performance**: The user has to wait for the browser to download, parse and run JavaScript files. Depending on the network for the download part and the user's device for the parsing and execution, this can take some time and impact the user's experience.
- **Search Engine Optimization**: Indexing and updating the content delivered via client-side rendering takes more time than with a server-rendered HTML document. This is related to the performance drawback we discussed, as search engine crawlers won't wait for the interface to be fully rendered on their first try to index the page. Your content will take more time to show and update in search results pages with pure client-side rendering.
Client-side rendering is a good choice for heavily interactive **web applications** that don't need indexing or whose users visit frequently. It can leverage browser caching to skip the download phase on subsequent visits, such as **SaaS, back-office applications, or online games**.
You can enable client-side only rendering with Nuxt in your `nuxt.config.ts`:
```ts [nuxt.config.ts]
export default defineNuxtConfig({
ssr: false,
})
```
::note
If you do use `ssr: false`, you should also place an HTML file in `~/spa-loading-template.html` with some HTML you would like to use to render a loading screen that will be rendered until your app is hydrated.
:read-more{title="SPA Loading Template" to="/docs/4.x/api/configuration/nuxt-config#spaloadingtemplate"}
::
:video-accordion{title="Watch a video from Alexander Lichter about Building a plain SPA with Nuxt" videoId="7Lr0QTP1Ro8"}
### Deploying a Static Client-Rendered App
If you deploy your app to [static hosting](/docs/4.x/getting-started/deployment#static-hosting) with the `nuxt generate` or `nuxt build --prerender` commands, then by default, Nuxt will render every page as a separate static HTML file.
::warning
If you prerender your app with the `nuxt generate` or `nuxt build --prerender` commands, then you will not be able to use any server endpoints as no server will be included in your output folder. If you need server functionality, use `nuxt build` instead.
::
If you are using purely client-side rendering, then this might be unnecessary. You might only need a single `index.html` file, plus `200.html` and `404.html` fallbacks, which you can tell your static web host to serve up for all requests.
In order to achieve this we can change how the routes are prerendered. Just add this to [your hooks](/docs/4.x/api/advanced/hooks#nuxt-hooks-build-time) in your `nuxt.config.ts`:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
hooks: {
'prerender:routes' ({ routes }) {
routes.clear() // Do not generate any routes (except the defaults)
},
},
})
```
This will produce three files:
- `index.html`
- `200.html`
- `404.html`
The `200.html` and `404.html` might be useful for the hosting provider you are using.
#### Skipping Client Fallback Generation
When prerendering a client-rendered app, Nuxt will generate `index.html`, `200.html` and `404.html` files by default. However, if you need to prevent any (or all) of these files from being generated in your build, you can use the `'prerender:generate'` hook from [Nitro](/docs/4.x/getting-started/prerendering#prerendergenerate-nitro-hook).
```ts twoslash [nuxt.config.ts]
// @errors: 2353 7006
export default defineNuxtConfig({
ssr: false,
nitro: {
hooks: {
'prerender:generate' (route) {
const routesToSkip = ['/index.html', '/200.html', '/404.html']
if (routesToSkip.includes(route.route)) {
route.skip = true
}
},
},
},
})
```
## Hybrid Rendering
Hybrid rendering allows different caching rules per route using **Route Rules** and decides how the server should respond to a new request on a given URL.
Previously every route/page of a Nuxt application and server must use the same rendering mode, universal or client-side. In various cases, some pages could be generated at build time, while others should be client-side rendered. For example, think of a content website with an admin section. Every content page should be primarily static and generated once, but the admin section requires registration and behaves more like a dynamic application.
Nuxt includes route rules and hybrid rendering support. Using route rules you can define rules for a group of nuxt routes, change rendering mode or assign a cache strategy based on route!
Nuxt server will automatically register corresponding middleware and wrap routes with cache handlers using [Nitro caching layer](https://nitro.build/guide/cache).
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
routeRules: {
// Homepage pre-rendered at build time
'/': { prerender: true },
// Products page generated on demand, revalidates in background, cached until API response changes
'/products': { swr: true },
// Product pages generated on demand, revalidates in background, cached for 1 hour (3600 seconds)
'/products/**': { swr: 3600 },
// Blog posts page generated on demand, revalidates in background, cached on CDN for 1 hour (3600 seconds)
'/blog': { isr: 3600 },
// Blog post page generated on demand once until next deployment, cached on CDN
'/blog/**': { isr: true },
// Admin dashboard renders only on client-side
'/admin/**': { ssr: false },
// Add cors headers on API routes
'/api/**': { cors: true },
// Redirects legacy urls
'/old-page': { redirect: '/new-page' },
},
})
```
### Route Rules
The different properties you can use are the following:
- `redirect: string`{lang=ts} - Define server-side redirects.
- `ssr: boolean`{lang=ts} - Disables server-side rendering of the HTML for sections of your app and make them render only in the browser with `ssr: false`
- `cors: boolean`{lang=ts} - Automatically adds cors headers with `cors: true` - you can customize the output by overriding with `headers`
- `headers: object`{lang=ts} - Add specific headers to sections of your site - for example, your assets
- `swr: number | boolean`{lang=ts} - Add cache headers to the server response and cache it on the server or reverse proxy for a configurable TTL (time to live). The `node-server` preset of Nitro is able to cache the full response. When the TTL expired, the cached response will be sent while the page will be regenerated in the background. If true is used, a `stale-while-revalidate` header is added without a MaxAge.
- `isr: number | boolean`{lang=ts} - The behavior is the same as `swr` except that we are able to add the response to the CDN cache on platforms that support this (currently Netlify or Vercel). If `true` is used, the content persists until the next deploy inside the CDN.
- `prerender: boolean`{lang=ts} - Prerenders routes at build time and includes them in your build as static assets
- `noScripts: boolean`{lang=ts} - Disables rendering of Nuxt scripts and JS resource hints for sections of your site.
- `appMiddleware: string | string[] | Record<string, boolean>`{lang=ts} - Allows you to define middleware that should or should not run for page paths within the Vue app part of your application (that is, not your Nitro routes)
::note
Routes using `isr` or `swr` also generate `_payload.json` files alongside HTML. Client-side navigation loads these cached payloads instead of re-fetching data. Configure dynamic routes like `pages/[...slug].vue` with glob patterns: `'/**': { isr: true }`.
::
Whenever possible, route rules will be automatically applied to the deployment platform's native rules for optimal performances (Netlify and Vercel are currently supported).
::important
Note that Hybrid Rendering is not available when using [`nuxt generate`](/docs/4.x/api/commands/generate).
::
**Examples:**
::card-group
::card
---
icon: i-simple-icons-github
title: Nuxt Vercel ISR
to: https://github.com/danielroe/nuxt-vercel-isr
target: _blank
ui.icon.base: text-black dark:text-white
---
Example of a Nuxt application with hybrid rendering deployed on Vercel.
::
::
## Edge-Side Rendering
Edge-Side Rendering (ESR) is a powerful feature introduced in Nuxt that allows the rendering of your Nuxt application closer to your users via edge servers of a Content Delivery Network (CDN). By leveraging ESR, you can ensure improved performance and reduced latency, thereby providing an enhanced user experience.
With ESR, the rendering process is pushed to the 'edge' of the network - the CDN's edge servers. Note that ESR is more a deployment target than an actual rendering mode.
When a request for a page is made, instead of going all the way to the original server, it's intercepted by the nearest edge server. This server generates the HTML for the page and sends it back to the user. This process minimizes the physical distance the data has to travel, **reducing latency and loading the page faster**.
Edge-side rendering is possible thanks to [Nitro](https://nitro.build/), the [server engine](/docs/4.x/guide/concepts/server-engine) that powers Nuxt. It offers cross-platform support for Node.js, Deno, Cloudflare Workers, and more.
The current platforms where you can leverage ESR are:
- [Cloudflare Pages](https://pages.cloudflare.com) with zero configuration using the git integration and the `nuxt build` command
- [Vercel Cloud](https://vercel.com/home) using the `nuxt build` command and `NITRO_PRESET=vercel-edge` environment variable
- [Netlify Edge Functions](https://www.netlify.com/platform/#netlify-edge-functions) using the `nuxt build` command and `NITRO_PRESET=netlify-edge` environment variable
Note that **Hybrid Rendering** can be used when using Edge-Side Rendering with route rules.

View File

@@ -0,0 +1,104 @@
---
title: 'Vue.js Development'
description: "Nuxt uses Vue.js and adds features such as component auto-imports, file-based routing and composables for an SSR-friendly usage."
navigation: false
---
Nuxt integrates Vue 3, the new major release of Vue that enables new patterns for Nuxt users.
::note
While an in-depth knowledge of Vue is not required to use Nuxt, we recommend that you read the documentation and go through some of the examples on [vuejs.org](https://vuejs.org).
::
Nuxt has always used Vue as a frontend framework.
We chose to build Nuxt on top of Vue for these reasons:
- The reactivity model of Vue, where a change in data automatically triggers a change in the interface.
- The component-based templating, while keeping HTML as the common language of the web, enables intuitive patterns to keep your interface consistent, yet powerful.
- From small projects to large web applications, Vue keeps performing well at scale to ensure that your application keeps delivering value to your users.
## Vue with Nuxt
### Single File Components
[Vues single-file components](https://vuejs.org/guide/scaling-up/sfc) (SFC or `*.vue` files) encapsulate the markup (`<template>`), logic (`<script>`) and styling (`<style>`) of a Vue component. Nuxt provides a zero-config experience for SFCs with [Hot Module Replacement](https://vite.dev/guide/features#hot-module-replacement) that offers a seamless developer experience.
### Auto-imports
Every Vue component created in the [`app/components/`](/docs/4.x/directory-structure/app/components) directory of a Nuxt project will be available in your project without having to import it. If a component is not used anywhere, your productions code will not include it.
:read-more{to="/docs/4.x/guide/concepts/auto-imports"}
### Vue Router
Most applications need multiple pages and a way to navigate between them. This is called **routing**. Nuxt uses an [`app/pages/`](/docs/4.x/directory-structure/app/pages) directory and naming conventions to directly create routes mapped to your files using the official [Vue Router library](https://router.vuejs.org).
:read-more{to="/docs/4.x/getting-started/routing"}
:link-example{to="/docs/4.x/examples/features/auto-imports"}
## Differences with Nuxt 2 / Vue 2
Nuxt 3+ is based on Vue 3. The new major Vue version introduces several changes that Nuxt takes advantage of:
- Better performance
- Composition API
- TypeScript support
### Faster Rendering
The Vue Virtual DOM (VDOM) has been rewritten from the ground up and allows for better rendering performance. On top of that, when working with compiled Single-File Components, the Vue compiler can further optimize them at build time by separating static and dynamic markup.
This results in faster first rendering (component creation) and updates, and less memory usage. In Nuxt 3, it enables faster server-side rendering as well.
### Smaller Bundle
With Vue 3 and Nuxt 3, a focus has been put on bundle size reduction. With version 3, most of Vues functionality, including template directives and built-in components, is tree-shakable. Your production bundle will not include them if you dont use them.
This way, a minimal Vue 3 application can be reduced to 12 kb gzipped.
### Composition API
The only way to provide data and logic to components in Vue 2 was through the Options API, which allows you to return data and methods to a template with pre-defined properties like `data` and `methods`:
```vue twoslash
<script>
export default {
data () {
return {
count: 0,
}
},
methods: {
increment () {
this.count++
},
},
}
</script>
```
The [Composition API](https://vuejs.org/guide/extras/composition-api-faq) introduced in Vue 3 is not a replacement of the Options API, but it enables better logic reuse throughout an application, and is a more natural way to group code by concern in complex components.
Used with the `setup` keyword in the `<script>` definition, here is the above component rewritten with Composition API and Nuxt 3s auto-imported Reactivity APIs:
```vue twoslash [components/Counter.vue]
<script setup lang="ts">
const count = ref(0)
const increment = () => count.value++
</script>
```
The goal of Nuxt is to provide a great developer experience around the Composition API.
- Use auto-imported [Reactivity functions](https://vuejs.org/api/reactivity-core) from Vue and Nuxt [built-in composables](/docs/4.x/api/composables/use-async-data).
- Write your own auto-imported reusable functions in the [`app/composables/` directory](/docs/4.x/directory-structure/app/composables).
### TypeScript Support
Both Vue 3 and Nuxt 3+ are written in TypeScript. A fully typed codebase prevents mistakes and documents APIs usage. This doesnt mean that you have to write your application in TypeScript to take advantage of it. With Nuxt 3, you can opt-in by renaming your file from `.js` to `.ts` , or add `<script setup lang="ts">` in a component.
::read-more{to="/docs/4.x/guide/concepts/typescript"}
Read the details about TypeScript in Nuxt
::

View File

@@ -0,0 +1,158 @@
---
title: 'Nuxt Lifecycle'
description: "Understanding the lifecycle of Nuxt applications can help you gain deeper insights into how the framework operates, especially for both server-side and client-side rendering."
---
The goal of this chapter is to provide a high-level overview of the different parts of the framework, their execution order, and how they work together.
## Server lifecycle
On the server, the following steps are executed for every initial request to your application:
::steps
### Server plugins :badge[once]{color="info" class="align-middle"}
Nuxt is powered by [Nitro](https://nitro.build/), a modern server engine.
When Nitro starts, it initializes and executes the plugins under the [`/server/plugins`](/docs/4.x/directory-structure/server#server-plugins) directory. These plugins can:
- Capture and handle application-wide errors.
- Register hooks that execute when Nitro shuts down.
- Register hooks for request lifecycle events, such as modifying responses.
::callout{icon="i-lucide-lightbulb"}
Nitro plugins are executed only once when the server starts. In a serverless environment, the server boots on each incoming request, and so do the Nitro plugins. However, they are not awaited.
::
:read-more{to="/docs/4.x/directory-structure/server#server-plugins"}
### Server middleware
After initializing the Nitro server, middleware under `server/middleware/` is executed for every request. Middleware can be used for tasks such as authentication, logging, or request transformation.
::warning
Returning a value from middleware will terminate the request and send the returned value as the response. This behavior should generally be avoided to ensure proper request handling!
::
:read-more{to="/docs/4.x/directory-structure/server#server-middleware"}
### App plugins
The Vue and Nuxt instances are created first. Afterward, Nuxt executes its app plugins. This includes:
- Built-in plugins, such as Vue Router and `unhead`.
- Custom plugins located in the `app/plugins/` directory, including those without a suffix (e.g., `myPlugin.ts`) and those with the `.server` suffix (e.g., `myServerPlugin.server.ts`).
Plugins execute in a specific order and may have dependencies on one another. For more details, including execution order and parallelism, refer to the [Plugins documentation](/docs/4.x/directory-structure/app/plugins).
::callout{icon="i-lucide-lightbulb"}
After this step, Nuxt calls the [`app:created`](/docs/4.x/api/advanced/hooks#app-hooks-runtime) hook, which can be used to execute additional logic.
::
:read-more{to="/docs/4.x/directory-structure/app/plugins"}
### Route validation
After initializing plugins and before executing middleware, Nuxt calls the `validate` method if it is defined in the `definePageMeta` function. The `validate` method, which can be synchronous or asynchronous, is often used to validate dynamic route parameters.
- The `validate` function should return `true` if the parameters are valid.
- If validation fails, it should return `false` or an object containing a `status` and/or `statusText` to terminate the request.
For more information, see the [Route Validation documentation](/docs/4.x/getting-started/routing#route-validation).
:read-more{to="/docs/4.x/getting-started/routing#route-validation"}
### App middleware
Middleware allows you to run code before navigating to a particular route. It is often used for tasks such as authentication, redirection, or logging.
In Nuxt, there are three types of middleware:
- **Global route middleware**
- **Named route middleware**
- **Anonymous (or inline) route middleware**
Nuxt executes all global middleware on the initial page load (both on server and client) and then again before any client-side navigation. Named and anonymous middleware are executed only on the routes specified in the middleware property of the page(route) meta defined in the corresponding page components.
For details about each type and examples, see the [Middleware documentation](/docs/4.x/directory-structure/app/middleware).
Any redirection on the server will result in a `Location:` header being sent to the browser; the browser then makes a fresh request to this new location. All application state will be reset when this happens, unless persisted in a cookie.
:read-more{to="/docs/4.x/directory-structure/app/middleware"}
### Page and components
Nuxt renders the page and its components and fetches any required data with `useFetch` and `useAsyncData` during this step. Since there are no dynamic updates and no DOM operations occur on the server, Vue lifecycle hooks such as `onBeforeMount`, `onMounted`, and subsequent hooks are **NOT** executed during SSR.
By default, Vue pauses dependency tracking during SSR for better performance.
::callout{icon="i-lucide-lightbulb"}
There is no reactivity on the server side because Vue SSR renders the app top-down as static HTML, making it impossible to go back and modify content that has already been rendered.
::
::important
You should avoid code that produces side effects that need cleanup in root scope of `<script setup>`. An example of such side effects is setting up timers with `setInterval`. In client-side only code we may setup a timer and then tear it down in `onBeforeUnmount` or `onUnmounted`. However, because the unmount hooks will never be called during SSR, the timers will stay around forever. To avoid this, move your side-effect code into `onMounted` instead.
::
::tip{icon="i-lucide-video" to="https://youtu.be/dZSNW07sO-A" target="_blank"}
Watch a video from Daniel Roe explaining Server Rendering and Global State.
::
### HTML Output
After all required data is fetched and the components are rendered, Nuxt combines the rendered components with settings from `unhead` to generate a complete HTML document. This HTML, along with the associated data, is then sent back to the client to complete the SSR process.
::callout{icon="i-lucide-lightbulb"}
After rendering the Vue application to HTML, Nuxt calls the [`app:rendered`](/docs/4.x/api/advanced/hooks#app-hooks-runtime) hook.
::
::callout{icon="i-lucide-lightbulb"}
Before finalizing and sending the HTML, Nitro will call the [`render:html`](/docs/4.x/api/advanced/hooks#nitro-app-hooks-runtime-server-side) hook. This hook allows you to manipulate the generated HTML, such as injecting additional scripts or modifying meta tags.
::
::
## Client lifecycle
This part of the lifecycle is fully executed in the browser, no matter which Nuxt mode you've chosen.
::steps
### App plugins
This step is similar to the server-side execution and includes both built-in and custom plugins.
Custom plugins in the `app/plugins/` directory, such as those without a suffix (e.g., `myPlugin.ts`) and with the `.client` suffix (e.g., `myClientPlugin.client.ts`), are executed on the client side.
::callout{icon="i-lucide-lightbulb"}
After this step, Nuxt calls the [`app:created`](/docs/4.x/api/advanced/hooks#app-hooks-runtime) hook, which can be used to execute additional logic.
::
:read-more{to="/docs/4.x/directory-structure/app/plugins"}
### Route validation
This step is the same as the server-side execution and includes the `validate` method if defined in the `definePageMeta` function.
### App middleware
Nuxt middleware runs on both the server and the client. If you want certain code to run in specific environments, consider splitting it by using `import.meta.client` for the client and `import.meta.server` for the server.
:read-more{to="/docs/4.x/directory-structure/app/middleware#when-middleware-runs"}
### Mount Vue app and hydrate
Calling `app.mount('#__nuxt')` mounts the Vue application to the DOM. If the application uses SSR or SSG mode, Vue performs a hydration step to make the client-side application interactive. During hydration, Vue recreates the application (excluding [Server Components](/docs/4.x/directory-structure/app/components#server-components)), matches each component to its corresponding DOM nodes, and attaches DOM event listeners.
To ensure proper hydration, it's important to maintain consistency between the data on the server and the client. For API requests, it is recommended to use `useAsyncData`, `useFetch`, or other SSR-friendly composables. These methods ensure that the data fetched on the server side is reused during hydration, avoiding repeated requests. Any new requests should only be triggered after hydration, preventing hydration errors.
::callout{icon="i-lucide-lightbulb"}
Before mounting the Vue application, Nuxt calls the [`app:beforeMount`](/docs/4.x/api/advanced/hooks#app-hooks-runtime) hook.
::
::callout{icon="i-lucide-lightbulb"}
After mounting the Vue application, Nuxt calls the [`app:mounted`](/docs/4.x/api/advanced/hooks#app-hooks-runtime) hook.
::
### Vue lifecycle
Unlike on the server, the browser executes the full [Vue lifecycle](https://vuejs.org/guide/essentials/lifecycle).
::

View File

@@ -0,0 +1,205 @@
---
title: Auto-imports
description: "Nuxt auto-imports components, composables, helper functions and Vue APIs."
---
Nuxt auto-imports components, composables and [Vue.js APIs](https://vuejs.org/api/) to use across your application without explicitly importing them.
```vue twoslash [app/app.vue]
<script setup lang="ts">
const count = ref(1) // ref is auto-imported
</script>
```
Thanks to its opinionated directory structure, Nuxt can auto-import your [`app/components/`](/docs/4.x/directory-structure/app/components), [`app/composables/`](/docs/4.x/directory-structure/app/composables) and [`app/utils/`](/docs/4.x/directory-structure/app/utils).
Contrary to a classic global declaration, Nuxt preserves typings, IDEs completions and hints, and **only includes what is used in your production code**.
::note
In the docs, every function that is not explicitly imported is auto-imported by Nuxt and can be used as-is in your code. You can find a reference for auto-imported components, composables and utilities in the [API section](/docs/4.x/api).
::
::note
In the [`server`](/docs/4.x/directory-structure/server) directory, Nuxt auto-imports exported functions and variables from `server/utils/`.
::
::note
You can also auto-import functions exported from custom folders or third-party packages by configuring the [`imports`](/docs/4.x/api/nuxt-config#imports) section of your `nuxt.config` file.
::
## Built-in Auto-imports
Nuxt auto-imports functions and composables to perform [data fetching](/docs/4.x/getting-started/data-fetching), get access to the [app context](/docs/4.x/api/composables/use-nuxt-app) and [runtime config](/docs/4.x/guide/going-further/runtime-config), manage [state](/docs/4.x/getting-started/state-management) or define components and plugins.
```vue twoslash
<script setup lang="ts">
/* useFetch() is auto-imported */
const { data, refresh, status } = await useFetch('/api/hello')
</script>
```
Vue exposes Reactivity APIs like `ref` or `computed`, as well as lifecycle hooks and helpers that are auto-imported by Nuxt.
```vue twoslash
<script setup lang="ts">
/* ref() and computed() are auto-imported */
const count = ref(1)
const double = computed(() => count.value * 2)
</script>
```
### Vue and Nuxt Composables
<!-- TODO: move to separate page with https://github.com/nuxt/nuxt/issues/14723 and add more information -->
When you are using the built-in Composition API composables provided by Vue and Nuxt, be aware that many of them rely on being called in the right _context_.
During a component lifecycle, Vue tracks the temporary instance of the current component (and similarly, Nuxt tracks a temporary instance of `nuxtApp`) via a global variable, and then unsets it in the same tick. This is essential when server rendering, both to avoid cross-request state pollution (leaking a shared reference between two users) and to avoid leakage between different components.
That means that (with very few exceptions) you cannot use them outside a Nuxt plugin, Nuxt route middleware or Vue setup function. On top of that, you must use them synchronously - that is, you cannot use `await` before calling a composable, except within `<script setup>` blocks, within the setup function of a component declared with `defineNuxtComponent`, in `defineNuxtPlugin` or in `defineNuxtRouteMiddleware`, where we perform a transform to keep the synchronous context even after the `await`.
If you get an error message like `Nuxt instance is unavailable` then it probably means you are calling a Nuxt composable in the wrong place in the Vue or Nuxt lifecycle.
:video-accordion{title="Watch a video from Alexander Lichter about avoiding the 'Nuxt instance is unavailable' error" videoId="ofuKRZLtOdY"}
::tip
When using a composable that requires the Nuxt context inside a non-SFC component, you need to wrap your component with `defineNuxtComponent` instead of `defineComponent`
::
::read-more{to="/docs/4.x/guide/going-further/experimental-features#asynccontext" icon="i-lucide-star"}
Checkout the `asyncContext` experimental feature to use Nuxt composables in async functions.
::
::read-more{to="https://github.com/nuxt/nuxt/issues/14269#issuecomment-1397352832" target="_blank"}
See the full explanation in this GitHub comment.
::
**Example of breaking code:**
```ts twoslash [composables/example.ts]
// trying to access runtime config outside a composable
const config = useRuntimeConfig()
export const useMyComposable = () => {
// accessing runtime config here
}
```
**Example of working code:**
```ts twoslash [composables/example.ts]
export const useMyComposable = () => {
// Because your composable is called in the right place in the lifecycle,
// useRuntimeConfig will work here
const config = useRuntimeConfig()
// ...
}
```
## Directory-based Auto-imports
Nuxt directly auto-imports files created in defined directories:
- `app/components/` for [Vue components](/docs/4.x/directory-structure/app/components).
- `app/composables/` for [Vue composables](/docs/4.x/directory-structure/app/composables).
- `app/utils/` for helper functions and other utilities.
:link-example{to="/docs/4.x/examples/features/auto-imports"}
::warning
**Auto-imported `ref` and `computed` won't be unwrapped in a component `<template>`.** :br
This is due to how Vue works with refs that aren't top-level to the template. You can read more about it [in the Vue documentation](https://vuejs.org/guide/essentials/reactivity-fundamentals#caveat-when-unwrapping-in-templates).
::
### Explicit Imports
Nuxt exposes every auto-import with the `#imports` alias that can be used to make the import explicit if needed:
<!-- TODO:twoslash: Twoslash does not support tsconfig paths yet -->
```vue
<script setup lang="ts">
import { computed, ref } from '#imports'
const count = ref(1)
const double = computed(() => count.value * 2)
</script>
```
### Disabling Auto-imports
If you want to disable auto-importing composables and utilities, you can set `imports.autoImport` to `false` in the `nuxt.config` file.
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
imports: {
autoImport: false,
},
})
```
This will disable auto-imports completely but it's still possible to use [explicit imports](/docs/4.x/guide/concepts/auto-imports#explicit-imports) from `#imports`.
### Partially Disabling Auto-imports
If you want framework-specific functions like `ref` to remain auto-imported but wish to disable auto-imports for your own code (e.g., custom composables), you can set the `imports.scan` option to `false` in your `nuxt.config.ts` file:
```ts
export default defineNuxtConfig({
imports: {
scan: false,
},
})
```
With this configuration:
- Framework functions like `ref`, `computed`, or `watch` will still work without needing manual imports.
- Custom code, such as composables, will need to be manually imported in your files.
::warning
**Caution:** This setup has certain limitations:
- If you structure your project with layers, you will need to explicitly import the composables from each layer, rather than relying on auto-imports.
- This breaks the layer systems override feature. If you use `imports.scan: false`, ensure you understand this side-effect and adjust your architecture accordingly.
::
## Auto-imported Components
Nuxt also automatically imports components from your `~/components` directory, although this is configured separately from auto-importing composables and utility functions.
:read-more{to="/docs/4.x/directory-structure/app/components"}
To disable auto-importing components from your own `~/components` directory, you can set `components.dirs` to an empty array (though note that this will not affect components added by modules).
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
components: {
dirs: [],
},
})
```
## Auto-import from Third-Party Packages
Nuxt also allows auto-importing from third-party packages.
::tip
If you are using the Nuxt module for that package, it is likely that the module has already configured auto-imports for that package.
::
For example, you could enable the auto-import of the `useI18n` composable from the `vue-i18n` package like this:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
imports: {
presets: [
{
from: 'vue-i18n',
imports: ['useI18n'],
},
],
},
})
```
:video-accordion{title="Watch a video from Alexander Lichter on how to easily set up custom auto imports" videoId="FT2LQJ2NvVI"}

View File

@@ -0,0 +1,62 @@
---
title: Server Engine
description: 'Nuxt is powered by a new server engine: Nitro.'
---
While building Nuxt, we created a new server engine: [Nitro](https://nitro.build/).
It is shipped with many features:
- Cross-platform support for Node.js, browsers, service workers and more.
- Serverless support out-of-the-box.
- API routes support.
- Automatic code-splitting and async-loaded chunks.
- Hybrid mode for static + serverless sites.
- Development server with hot module reloading.
## API Layer
Server [API endpoints](/docs/4.x/directory-structure/server#server-routes) and [Middleware](/docs/4.x/directory-structure/server#server-middleware) are added by Nitro that internally uses [h3](https://github.com/h3js/h3).
Key features include:
- Handlers can directly return objects/arrays for an automatically-handled JSON response
- Handlers can return promises, which will be awaited (`res.end()` and `next()` are also supported)
- Helper functions for body parsing, cookie handling, redirects, headers and more
Check out [the h3 docs](https://github.com/h3js/h3) for more information.
::read-more{to="/docs/4.x/directory-structure/server#server-routes"}
Learn more about the API layer in the `server/` directory.
::
## Direct API Calls
Nitro allows 'direct' calling of routes via the globally-available [`$fetch`](/docs/4.x/api/utils/dollarfetch) helper. This will make an API call to the server if run on the browser, but will directly call the relevant function if run on the server, **saving an additional API call**.
[`$fetch`](/docs/4.x/api/utils/dollarfetch) API is using [ofetch](https://github.com/unjs/ofetch), with key features including:
- Automatic parsing of JSON responses (with access to raw response if needed)
- Request body and params are automatically handled, with correct `Content-Type` headers
For more information on `$fetch` features, check out [ofetch](https://github.com/unjs/ofetch).
## Typed API Routes
When using API routes (or middleware), Nitro will generate typings for these routes as long as you are returning a value instead of using `res.end()` to send a response.
You can access these types when using [`$fetch()`](/docs/4.x/api/utils/dollarfetch) or [`useFetch()`](/docs/4.x/api/composables/use-fetch).
## Standalone Server
Nitro produces a standalone server dist that is independent of `node_modules`.
The server in Nuxt 2 is not standalone and requires part of Nuxt core to be involved by running `nuxt start` (with the [`nuxt-start`](https://www.npmjs.com/package/nuxt-start) or [`nuxt`](https://www.npmjs.com/package/nuxt) distributions) or custom programmatic usage, which is fragile and prone to breakage and not suitable for serverless and service worker environments.
Nuxt generates this dist when running `nuxt build` into a [`.output`](/docs/4.x/directory-structure/output) directory.
The output contains runtime code to run your Nuxt server in any environment (including experimental browser service workers!) and serve your static files, making it a true hybrid framework for the JAMstack. In addition, Nuxt implements a native storage layer, supporting multi-source drivers and local assets.
::read-more{icon="i-simple-icons-github" to="https://github.com/nitrojs/nitro" target="_blank"}
Read more about Nitro engine on GitHub.
::

View File

@@ -0,0 +1,61 @@
---
title: 'Modules'
description: "Nuxt provides a module system to extend the framework core and simplify integrations."
---
## Exploring Nuxt Modules
When developing production-grade applications with Nuxt you might find that the framework's core functionality is not enough. Nuxt can be extended with configuration options and plugins, but maintaining these customizations across multiple projects can be tedious, repetitive and time-consuming. On the other hand, supporting every project's needs out of the box would make Nuxt very complex and hard to use.
This is one of the reasons why Nuxt provides a module system that makes it possible to extend the core. Nuxt modules are async functions that sequentially run when starting Nuxt in development mode using [`nuxt dev`](/docs/4.x/api/commands/dev) or building a project for production with [`nuxt build`](/docs/4.x/api/commands/build). They can override templates, configure webpack loaders, add CSS libraries, and perform many other useful tasks.
Best of all, Nuxt modules can be distributed in npm packages. This makes it possible for them to be reused across projects and shared with the community, helping create an ecosystem of high-quality add-ons.
::read-more{to="/modules"}
Explore Nuxt Modules
::
## Add Nuxt Modules
Once you have installed the modules you can add them to your [`nuxt.config.ts`](/docs/4.x/directory-structure/nuxt-config) file under the `modules` property. Module developers usually provide additional steps and details for usage.
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
modules: [
// Using package name (recommended usage)
'@nuxtjs/example',
// Load a local module
'./modules/example',
// Add module with inline-options
['./modules/example', { token: '123' }],
// Inline module definition
async (inlineOptions, nuxt) => { },
],
})
```
::warning
Nuxt modules are now build-time-only, and the `buildModules` property used in Nuxt 2 is deprecated in favor of `modules`.
::
## Disabling Modules
You can disable a module by setting its config key to `false` in your Nuxt config. This is particularly useful when you want to disable modules inherited from layers.
```ts [nuxt.config.ts]
export default defineNuxtConfig({
// Disable `@nuxt/image` module
image: false,
})
```
:read-more{to="/docs/4.x/guide/going-further/layers#disabling-modules-from-layers"}
## Create a Nuxt Module
Everyone has the opportunity to develop modules and we cannot wait to see what you will build.
:read-more{to="/docs/4.x/guide/modules" title="Module Author Guide"}

View File

@@ -0,0 +1,306 @@
---
title: 'ES Modules'
description: "Nuxt uses native ES modules."
navigation: false
---
This guide helps explain what ES Modules are and how to make a Nuxt app (or upstream library) compatible with ESM.
## Background
### CommonJS Modules
CommonJS (CJS) is a format introduced by Node.js that allows sharing functionality between isolated JavaScript modules ([read more](https://nodejs.org/api/modules.html)).
You might be already familiar with this syntax:
```js
const a = require('./a')
module.exports.a = a
```
Bundlers like webpack and Rollup support this syntax and allow you to use modules written in CommonJS in the browser.
### ESM Syntax
Most of the time, when people talk about ESM vs. CJS, they are talking about a different syntax for writing [modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules).
```js
import a from './a'
export { a }
```
Before ECMAScript Modules (ESM) became a standard (it took more than 10 years!), tooling like
[webpack](https://webpack.js.org/guides/ecma-script-modules/) and even languages like TypeScript started supporting so-called **ESM syntax**.
However, there are some key differences with actual spec; here's [a helpful explainer](https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/).
### What is 'Native' ESM?
You may have been writing your app using ESM syntax for a long time. After all, it's natively supported by the browser, and in Nuxt 2 we compiled all the code you wrote to the appropriate format (CJS for server, ESM for browser).
When adding modules to your package, things were a little different. A sample library might expose both CJS and ESM versions, and let us pick which one we wanted:
```json
{
"name": "sample-library",
"main": "dist/sample-library.cjs.js",
"module": "dist/sample-library.esm.js"
}
```
So in Nuxt 2, the bundler (webpack) would pull in the CJS file ('main') for the server build and use the ESM file ('module') for the client build.
::note
The `module` field is a convention used by bundlers like webpack and Rollup, but is not recognized by Node.js itself. Node.js only uses the [`exports`](https://nodejs.org/api/packages.html#exports) and [`main`](https://nodejs.org/api/packages.html#main) fields for module resolution.
::
However, in recent Node.js LTS releases, it is now possible to [use native ESM module](https://nodejs.org/api/esm.html) within Node.js. That means that Node.js itself can process JavaScript using ESM syntax, although it doesn't do it by default. The two most common ways to enable ESM syntax are:
- set `"type": "module"` within your `package.json` and keep using `.js` extension
- use the `.mjs` file extensions (recommended)
This is what we do for Nuxt Nitro; we output a `.output/server/index.mjs` file. That tells Node.js to treat this file as a native ES module.
### What Are Valid Imports in a Node.js Context?
When you `import` a module rather than `require` it, Node.js resolves it differently. For example, when you import `sample-library`, Node.js will look for the `exports` entry in that library's `package.json`, or fall back to the `main` entry if `exports` is not defined.
This is also true of dynamic imports, like `const b = await import('sample-library')`.
Node supports the following kinds of imports (see [docs](https://nodejs.org/api/packages.html#determining-module-system)):
1. files ending in `.mjs` - these are expected to use ESM syntax
1. files ending in `.cjs` - these are expected to use CJS syntax
1. files ending in `.js` - these are expected to use CJS syntax unless their `package.json` has `"type": "module"`
### What Kinds of Problems Can There Be?
For a long time module authors have been producing ESM-syntax builds but using conventions like `.esm.js` or `.es.js`, which they have added to the `module` field in their `package.json`. This hasn't been a problem until now because they have only been used by bundlers like webpack, which don't especially care about the file extension.
However, if you try to import a package with an `.esm.js` file in a Node.js ESM context, it won't work, and you'll get an error like:
```bash [Terminal]
(node:22145) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
/path/to/index.js:1
export default {}
^^^^^^
SyntaxError: Unexpected token 'export'
at wrapSafe (internal/modules/cjs/loader.js:1001:16)
at Module._compile (internal/modules/cjs/loader.js:1049:27)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
....
at async Object.loadESM (internal/process/esm_loader.js:68:5)
```
You might also get this error if you have a named import from an ESM-syntax build that Node.js thinks is CJS:
```bash [Terminal]
file:///path/to/index.mjs:5
import { named } from 'sample-library'
^^^^^
SyntaxError: Named export 'named' not found. The requested module 'sample-library' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:
import pkg from 'sample-library';
const { named } = pkg;
at ModuleJob._instantiate (internal/modules/esm/module_job.js:120:21)
at async ModuleJob.run (internal/modules/esm/module_job.js:165:5)
at async Loader.import (internal/modules/esm/loader.js:177:24)
at async Object.loadESM (internal/process/esm_loader.js:68:5)
```
## Troubleshooting ESM Issues
If you encounter these errors, the issue is almost certainly with the upstream library. They need to [fix their library](/docs/4.x/guide/concepts/esm#library-author-guide) to support being imported by Node.
### Transpiling Libraries
In the meantime, you can tell Nuxt not to try to import these libraries by adding them to `build.transpile`:
```ts twoslash
export default defineNuxtConfig({
build: {
transpile: ['sample-library'],
},
})
```
You may find that you _also_ need to add other packages that are being imported by these libraries.
### Aliasing Libraries
In some cases, you may also need to manually alias the library to the CJS version, for example:
```ts twoslash
export default defineNuxtConfig({
alias: {
'sample-library': 'sample-library/dist/sample-library.cjs.js',
},
})
```
### Default Exports
A dependency with CommonJS format, can use `module.exports` or `exports` to provide a default export:
```js [node_modules/cjs-pkg/index.js]
module.exports = { test: 123 }
// or
exports.test = 123
```
This normally works well if we `require` such dependency:
```js [test.cjs]
const pkg = require('cjs-pkg')
console.log(pkg) // { test: 123 }
```
[Node.js in native ESM mode](https://nodejs.org/api/esm.html#interoperability-with-commonjs), [typescript with `esModuleInterop` enabled](https://www.typescriptlang.org/tsconfig/#esModuleInterop) and bundlers such as webpack, provide a compatibility mechanism so that we can default import such library.
This mechanism is often referred to as "interop require default":
```js
import pkg from 'cjs-pkg'
console.log(pkg) // { test: 123 }
```
However, because of the complexities of syntax detection and different bundle formats, there is always a chance that the interop default fails and we end up with something like this:
```js
import pkg from 'cjs-pkg'
console.log(pkg) // { default: { test: 123 } }
```
Also when using dynamic import syntax (in both CJS and ESM files), we always have this situation:
```js
import('cjs-pkg').then(console.log) // [Module: null prototype] { default: { test: '123' } }
```
In this case, we need to manually interop the default export:
<!-- eslint-disable import/no-named-default -->
```js
// Static import
import { default as pkg } from 'cjs-pkg'
// Dynamic import
import('cjs-pkg').then(m => m.default || m).then(console.log)
```
For handling more complex situations and more safety, we recommend and internally use [mlly](https://github.com/unjs/mlly) in Nuxt that can preserve named exports.
```js
import { interopDefault } from 'mlly'
// Assuming the shape is { default: { foo: 'bar' }, baz: 'qux' }
import myModule from 'my-module'
console.log(interopDefault(myModule)) // { foo: 'bar', baz: 'qux' }
```
## Library Author Guide
The good news is that it's relatively simple to fix issues of ESM compatibility. There are two main options:
1. **You can rename your ESM files to end with `.mjs`.**
_This is the recommended and simplest approach._ You may have to sort out issues with your library's dependencies and possibly with your build system, but in most cases, this should fix the problem for you. It's also recommended to rename your CJS files to end with `.cjs`, for the greatest explicitness.
1. **You can opt to make your entire library ESM-only**.
This would mean setting `"type": "module"` in your `package.json` and ensuring that your built library uses ESM syntax. However, you may face issues with your dependencies - and this approach means your library can _only_ be consumed in an ESM context.
### Migration
The initial step from CJS to ESM is updating any usage of `require` to use `import` instead:
::code-group
```ts [Before]
module.exports = function () { /* ... */ }
exports.hello = 'world'
```
```ts [After]
export default function () { /* ... */ }
export const hello = 'world'
```
::
::code-group
```js [Before]
const myLib = require('my-lib')
```
```js [After]
import myLib from 'my-lib'
// or
const dynamicMyLib = await import('my-lib').then(lib => lib.default || lib)
```
::
In ESM Modules, unlike CJS, `require`, `require.resolve`, `__filename` and `__dirname` globals are not available
and should be replaced with `import()` and `import.meta.filename`.
::code-group
```js [Before]
const { join } = require('node:path')
const newDir = join(__dirname, 'new-dir')
```
```js [After]
import { fileURLToPath } from 'node:url'
const newDir = fileURLToPath(new URL('./new-dir', import.meta.url))
```
::
::code-group
```js [Before]
const someFile = require.resolve('./lib/foo.js')
```
```js [After]
import { resolvePath } from 'mlly'
const someFile = await resolvePath('my-lib', { url: import.meta.url })
```
::
### Best Practices
- Prefer named exports rather than default export. This helps reduce CJS conflicts. (see [Default exports](/docs/4.x/guide/concepts/esm#default-exports) section)
- Avoid depending on Node.js built-ins and CommonJS or Node.js-only dependencies as much as possible to make your library usable in Browsers and Edge Workers without needing Nitro polyfills.
- Use new `exports` field with conditional exports. ([read more](https://nodejs.org/api/packages.html#conditional-exports)).
```json
{
"exports": {
".": {
"import": "./dist/mymodule.mjs"
}
}
}
```

View File

@@ -0,0 +1,120 @@
---
title: 'TypeScript'
description: "Nuxt is fully typed and provides helpful shortcuts to ensure you have access to accurate type information when you are coding."
---
## Type-checking
By default, Nuxt doesn't check types when you run [`nuxt dev`](/docs/4.x/api/commands/dev) or [`nuxt build`](/docs/4.x/api/commands/build), for performance reasons.
To enable type-checking at build or development time, install `vue-tsc` and `typescript` as development dependency:
::code-group{sync="pm"}
```bash [npm]
npm install --save-dev vue-tsc typescript
```
```bash [yarn]
yarn add --dev vue-tsc typescript
```
```bash [pnpm]
pnpm add -D vue-tsc typescript
```
```bash [bun]
bun add -D vue-tsc typescript
```
```bash [deno]
deno add -D npm:vue-tsc npm:typescript
```
::
Then, run [`nuxt typecheck`](/docs/4.x/api/commands/typecheck) command to check your types:
```bash [Terminal]
npx nuxt typecheck
```
To enable type-checking at build or development time, you can also use the [`typescript.typeCheck`](/docs/4.x/api/nuxt-config#typecheck) option in your `nuxt.config` file:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
typescript: {
typeCheck: true,
},
})
```
## Auto-generated Types
Nuxt projects rely on auto-generated types to work properly. These types are stored in the [`.nuxt`](/docs/4.x/directory-structure/nuxt) directory and are generated when you run the dev server or build your application. You can also generate these files manually by running `nuxt prepare`.
The generated `tsconfig.json` files inside the [`.nuxt`](/docs/4.x/directory-structure/nuxt) directory include **recommended basic TypeScript configuration** for your project, references to [auto-imports](/docs/4.x/guide/concepts/auto-imports), [API route types](/docs/4.x/guide/concepts/server-engine#typed-api-routes), path aliases like `#imports`, `~/file`, or `#build/file`, and more.
::warning
Nuxt relies on this configuration, and [Nuxt modules](/docs/4.x/guide/modules) can extend it as well. For this reason, it is not recommended to modify your `tsconfig.json` file directly, as doing so could overwrite important settings. Instead, extend it via `nuxt.config.ts`. [Learn more about extending the configuration here](/docs/4.x/directory-structure/tsconfig).
::
::tip{icon="i-lucide-video" to="https://youtu.be/umLI7SlPygY" target="_blank"}
Watch a video from Daniel Roe explaining built-in Nuxt aliases.
::
## Project References
Nuxt uses [TypeScript project references](https://www.typescriptlang.org/docs/handbook/project-references.html) to improve type-checking performance and provide better IDE support. This feature allows TypeScript to break up your codebase into smaller, more manageable pieces.
### How Nuxt Uses Project References
When you run `nuxt dev`, `nuxt build` or `nuxt prepare`, Nuxt will generate multiple `tsconfig.json` files for different parts of your application.
- **`.nuxt/tsconfig.app.json`** - Configuration for your application code within the `app/` directory
- **`.nuxt/tsconfig.node.json`** - Configuration for your `nuxt.config.ts` and files outside the other contexts
- **`.nuxt/tsconfig.server.json`** - Configuration for server-side code (when applicable)
- **`.nuxt/tsconfig.shared.json`** - For code shared between app and server contexts (like types and non-environment specific utilities)
Each of these files is configured to reference the appropriate dependencies and provide optimal type-checking for their specific context.
::note
For backward compatibility, Nuxt still generates `.nuxt/tsconfig.json`. However, we recommend using [TypeScript project references](/docs/4.x/directory-structure/tsconfig) with the new configuration files (`.nuxt/tsconfig.app.json`, `.nuxt/tsconfig.server.json`, etc.) for better type safety and performance. This legacy file will be removed in a future version of Nuxt.
::
### Benefits of Project References
- **Faster builds**: TypeScript can skip rebuilding unchanged projects
- **Better IDE performance**: Your IDE can provide faster IntelliSense and error checking
- **Isolated compilation**: Errors in one part of your application don't prevent compilation of other parts
- **Clearer dependency management**: Each project explicitly declares its dependencies
### Augmenting Types with Project References
Since the project is divided into **multiple type contexts**, it's important to **augment types within the correct context** to ensure they're properly recognized. TypeScript will not recognize augmentations placed outside these directories unless they are explicitly included in the appropriate context.
For example, if you want to augment types for the `app` context, the augmentation file should be placed in the `app/` directory.
Similarly:
- For the `server` context, place the augmentation file in the `server/` directory.
- For types that are **shared between the app and server**, place the file in the `shared/` directory.
::read-more{to="/docs/4.x/guide/modules/recipes-advanced#extend-typescript-config"}
Read more about augmenting specific type contexts from **files outside those contexts** in the Module Author Guide.
::
## Strict Checks
TypeScript comes with certain checks to give you more safety and analysis of your program.
[Strict checks](https://www.typescriptlang.org/docs/handbook/migrating-from-javascript.html#getting-stricter-checks) are enabled by default in Nuxt when the [`typescript.typeCheck`](/docs/4.x/guide/concepts/typescript#type-checking) option is enabled to give you greater type safety.
If you are currently converting your codebase to TypeScript, you may want to temporarily disable strict checks by setting `strict` to `false` in your `nuxt.config`:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
typescript: {
strict: false,
},
})
```

View File

@@ -0,0 +1,22 @@
---
title: 'Code Style'
description: "Nuxt supports ESLint out of the box"
---
## ESLint
The recommended approach for Nuxt is to enable ESLint support using the [`@nuxt/eslint`](https://eslint.nuxt.com/packages/module) module, that will setup project-aware ESLint configuration for you.
:::callout{icon="i-lucide-lightbulb"}
The module is designed for the [new ESLint flat config format](https://eslint.org/docs/latest/use/configure/configuration-files) which is the [default format since ESLint v9](https://eslint.org/blog/2024/04/eslint-v9.0.0-released/). If you are using the legacy `.eslintrc` config, you will need to [configure manually with `@nuxt/eslint-config`](https://eslint.nuxt.com/packages/config#customizing-the-config). We highly recommend you to migrate over the flat config to be future-proof.
:::
## Quick Setup
```bash
npx nuxt module add eslint
```
Start your Nuxt app, a `eslint.config.mjs` file will be generated under your project root. You can customize it as needed.
You can learn more about the module and customizations in [Nuxt ESLint's documentation](https://eslint.nuxt.com/packages/module).

View File

@@ -0,0 +1,3 @@
title: Best Practices
titleTemplate: '%s · Best Practices'
icon: i-lucide-square-check

View File

@@ -0,0 +1,188 @@
---
navigation.title: 'Nuxt and Hydration'
title: Nuxt and Hydration
description: Why fixing hydration issues is important
---
When developing, you may face hydration issues. Don't ignore those warnings.
## Why is it important to fix them?
Hydration mismatches are not just warnings - they are indicators of serious problems that can break your application:
### Performance Impact
- **Increased time to interactive**: Hydration errors force Vue to re-render the entire component tree, which will increase the time for your Nuxt app to become interactive
- **Poor user experience**: Users may see content flashing or unexpected layout shifts
### Functionality Issues
- **Broken interactivity**: Event listeners may not attach properly, leaving buttons and forms non-functional
- **State inconsistencies**: Application state can become out of sync between what the user sees and what the application thinks is rendered
- **SEO problems**: Search engines may index different content than what users actually see
## How to detect them
### Development Console Warnings
Vue will log hydration mismatch warnings in the browser console during development:
![Screenshot of Vue hydration mismatch warning in the browser console](/assets/docs/best-practices/vue-console-hydration.png)
## Common reasons
### Browser-only APIs in Server Context
**Problem**: Using browser-specific APIs during server-side rendering.
```html
<template>
<div>User preference: {{ userTheme }}</div>
</template>
<script setup>
// This will cause hydration mismatch!
// localStorage doesn't exist on the server!
const userTheme = localStorage.getItem('theme') || 'light'
</script>
```
**Solution**: You can use [`useCookie`](/docs/4.x/api/composables/use-cookie):
```html
<template>
<div>User preference: {{ userTheme }}</div>
</template>
<script setup>
// This works on both server and client
const userTheme = useCookie('theme', { default: () => 'light' })
</script>
```
### Inconsistent Data
**Problem**: Different data between server and client.
```html
<template>
<div>{{ Math.random() }}</div>
</template>
```
**Solution**: Use SSR-friendly state:
```html
<template>
<div>{{ state }}</div>
</template>
<script setup>
const state = useState('random', () => Math.random())
</script>
```
### Conditional Rendering Based on Client State
**Problem**: Using client-only conditions during SSR.
```html
<template>
<div v-if="window?.innerWidth > 768">
Desktop content
</div>
</template>
```
**Solution**: Use media queries or handle it client-side:
```html
<template>
<div class="responsive-content">
<div class="hidden md:block">Desktop content</div>
<div class="md:hidden">Mobile content</div>
</div>
</template>
```
### Third-party Libraries with Side Effects
**Problem**: Libraries that modify the DOM or have browser dependencies (this happens a LOT with tag managers).
```html
<script setup>
if (import.meta.client) {
const { default: SomeBrowserLibrary } = await import('browser-only-lib')
SomeBrowserLibrary.init()
}
</script>
```
**Solution**: Initialise libraries after hydration has completed:
```html
<script setup>
onMounted(async () => {
const { default: SomeBrowserLibrary } = await import('browser-only-lib')
SomeBrowserLibrary.init()
})
</script>
```
### Dynamic Content Based on Time
**Problem**: Content that changes based on current time.
```html
<template>
<div>{{ greeting }}</div>
</template>
<script setup>
const hour = new Date().getHours()
const greeting = hour < 12 ? 'Good morning' : 'Good afternoon'
</script>
```
**Solution**: Use [`NuxtTime`](/docs/4.x/api/components/nuxt-time) component or handle it client-side:
```html
<template>
<div>
<NuxtTime :date="new Date()" format="HH:mm" />
</div>
</template>
```
```html
<template>
<div>
<ClientOnly>
{{ greeting }}
<template #fallback>
Hello!
</template>
</ClientOnly>
</div>
</template>
<script setup>
const greeting = ref('Hello!')
onMounted(() => {
const hour = new Date().getHours()
greeting.value = hour < 12 ? 'Good morning' : 'Good afternoon'
})
</script>
```
## In summary
1. **Use SSR-friendly composables**: [`useFetch`](/docs/4.x/api/composables/use-fetch), [`useAsyncData`](/docs/4.x/api/composables/use-async-data), [`useState`](/docs/4.x/api/composables/use-state)
2. **Wrap client-only code**: Use [`ClientOnly`](/docs/4.x/api/components/client-only) component for browser-specific content
3. **Consistent data sources**: Ensure server and client uses the same data
4. **Avoid side effects in setup**: Move browser-dependent code to `onMounted`
::tip
You can read the [Vue documentation on SSR hydration mismatch](https://vuejs.org/guide/scaling-up/ssr#hydration-mismatch) for a better understanding of hydration.
::

View File

@@ -0,0 +1,307 @@
---
navigation.title: 'Nuxt Performance'
title: Nuxt performance
description: Best practices for improving performance of Nuxt apps.
---
Nuxt comes with built-in features designed to improve your application's performance and contribute to better [Core Web Vitals](https://web.dev/articles/vitals). There are also multiple Nuxt core modules that assist in improving performance in specific areas. This guide outlines best practices to optimize performance of your Nuxt application.
## Built-in Features
Nuxt offers several built-in features that help you optimize performance of your website. Understanding how these features work is crucial for achieving blazingly-fast performance.
### Links
[`<NuxtLink>`](/docs/4.x/api/components/nuxt-link) is a drop-in replacement for both Vue Router's `<RouterLink>` component and HTML's `<a>` tag. It intelligently determines whether the link is internal or external and renders it accordingly with available optimizations (prefetching, default attributes, etc.)
```html
<template>
<NuxtLink to="/about">About page</NuxtLink>
</template>
<!-- Which will render to with Vue Router & Smart Prefetching -->
<a href="/about">About page</a>
```
Nuxt automatically includes smart prefetching. That means it detects when a link is visible (by default), either in the viewport or when scrolling and prefetches the JavaScript for those pages so that they are ready when the user clicks the link.
You can also opt for prefetching on interaction instead:
```ts
export default defineNuxtConfig({
experimental: {
defaults: {
nuxtLink: {
prefetchOn: {
interaction: true,
visibility: false,
},
},
},
},
})
```
:read-more{title="NuxtLink" to="/docs/4.x/api/components/nuxt-link"}
### Hybrid Rendering
In more complex applications, we may need a full control over how our application is rendered to support cases where some pages could be generated at build time, while others should be client-side rendered
Hybrid rendering allows different caching rules per route using Route Rules and decides how the server should respond to a new request on a given URL:
```ts
export default defineNuxtConfig({
routeRules: {
'/': {
prerender: true,
},
'/products/**': {
swr: 3600,
},
'/blog': {
isr: 3600,
},
'/admin/**': {
ssr: false,
},
},
})
```
Nuxt server will automatically register corresponding middleware and wrap routes with cache handlers using Nitro caching layer.
:read-more{title="Hybrid rendering" to="/docs/4.x/guide/concepts/rendering#hybrid-rendering"}
### Lazy Loading Components
To dynamically import a component (also known as lazy-loading a component) all you need to do is add the Lazy prefix to the component's name. This is useful if the component is not always needed.
```html
<script setup lang="ts">
const show = ref(false)
</script>
<template>
<div>
<h1>Mountains</h1>
<LazyMountainsList v-if="show" />
<button v-if="!show" @click="show = true">Show List</button>
</div>
</template>
```
By using the Lazy prefix you can delay loading the component code until the right moment, which can be helpful for optimizing your JavaScript bundle size.
:read-more{title="Lazy loading components" to="/docs/4.x/directory-structure/app/components#dynamic-imports"}
### Lazy Hydration
It is not always necessary to hydrate (or make interactive) all the components of your site on the initial load. Using lazy hydration, you can control when components can have their code loaded, which can improve the time-to-interactive metric for your app. Nuxt allows you to control when components become interactive with lazy hydration (added in Nuxt v3.16).
```html
<template>
<div>
<LazyMyComponent hydrate-on-visible />
</div>
</template>
```
To optimize your app, you may want to delay the hydration of some components until they're visible, or until the browser is done with more important tasks.
:read-more{title="Lazy hydration" to="/docs/4.x/directory-structure/app/components#delayed-or-lazy-hydration"}
### Fetching data
To avoid fetching same data twice (once on the server and once on client) Nuxt provides [`useFetch`](/docs/4.x/api/composables/use-fetch) and [`useAsyncData`](/docs/4.x/api/composables/use-async-data). They ensure that if an API call is made on the server, the data is forwarded to the client in the payload instead of being fetched again.
:read-more{title="Data fetching" to="/docs/4.x/getting-started/data-fetching"}
## Core Nuxt Modules
Apart from Nuxt's built-in features, there are also core modules maintained by the Nuxt team which help improve performance even further. These modules help handle assets such as images, custom fonts, or third party scripts.
### Images
Unoptimized images can have a significant negative impact on your website performance, specifically the [Largest Contentful Paint (LCP)](https://web.dev/articles/lcp) score.
In Nuxt we can use [Nuxt Image](https://image.nuxt.com/) module that is a plug-and-play image optimization for Nuxt apps. It allows resizing and transforming your images using built-in optimizer or your favorite images CDN.
:video-accordion{title="Watch the video by LearnVue about Nuxt Image" videoId="_UBff2eqGY0"}
[`<NuxtImg>`](/docs/4.x/api/components/nuxt-img) is a drop-in replacement for the native `<img>` tag that comes with following enhancements:
* Uses built-in provider to optimize local and remote images
* Converts `src` to provider optimized URLs with modern formats such as WebP or Avif
* Automatically resizes images based on `width` and `height`
* Generates responsive `sizes` when providing sizes option
* Supports native `lazy loading` as well as other `<img>` attributes
Images in your website can usually be separated by importance; the ones that are needed to be delivered first at initial load (i.e. `Largest Contentful Paint`), and the ones that can be loaded later or when specifically needed. For that, we could use the following optimizations:
```html
<template>
<!-- 🚨 Needs to be loaded ASAP -->
<NuxtImg
src="/hero-banner.jpg"
format="webp"
:preload="{ fetchPriority: 'high' }"
loading="eager"
width="200"
height="100"
/>
<!-- 🐌 Can be loaded later -->
<NuxtImg
src="/facebook-logo.jpg"
format="webp"
loading="lazy"
fetchpriority="low"
width="200"
height="100"
/>
</template>
```
:read-more{title="Nuxt Image" to="https://image.nuxt.com/usage/nuxt-img"}
### Fonts
[Nuxt Fonts](https://fonts.nuxt.com/) will automatically optimize your fonts (including custom fonts) and remove external network requests for improved privacy and performance.
It includes built-in automatic self-hosting for any font file which means you can optimally load web fonts with reduced layout shift, thanks to the underlying package [fontaine](https://github.com/unjs/fontaine).
:video-accordion{title="Watch the talk by Daniel Roe about the idea behind Nuxt Fonts" videoId="D3F683UViBY"}
Nuxt Fonts processes all your CSS and does the following things automatically when it encounters a font-family declaration.
1. **Resolves fonts** Looks for font files in public/, then checks web providers like Google, Bunny, and Fontshare.
2. **Generates @font-face rules** Injects CSS rules to load fonts from the correct sources.
3. **Proxies & caches fonts** Rewrites URLs to `/_fonts`, downloads and caches fonts locally.
4. **Creates fallback metrics** Adjusts local system fonts to match web fonts, reducing layout shift ([CLS](https://web.dev/articles/cls)).
5. **Includes fonts in build** Bundles fonts with your project, hashing file names and setting long-lived cache headers.
It supports multiple providers that are designed to be pluggable and extensible, so no matter your setup you should be able to use an existing provider or write your own.
### Scripts
Third-party resources like analytics tools, video embeds, maps, and social media integrations enhance website functionality but can significantly degrade user experience and negatively impact [Interaction to Next Paint (INP)](https://web.dev/articles/inp) and Largest Contentful Paint (LCP) scores.
[Nuxt Scripts](https://scripts.nuxt.com/) lets you load third-party scripts with better performance, privacy, security and DX.
:video-accordion{title="Watch the video by Alex Lichter about Nuxt Scripts" videoId="sjMqUUvH9AE"}
Nuxt Scripts provides an abstraction layer on top of third-party scripts, providing SSR support and type-safety and while still giving you full low-level control over how a script is loaded.
```ts
const { onLoaded, proxy } = useScriptGoogleAnalytics(
{
id: 'G-1234567',
scriptOptions: {
trigger: 'manual',
},
},
)
// queue events to be sent when ga loads
proxy.gtag('config', 'UA-123456789-1')
// or wait until ga is loaded
onLoaded((gtag) => {
// script loaded
})
```
:read-more{title="Nuxt Scripts" to="https://scripts.nuxt.com/scripts"}
## Profiling Tools
To improve performance, we need to first know how to measure it, starting with measuring performance during development - on local environment, and then moving to auditing application that are deployed on production.
### Nuxi Analyze
[This](/docs/4.x/api/commands/analyze) command of `nuxi` allows to analyze the production bundle or your Nuxt application. It leverages `vite-bundle-visualizer` (similar to `webpack-bundle-analyzer`) to generate a visual representation of your application's bundle, making it easier to identify which components take up the most space.
When you see a large block in the visualization, it often signals an opportunity for optimization—whether by splitting it into smaller parts, implementing lazy loading, or replacing it with a more efficient alternative, especially for third-party libraries.
Large blocks containing multiple elements can often be reduced by importing only the necessary components rather than entire modules while large standalone blocks may be better suited for lazy loading rather than being included in the main bundle.
### Nuxt DevTools
The [Nuxt DevTools](https://devtools.nuxt.com/) gives you insights and transparency about your Nuxt App to identify performance gaps and seamlessly manage your app configurations.
![Nuxt DevTools example](https://user-images.githubusercontent.com/11247099/217670806-fb39aeff-3881-44e5-b9c8-6c757f5925fc.png)
It comes with several features we can use to measure performance of Nuxt apps:
1. **Timeline** Tracks time spent on rendering, updating, and initializing components to identify performance bottlenecks.
2. **Assets** Displays file sizes (e.g., images) without transformations.
3. **Render Tree** Shows connections between Vue components, scripts, and styles to optimize dynamic loading.
4. **Inspect** Lists all files used in the Vue app with their size and evaluation time.
### Chrome DevTools
Chrome DevTools come with two useful tabs for measuring performance; `Performance` and `Lighthouse`.
When you open the [Performance](https://developer.chrome.com/docs/devtools/performance/overview) panel, it instantly shows your local **Largest Contentful Paint (LCP)** and **Cumulative Layout Shift (CLS)** scores (good, needs improvement, or bad).
If you interact with the page, it also captures **Interaction to Next Paint (INP)**, giving you a full view of your Core Web Vitals based on your device and network.
![Chrome DevTools Performance Panel](https://developer.chrome.com/static/docs/devtools/performance/image/cpu-throttling_856.png)
[Lighthouse](https://developer.chrome.com/docs/devtools/lighthouse) audits performance, accessibility, SEO, progressive web apps, and best practices. It runs tests on your page and generates a report. Use failing audits as a guide to improve your site.
![Lighthouse](https://developer.chrome.com/static/docs/lighthouse/images/lighthouse-overview_720.png)
Each audit has a reference document explaining why the audit is important, as well as how to fix it.
### PageSpeed Insights
[PageSpeed Insights (PSI)](https://developers.google.com/speed/docs/insights/v5/about) reports on the user experience of a page on both mobile and desktop devices, and provides suggestions on how that page may be improved.
It provides both lab and field data about a page. Lab data is useful for debugging issues, as it is collected in a controlled environment while field data is useful for capturing true, real-world user experience.
### Web Page Test
[WebPageTest](https://www.webpagetest.org/) is a web performance tool providing deep diagnostic information about how a page performs under a variety of conditions.
Each test can be run from different locations around the world, on real browsers, over any number of customizable network conditions.
## Common problems
When building more complex Nuxt applications, you will probably encounter some of the problems listed below. Understanding these problems and fixing them will help you improve performance of your website.
### Overusing plugins
**Problem**: A large number of plugins can cause performance issues, especially if they require expensive computations or take too long to initialize. Since plugins run during the hydration phase, inefficient setups can block rendering and degrade the user experience.
**Solution**: Inspect your plugins and see if some of them could be implemented rather as a composable or utility function instead.
### Unused code / dependencies
**Problem**: With the development of the project, there can be a case where there will be some unused code or a dependency. This additional functionality may not be used or needed while it will be increase the bundle size of our project.
**Solution**: Inspect your `package.json` for unused dependencies and analyze your code for unused utils/composables/functions.
### Not using Vue Performance tips
**Problem**: [Vue documentation](https://vuejs.org/guide/best-practices/performance) lists several Performance improvements we can use in our Nuxt projects as well but as they are part of Vue documentation, developers tend to forget about it and focus on Nuxt specific improvements only - while Nuxt application is still a Vue project.
**Solution**: Use concepts such as `shallowRef`, `v-memo`, `v-once`, etc to improve performance.
### Not following patterns
**Problem**: The more people are currently working on the project, the more difficult it will be to maintain the stable codebase. Developers have a tendency to introduce new concepts they've seen in another project which can cause conflicts and problems with performance.
**Solution**: Establish rules and patterns in the project such as [Good practices and Design Patterns for Vue Composables](https://dev.to/jacobandrewsky/good-practices-and-design-patterns-for-vue-composables-24lk)
### Trying to load everything at the same time
**Problem**: When a page is loaded and it is not correctly instructed about the order of loading elements it will result in fetching everything at the same time - which can be slow and result in bad User Experience.
**Solution**: Use concepts such as Progressive Enhancement where core webpage content is set first, then more nuanced and technically rigorous layers of presentation and features are added on top as the browser/internet connection allow.
## Useful Resources
To learn more about various techniques for improving performance, take a look at the following resources:
1. [Apply instant loading with the PRPL pattern](https://web.dev/articles/apply-instant-loading-with-prpl)
2. [Perceived performance](https://developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Performance/Perceived_performance)
3. [Understanding Critical Rendering Path](https://developer.mozilla.org/en-US/docs/Web/Performance/Guides/Critical_rendering_path)

View File

@@ -0,0 +1,20 @@
---
navigation.title: 'Nuxt Plugins'
title: Nuxt Plugins
description: Best practices when using Nuxt plugins.
---
Plugins in Nuxt allow you to extend your application with additional functionality. However, improper use can lead to performance bottlenecks. This guide outlines best practices to optimize your Nuxt plugins.
## Avoid costly plugin setup
A large number of plugins can cause performance issues, especially if they require expensive computations or take too long to initialize. Since plugins run during the hydration phase, inefficient setups can block rendering and degrade the user experience.
## Use Composition whenever possible
Whenever possible, favor composition over plugins. Just like in Vue, many utilities and composables can be used directly without the need for a plugin. This keeps your project lightweight and improves maintainability.
## If `async`, enable `parallel`
By default, all plugins loads synchronously.
When defining asynchronous plugins, setting `parallel: true` allows multiple plugins to load concurrently, improving performance by preventing blocking operations.

View File

@@ -0,0 +1,3 @@
title: 'Working with AI'
titleTemplate: 'Working with AI: %s'
icon: i-lucide-bot

View File

@@ -0,0 +1,277 @@
---
title: Nuxt MCP Server
description: Use Nuxt documentation in your AI assistants with Model Context Protocol support.
navigation.title: MCP Server
---
## What is MCP?
MCP (Model Context Protocol) is a standardized protocol that enables AI assistants to access external data sources and tools. Nuxt provides an MCP server that allows AI assistants like Claude Code, Cursor, and Windsurf to access documentation, blog posts, and deployment guides directly.
The MCP server provides structured access to the Nuxt documentation, making it easy for AI tools to understand and assist with Nuxt development.
## Resources
The Nuxt MCP server provides the following resources for discovery:
- **`resource://nuxt-com/documentation-pages`**: Browse all available documentation pages (defaults to v4.x)
- **`resource://nuxt-com/blog-posts`**: Browse all Nuxt blog posts including releases and tutorials
- **`resource://nuxt-com/deploy-providers`**: Browse all deployment providers and hosting platforms
You're able to access these resources with tools like Claude Code by using `@`.
## Tools
The Nuxt MCP server provides the following tools organized by category:
### Documentation
- **`list_documentation_pages`**: Lists all available Nuxt documentation pages with their categories and basic information. Supports version filtering (3.x, 4.x, or all)
- **`get_documentation_page`**: Retrieves documentation page content and details by path
- **`get_getting_started_guide`**: Gets the getting started guide for a specific Nuxt version
### Blog
- **`list_blog_posts`**: Lists all Nuxt blog posts with metadata including dates, categories, and tags
- **`get_blog_post`**: Retrieves blog post content and details by path
### Deployment
- **`list_deploy_providers`**: Lists all deployment providers and hosting platforms for Nuxt applications
- **`get_deploy_provider`**: Retrieves deployment provider details and instructions by path
## Prompts
The Nuxt MCP server provides guided prompts for common workflows:
- **`find_documentation_for_topic`**: Find the best Nuxt documentation for a specific topic or feature
- **`deployment_guide`**: Get deployment instructions for a specific hosting provider
- **`migration_help`**: Get help with migrating between Nuxt versions
You're able to access these resources with tools like Claude Code by using `/`.
## Setup
The Nuxt MCP server uses HTTP transport and can be installed in different AI assistants.
### ChatGPT
::note{icon="i-lucide-info"}
**Custom connectors using MCP are available on ChatGPT for Pro and Plus accounts** on the web.
::
Follow these steps to set up Nuxt as a connector within ChatGPT:
1. **Enable Developer mode:**
- Go to Settings → Connectors → Advanced settings → Developer mode
2. **Open ChatGPT settings**
3. **In the Connectors tab, Create a new connector:**
- Give it a name: `Nuxt`
- MCP server URL: `https://nuxt.com/mcp`
- Authentication: `None`
4. **Click Create**
The Nuxt connector will appear in the composer's "Developer mode" tool later during conversations.
### Claude Code
::note{icon="i-lucide-info"}
**Ensure Claude Code is installed** - Visit [Anthropic's documentation](https://code.claude.com/docs/en/quickstart) for installation instructions.
::
Add the server using the CLI command:
```bash
claude mcp add --transport http nuxt-remote https://nuxt.com/mcp
```
### Claude Desktop
#### Setup Instructions
1. Open Claude Desktop and navigate to "Settings" > "Developer".
2. Click on "Edit Config". This will open the local Claude directory.
3. Modify the `claude_desktop_config.json` file with your custom MCP server configuration.
```json [claude_desktop_config.json]
{
"mcpServers": {
"nuxt": {
"command": "npx",
"args": [
"mcp-remote",
"https://nuxt.com/mcp"
]
}
}
}
```
4. Restart Claude Desktop app. The Nuxt MCP server should now be registered.
### Cursor
Click the button below to install the Nuxt MCP server directly in Cursor:
::u-button
---
to: "cursor://anysphere.cursor-deeplink/mcp/install?name=nuxt&config=eyJ0eXBlIjoiaHR0cCIsInVybCI6Imh0dHBzOi8vbnV4dC5jb20vbWNwIn0%3D"
label: Install MCP Server
color: neutral
icon: i-custom-cursor
---
::
For manual setup, follow these steps:
1. Open Cursor and go to "Settings" > "Tools & MCP"
2. Add the Nuxt MCP server configuration
Or manually create/update `.cursor/mcp.json` in your project root:
```json [.cursor/mcp.json]
{
"mcpServers": {
"nuxt": {
"type": "http",
"url": "https://nuxt.com/mcp"
}
}
}
```
### Le Chat Mistral
1. Navigate to "Intelligence" > "Connectors"
2. Click on "Add Connector" button, then select "Custom MCP Connector"
3. Create your Custom MCP Connector:
- Connector Name : `Nuxt`
- Connector Server : `https://nuxt.com/mcp`
### Visual Studio Code
::note{icon="i-lucide-info"}
**Install required extensions** - Ensure you have [GitHub Copilot](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot) and [GitHub Copilot Chat](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot-chat) extensions installed.
::
1. Open VS Code and access the Command Palette (Ctrl/Cmd + Shift + P)
2. Type "Preferences: Open Workspace Settings (JSON)" and select it
3. Navigate to your project's `.vscode` folder or create one if it doesn't exist
4. Create or edit the `mcp.json` file with the following configuration:
```json [.vscode/mcp.json]
{
"servers": {
"nuxt": {
"type": "http",
"url": "https://nuxt.com/mcp"
}
}
}
```
### GitHub Copilot Agent
::note{icon="i-lucide-info"}
**Repository administrator access required** to configure MCP servers for GitHub Copilot coding agent.
::
If you have already configured MCP servers in VS Code (replace the `servers` key with `mcpServers` for GitHub Copilot Agent), you can leverage a similar configuration for GitHub Copilot coding agent. You will need to add a `tools` key specifying which tools are available to Copilot.
1. Navigate to your GitHub repository
2. Go to **Settings** > **Code & automation** > **Copilot** > **Coding agent**
3. In the **MCP configuration** section, add the following configuration:
```json
{
"mcpServers": {
"nuxt": {
"type": "http",
"url": "https://nuxt.com/mcp",
"tools": ["*"]
}
}
}
```
4. Click **Save**
#### Validating the Configuration
To verify the MCP server is configured correctly:
1. Create an issue in your repository and assign it to Copilot
2. Wait for Copilot to create a pull request
3. In the pull request, click **View session** in the "Copilot started work" timeline event
4. Click the ellipsis button (**...**) at the top right, then click **Copilot** in the sidebar
5. Expand the **Start MCP Servers** step to see the configured Nuxt tools
For more information on using MCP with GitHub Copilot coding agent, see [Extend coding agent with MCP](https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/extend-coding-agent-with-mcp).
### Windsurf
1. Open Windsurf and navigate to "Settings" > "Windsurf Settings" > "Cascade"
2. Click the "Manage MCPs" button, then select the "View raw config" option
3. Add the following configuration to your MCP settings:
```json [.codeium/windsurf/mcp_config.json]
{
"mcpServers": {
"nuxt": {
"type": "http",
"url": "https://nuxt.com/mcp"
}
}
}
```
### Zed
1. Open Zed and go to "Settings" > "Open Settings"
2. Navigate to the JSON settings file
3. Add the following context server configuration to your settings:
```json [.config/zed/settings.json]
{
"context_servers": {
"nuxt": {
"source": "custom",
"command": "npx",
"args": ["mcp-remote", "https://nuxt.com/mcp"],
"env": {}
}
}
}
```
### Opencode
1. In your project root, create `opencode.json`
2. Add the following configuration:
```json
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"nuxt": {
"type": "remote",
"url": "https://nuxt.com/mcp",
"enabled": true
}
}
}
```
## Prompts Examples
Once configured, you can ask your AI assistant questions like:
- "List all available Nuxt documentation pages"
- "Get the introduction documentation"
- "What's the difference between v3 and v4?"
- "How do I deploy to Vercel?"
- "Show me the latest blog posts"
- "Help me migrate from Nuxt 3 to Nuxt 4"
- "Search documentation about composables"
- "Find deployment guides for Cloudflare"
The AI assistant will use the MCP server to fetch structured JSON data and provide guided assistance for Nuxt development.

View File

@@ -0,0 +1,65 @@
---
title: Nuxt LLMs.txt
description: How to get AI tools like Cursor, Windsurf, GitHub Copilot, ChatGPT, and Claude to understand Nuxt concepts, APIs, and best practices.
navigation.title: LLMs.txt
---
## What is LLMs.txt?
LLMs.txt is a structured documentation format specifically designed for large language models (LLMs). Nuxt provides LLMs.txt files that contain comprehensive information about the framework, making it easy for AI tools to understand and assist with Nuxt development.
These files are optimized for AI consumption and contain structured information about concepts, APIs, usage patterns, and best practices.
## Available routes
We provide LLMs.txt routes to help AI tools access our documentation:
- **`/llms.txt`** - Contains a structured overview of all documentation pages and their links (~5K tokens)
- **`/llms-full.txt`** - Provides comprehensive documentation including getting started guides, API references, blog posts, and deployment guides (~1M+ tokens)
## Choosing the Right File
::note{icon="i-lucide-info"}
**Most users should start with `/llms.txt`** - it contains all essential information and works with standard LLM context windows.
Use `/llms-full.txt` only if you need comprehensive implementation details and your AI tool supports large contexts (200K+ tokens).
::
## Important usage notes
::warning{icon="i-lucide-alert-triangle"}
**@-symbol must be typed manually** - When using tools like Cursor or Windsurf, the `@` symbol must be typed by hand in the chat interface. Copy-pasting breaks the tool's ability to recognize it as a context reference.
::
## Usage with AI Tools
### Cursor
Nuxt provides specialized LLMs.txt files that you can reference in Cursor for better AI assistance with Nuxt development.
#### How to use
1. **Direct reference**: Mention the LLMs.txt URLs when asking questions
2. Add these specific URLs to your project context using `@docs`
[Read more about Cursor Web and Docs Search](https://cursor.com/docs/context/mentions)
### Windsurf
Windsurf can directly access the Nuxt LLMs.txt files to understand framework usage and best practices.
#### Using LLMs.txt with Windsurf
- Use `@docs` to reference specific LLMs.txt URLs
- Create persistent rules referencing these URLs in your workspace
[Read more about Windsurf Web and Docs Search](https://docs.windsurf.com/windsurf/cascade/web-search)
### Other AI Tools
Any AI tool that supports LLMs.txt can use these routes to better understand Nuxt.
#### Examples for ChatGPT, Claude, or other LLMs
- "Using Nuxt documentation from https://nuxt.com/llms.txt"
- "Follow complete Nuxt guidelines from https://nuxt.com/llms-full.txt"

View File

@@ -0,0 +1,3 @@
title: Module Author Guide
titleTemplate: '%s · Nuxt Modules Author Guide'
icon: i-lucide-box

View File

@@ -0,0 +1,103 @@
---
title: "Create Your First Module"
description: "Learn how to create your first Nuxt module using the official starter template."
---
## Create a Module
We recommend you get started with Nuxt modules using our [starter template](https://github.com/nuxt/starter/tree/module):
::code-group{sync="pm"}
```bash [npm]
npm create nuxt -- -t module my-module
```
```bash [yarn]
yarn create nuxt -t module my-module
```
```bash [pnpm]
pnpm create nuxt -t module my-module
```
```bash [bun]
bun create nuxt --template=module my-module
```
::
This will create a `my-module` project with all the boilerplate necessary to develop and publish your module.
**Next steps:**
1. Open `my-module` in your IDE of choice
2. Install dependencies using your favorite package manager
3. Prepare local files for development using `npm run dev:prepare`
4. Follow this document to learn more about Nuxt modules
## Use the Starter Template
Learn how to perform basic tasks with the module starter.
::tip{icon="i-lucide-video" to="https://vueschool.io/lessons/navigating-the-official-starter-template?friend=nuxt" target="_blank"}
Watch Vue School video about Nuxt module starter template.
::
### Develop Your Module
While your module source code lives inside the `src` directory, to develop a module you often need a Nuxt application to test it against. That's what the `playground` directory is for. It's a Nuxt application you can tinker with that is already configured to run with your module.
You can interact with the playground like with any Nuxt application.
- Launch its development server with `npm run dev`, it should reload itself as you make changes to your module in the `src` directory
- Build it with `npm run dev:build`
::note
All other `nuxt` commands can be used against the `playground` directory (e.g. `nuxt <COMMAND> playground`). Feel free to declare additional `dev:*` scripts within your `package.json` referencing them for convenience.
::
### Run Tests
The module starter comes with a basic test suite:
- A linter powered by [ESLint](https://eslint.org), run it with `npm run lint`
- A test runner powered by [Vitest](https://vitest.dev), run it with `npm run test` or `npm run test:watch`
::tip
Feel free to augment this default test strategy to better suit your needs.
::
### Build Your Module
Nuxt modules come with their own builder provided by [`@nuxt/module-builder`](https://github.com/nuxt/module-builder#readme). This builder doesn't require any configuration on your end, supports TypeScript, and makes sure your assets are properly bundled to be distributed to other Nuxt applications.
You can build your module by running `npm run prepack`.
::tip
While building your module can be useful in some cases, most of the time you won't need to build it on your own: the `playground` takes care of it while developing, and the release script also has you covered when publishing.
::
### Publish to npm
::important
Before publishing your module to npm, makes sure you have an [npmjs.com](https://www.npmjs.com) account and that you're authenticated to it locally with `npm login`.
::
While you can publish your module by bumping its version and using the `npm publish` command, the module starter comes with a release script that helps you make sure you publish a working version of your module to npm and more.
To use the release script, first, commit all your changes (we recommend you follow [Conventional Commits](https://www.conventionalcommits.org) to also take advantage of automatic version bump and changelog update), then run the release script with `npm run release`.
When running the release script, the following will happen:
- First, it will run your test suite by:
- Running the linter (`npm run lint`)
- Running unit, integration, and e2e tests (`npm run test`)
- Building the module (`npm run prepack`)
- Then, if your test suite went well, it will proceed to publish your module by:
- Bumping your module version and generating a changelog according to your Conventional Commits
- Publishing the module to npm (for that purpose, the module will be built again to ensure its updated version number is taken into account in the published artifact)
- Pushing a git tag representing the newly published version to your git remote origin
::tip
As with other scripts, feel free to fine-tune the default `release` script in your `package.json` to better suit your needs.
::

View File

@@ -0,0 +1,138 @@
---
title: "Understand Module Structure"
description: "Learn how Nuxt modules are structured and how to define them."
---
There are two types of Nuxt modules:
- published modules are distributed on npm - you can see a list of some community modules on [the Nuxt website](/modules).
- "local" modules exist within a Nuxt project, either [inlined in Nuxt config](/docs/4.x/api/nuxt-config#modules) or within [the `modules` directory](/docs/4.x/directory-structure/modules).
In either case, they work in the same way.
## Define Your Module
::note
When using the starter, your module definition is available at `src/module.ts`.
::
The module definition is the entry point of your module. It's what gets loaded by Nuxt when your module is referenced within a Nuxt configuration.
At a low level, a Nuxt module definition is a simple, potentially asynchronous, function accepting inline user options and a `nuxt` object to interact with Nuxt.
```ts
export default function (inlineOptions, nuxt) {
// You can do whatever you like here..
console.log(inlineOptions.token) // `123`
console.log(nuxt.options.dev) // `true` or `false`
nuxt.hook('ready', (nuxt) => {
console.log('Nuxt is ready')
})
}
```
You can get type hinting for this function using the higher-level `defineNuxtModule` helper provided by [Nuxt Kit](/docs/4.x/guide/going-further/kit).
```ts
import { defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule((options, nuxt) => {
nuxt.hook('pages:extend', (pages) => {
console.log(`Discovered ${pages.length} pages`)
})
})
```
However, **we do not recommend** using this low-level function definition. Instead, to define a module, **we recommend** using the object-syntax with `meta` property to identify your module, especially when publishing to npm.
This helper makes writing Nuxt modules more straightforward by implementing many common patterns needed by modules, guaranteeing future compatibility and improving the experience for both module authors and users.
```ts
import { defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
meta: {
// Usually the npm package name of your module
name: '@nuxtjs/example',
// The key in `nuxt.config` that holds your module options
configKey: 'sample',
// Compatibility constraints
compatibility: {
// Semver version of supported nuxt versions
nuxt: '>=3.0.0',
},
},
// Default configuration options for your module, can also be a function returning those
defaults: {},
// Shorthand sugar to register Nuxt hooks
hooks: {},
// Configuration for other modules - this does not ensure the module runs before
// your module, but it allows you to change the other module's configuration before it runs
moduleDependencies: {
'some-module': {
// You can specify a version constraint for the module. If the user has a different
// version installed, Nuxt will throw an error on startup.
version: '>=2',
// By default moduleDependencies will be added to the list of modules to be installed
// by Nuxt unless `optional` is set.
optional: true,
// Any configuration that should override `nuxt.options`.
overrides: {},
// Any configuration that should be set. It will override module defaults but
// will not override any configuration set in `nuxt.options`.
defaults: {},
},
},
// The function holding your module logic, it can be asynchronous
setup (moduleOptions, nuxt) {
// ...
},
})
```
`defineNuxtModule` returns a wrapper function with the lower level `(inlineOptions, nuxt)` module signature. This wrapper function applies defaults and other necessary steps before calling your `setup` function:
- Support `defaults` and `meta.configKey` for automatically merging module options
- Type hints and automated type inference
- Ensure module gets installed only once using a unique key computed from `meta.name` or `meta.configKey`
- Automatically register Nuxt hooks
- Automatically check for compatibility issues based on module meta
- Expose `getOptions` and `getMeta` for internal usage of Nuxt
- Ensuring backward and upward compatibility as long as the module is using `defineNuxtModule` from the latest version of `@nuxt/kit`
- Integration with module builder tooling
## Add Runtime Code
::note
When using the starter, the runtime directory is `src/runtime/`.
::
Modules, like everything in a Nuxt configuration, aren't included in your application runtime. However, you might want your module to provide, or inject runtime code to the application it's installed on. That's what the runtime directory enables you to do.
Inside the runtime directory, you can provide any kind of assets related to the Nuxt app:
- Vue components
- Composables
- [Nuxt plugins](/docs/4.x/directory-structure/app/plugins)
To the [server engine](/docs/4.x/guide/concepts/server-engine), Nitro:
- API routes
- Middlewares
- Nitro plugins
Or any other kind of asset you want to inject in users' Nuxt applications:
- Stylesheets
- 3D models
- Images
- etc.
You'll then be able to inject all those assets inside the application from your [module definition](#define-your-module).
::tip
Learn more about asset injection in [the recipes section](/docs/4.x/guide/modules/recipes-basics).
::
::warning
Published modules cannot leverage auto-imports for assets within their runtime directory. Instead, they have to import them explicitly from `#imports` or alike.
:br :br
Auto-imports are not enabled for files within `node_modules` (the location where a published module will eventually live) for performance reasons.
::

View File

@@ -0,0 +1,439 @@
---
title: "Add Plugins, Components & More"
description: "Learn how to inject plugins, components, composables and server routes from your module."
---
Here are some common patterns used by module authors.
## Modify Nuxt Configuration
Nuxt configuration can be read and altered by modules. Here's an example of a module enabling an experimental feature.
```js
import { defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
// We create the `experimental` object if it doesn't exist yet
nuxt.options.experimental ||= {}
nuxt.options.experimental.componentIslands = true
},
})
```
When you need to handle more complex configuration alterations, you should consider using [defu](https://github.com/unjs/defu).
::tip{icon="i-lucide-video" to="https://vueschool.io/lessons/extending-and-altering-nuxt-configuration-and-options?friend=nuxt" target="_blank"}
Watch Vue School video about altering Nuxt configuration.
::
## Expose Options to Runtime
Because modules aren't part of the application runtime, their options aren't either. However, in many cases, you might need access to some of these module options within your runtime code. We recommend exposing the needed config using Nuxt's [`runtimeConfig`](/docs/4.x/api/nuxt-config#runtimeconfig).
<!-- TODO: Update after #18466 (or equivalent) -->
```js
import { defineNuxtModule } from '@nuxt/kit'
import { defu } from 'defu'
export default defineNuxtModule({
setup (options, nuxt) {
nuxt.options.runtimeConfig.public.myModule = defu(nuxt.options.runtimeConfig.public.myModule, {
foo: options.foo,
})
},
})
```
Note that we use [`defu`](https://github.com/unjs/defu) to extend the public runtime configuration the user provides instead of overwriting it.
You can then access your module options in a plugin, component, the application like any other runtime configuration:
```js
import { useRuntimeConfig } from '@nuxt/kit'
const options = useRuntimeConfig().public.myModule
```
::warning
Be careful not to expose any sensitive module configuration on the public runtime config, such as private API keys, as they will end up in the public bundle.
::
:read-more{to="/docs/4.x/guide/going-further/runtime-config"}
::tip{icon="i-lucide-video" to="https://vueschool.io/lessons/passing-and-exposing-module-options?friend=nuxt" target="_blank"}
Watch Vue School video about passing and exposing Nuxt module options.
::
## Add Plugins
Plugins are a common way for a module to add runtime logic. You can use the `addPlugin` utility to register them from your module.
```js
import { addPlugin, createResolver, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
// Create resolver to resolve relative paths
const resolver = createResolver(import.meta.url)
addPlugin(resolver.resolve('./runtime/plugin'))
},
})
```
:read-more{to="/docs/4.x/guide/going-further/kit"}
## Add Components
If your module should provide Vue components, you can use the `addComponent` utility to add them as auto-imports for Nuxt to resolve.
```ts twoslash
import { addComponent, createResolver, defineNuxtModule, useRuntimeConfig } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
const resolver = createResolver(import.meta.url)
// From the runtime directory
addComponent({
name: 'MySuperComponent', // name of the component to be used in vue templates
export: 'MySuperComponent', // (optional) if the component is a named (rather than default) export
filePath: resolver.resolve('runtime/app/components/MySuperComponent.vue'),
})
// From a library
addComponent({
name: 'MyAwesomeComponent', // name of the component to be used in vue templates
export: 'MyAwesomeComponent', // (optional) if the component is a named (rather than default) export
filePath: '@vue/awesome-components',
})
},
})
```
Alternatively, you can add an entire directory by using `addComponentsDir`.
```ts
import { addComponentsDir, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
const resolver = createResolver(import.meta.url)
addComponentsDir({
path: resolver.resolve('runtime/app/components'),
})
},
})
```
::tip{icon="i-lucide-lightbulb"}
It is highly recommended to prefix your exports to avoid conflicts with user code or other modules.
:read-more{to="/docs/4.x/guide/modules/best-practices#prefix-your-exports"}
::
::note
Note that all components, pages, composables and other files that would be normally placed in your `app/` folder need to be in `runtime/app/`. This will mean they can be type checked properly.
::
## Add Composables
If your module should provide composables, you can use the `addImports` utility to add them as auto-imports for Nuxt to resolve.
```ts
import { addImports, createResolver, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
const resolver = createResolver(import.meta.url)
addImports({
name: 'useComposable', // name of the composable to be used
as: 'useMyComposable', // optional alias that will be available for the consuming apps
from: resolver.resolve('runtime/app/composables/useComposable'), // path of composable
})
},
})
```
Multiple entries can be passed as an array:
```ts
import { addImports, createResolver, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
const resolver = createResolver(import.meta.url)
addImports([
{ name: 'useFirstComposable', from: resolver.resolve('runtime/composables/useFirstComposable') },
{ name: 'useSecondComposable', from: resolver.resolve('runtime/composables/useSecondComposable') },
])
},
})
```
Alternatively, you can add an entire directory by using `addImportsDir`.
```ts
import { addImportsDir, createResolver, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
const resolver = createResolver(import.meta.url)
addImportsDir(resolver.resolve('runtime/composables'))
},
})
```
::tip{icon="i-lucide-lightbulb"}
It is highly recommended to prefix your exports to avoid conflicts with user code or other modules.
:read-more{to="/docs/4.x/guide/modules/best-practices#prefix-your-exports"}
::
::note
Note that all components, pages, composables and other files that would be normally placed in your `app/` folder need to be in `runtime/app/`. This will mean they can be type checked properly.
::
### Add Keyed Functions
Sometimes, you may need to maintain state consistency between the server and the client. Examples include Nuxt's built-in `useState` or `useAsyncData` composables. Nuxt provides a way to register such functions for automatic key injection.
When a function is registered, Nuxts compiler automatically injects a unique key as an additional argument if the function is called with fewer than the specified number of arguments. This key remains stable between server-side rendering and client hydration.
::tip
The injected key is a hash derived from the file path and call location.
::
Use the `keyedComposables` option to register your function:
```ts
import { createResolver, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
const resolver = createResolver(import.meta.url)
nuxt.options.optimization.keyedComposables.push({
name: 'useMyState',
source: resolver.resolve('./runtime/composables/state'),
argumentLength: 2,
})
},
})
```
The `keyedComposables` configuration accepts an array of objects with the following properties:
| Property | Type | Description |
|------------------|----------|----------------------------------------------------------------------------------------------------------------------------|
| `name` | `string` | The function name. Use `'default'` for default exports (the callable name will be derived from the filename in camelCase). |
| `source` | `string` | Resolved path to the file where the function is defined. Supports Nuxt aliases (`~`, `@`, etc.) |
| `argumentLength` | `number` | Maximum number of arguments the function accepts. When called with fewer arguments, a unique key is injected. |
For example, with `argumentLength: 2`:
```ts
useMyState() // useMyState('$HJiaryoL2y')
useMyState('myKey') // useMyState('myKey', '$HJiaryoL2y')
useMyState('a', 'b') // not transformed (already has 2 arguments)
```
::warning
The key injection plugin verifies the exact resolved import source of each function call. It does not follow barrel exports. The function must be exported from the exact source file specified in the `source` property.
```ts
// ✅ Works - direct import matches the configured source
import { useMyState } from 'my-module/runtime/composables/state'
// ❌ Won't work - re-exported through a barrel file
import { useMyState } from 'my-module/runtime/composables' // index.ts barrel
```
::
::warning
The function call must be statically analyzable. The compiler cannot inject keys for dynamic or indirect function calls.
```ts
import { useMyState } from 'my-module/runtime/composables/state'
import * as composables from 'my-module/runtime/composables/state'
// ✅ Works - direct function call
useMyState()
// ✅ Works - called on namespace import
composables.useMyState()
// ❌ Won't work - dynamic property access
const name = 'useMyState'
composables[name]()
// ❌ Won't work - reassigned to a variable
const myFn = useMyState
myFn()
// ❌ Won't work - passed as a callback
someFunction(useMyState)
// ❌ Won't work - destructured with renaming in a nested scope
function setup () {
const { useMyState: localState } = composables
localState() // not transformed
}
// ...
```
::
## Add Route Middleware
```ts
import { addRouteMiddleware, createResolver, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
const resolver = createResolver(import.meta.url)
// example to add a global route middleware
addRouteMiddleware({
global: true,
name: 'name-of-your-middleware',
path: resolver.resolve('./runtime/middleware/name-of-your-middleware'),
})
},
})
```
## Add Server Routes
```ts
import { addServerHandler, createResolver, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
const resolver = createResolver(import.meta.url)
addServerHandler({
route: '/api/_my-module/hello',
handler: resolver.resolve('./runtime/server/api/hello/index.get'),
})
},
})
```
You can also add a dynamic server route:
```ts
import { addServerHandler, createResolver, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
const resolver = createResolver(import.meta.url)
addServerHandler({
route: '/api/_my-module/hello/:name',
handler: resolver.resolve('./runtime/server/api/hello/[name].get'),
})
// Or using a catch all route
addServerHandler({
route: '/api/_my-module/files/**:path',
handler: resolver.resolve('./runtime/server/api/files/[...path].get'),
})
},
})
```
::tip{icon="i-lucide-lightbulb"}
It is highly recommended to prefix your server routes to avoid conflicts with user-defined routes. Common paths like `/api/auth`, `/api/login`, or `/api/user` may already be used by the application.
:read-more{to="/docs/4.x/guide/modules/best-practices#prefix-your-exports"}
::
## Add Other Assets
If your module should provide other kinds of assets, they can also be injected. Here's a simple example module injecting a stylesheet through Nuxt's `css` array.
```js
import { addPlugin, createResolver, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
const resolver = createResolver(import.meta.url)
nuxt.options.css.push(resolver.resolve('./runtime/style.css'))
},
})
```
And a more advanced one, exposing a folder of assets through [Nitro](/docs/4.x/guide/concepts/server-engine)'s `publicAssets` option:
```js
import { createResolver, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
const resolver = createResolver(import.meta.url)
nuxt.hook('nitro:config', (nitroConfig) => {
nitroConfig.publicAssets ||= []
nitroConfig.publicAssets.push({
dir: resolver.resolve('./runtime/public'),
maxAge: 60 * 60 * 24 * 365, // 1 year
})
})
},
})
```
## Use Other Modules
If your module depends on other modules, you can specify them using the `moduleDependencies` option. This provides a more robust way to handle module dependencies with version constraints and configuration merging:
```ts
import { createResolver, defineNuxtModule } from '@nuxt/kit'
const resolver = createResolver(import.meta.url)
export default defineNuxtModule<ModuleOptions>({
meta: {
name: 'my-module',
},
moduleDependencies: {
'@nuxtjs/tailwindcss': {
// You can specify a version constraint for the module
version: '>=6',
// Any configuration that should override `nuxt.options`
overrides: {
exposeConfig: true,
},
// Any configuration that should be set. It will override module defaults but
// will not override any configuration set in `nuxt.options`
defaults: {
config: {
darkMode: 'class',
content: {
files: [
resolver.resolve('./runtime/components/**/*.{vue,mjs,ts}'),
resolver.resolve('./runtime/*.{mjs,js,ts}'),
],
},
},
},
},
},
setup (options, nuxt) {
// We can inject our CSS file which includes Tailwind's directives
nuxt.options.css.push(resolver.resolve('./runtime/assets/styles.css'))
},
})
```
::callout{type="info"}
The `moduleDependencies` option replaces the deprecated `installModule` function and ensures proper setup order and configuration merging.
::

View File

@@ -0,0 +1,243 @@
---
title: "Use Hooks & Extend Types"
description: "Master lifecycle hooks, virtual files and TypeScript declarations in your modules."
---
Here are some advanced patterns for authoring modules, including hooks, templates, and type augmentation.
## Use Lifecycle Hooks
[Lifecycle hooks](/docs/4.x/guide/going-further/hooks) allow you to expand almost every aspect of Nuxt. Modules can hook to them programmatically or through the `hooks` map in their definition.
```js
import { addPlugin, createResolver, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
// Hook to the `app:error` hook through the `hooks` map
hooks: {
'app:error': (err) => {
console.info(`This error happened: ${err}`)
},
},
setup (options, nuxt) {
// Programmatically hook to the `pages:extend` hook
nuxt.hook('pages:extend', (pages) => {
console.info(`Discovered ${pages.length} pages`)
})
},
})
```
:read-more{to="/docs/4.x/api/advanced/hooks"}
::tip{icon="i-lucide-video" to="https://vueschool.io/lessons/nuxt-lifecycle-hooks?friend=nuxt" target="_blank"}
Watch Vue School video about using Nuxt lifecycle hooks in modules.
::
::note
**Module cleanup**
:br
:br
If your module opens, handles, or starts a watcher, you should close it when the Nuxt lifecycle is done. The `close` hook is available for this.
```ts
import { defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
nuxt.hook('close', async (nuxt) => {
// Your custom code here
})
},
})
```
::
### Create Custom Hooks
Modules can also define and call their own hooks, which is a powerful pattern for making your module extensible.
If you expect other modules to be able to subscribe to your module's hooks, you should call them in the `modules:done` hook. This ensures that all other modules have had a chance to be set up and register their listeners to your hook during their own `setup` function.
```ts
// my-module/module.ts
import { defineNuxtModule } from '@nuxt/kit'
export interface ModuleHooks {
'my-module:custom-hook': (payload: { foo: string }) => void
}
export default defineNuxtModule({
setup (options, nuxt) {
// Call your hook in `modules:done`
nuxt.hook('modules:done', async () => {
const payload = { foo: 'bar' }
await nuxt.callHook('my-module:custom-hook', payload)
})
},
})
```
## Add Virtual Files
If you need to add a virtual file that can be imported into the user's app, you can use the `addTemplate` utility.
```ts
import { addTemplate, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
// The file is added to Nuxt's internal virtual file system and can be imported from '#build/my-module-feature.mjs'
addTemplate({
filename: 'my-module-feature.mjs',
getContents: () => 'export const myModuleFeature = () => "hello world !"',
})
},
})
```
For the server, you should use the `addServerTemplate` utility instead.
```ts
import { addServerTemplate, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
// The file is added to Nitro's virtual file system and can be imported in the server code from 'my-server-module.mjs'
addServerTemplate({
filename: 'my-server-module.mjs',
getContents: () => 'export const myServerModule = () => "hello world !"',
})
},
})
```
## Update Virtual Files
If you need to update your templates/virtual files, you can leverage the `updateTemplates` utility like this:
```ts
nuxt.hook('builder:watch', (event, path) => {
if (path.includes('my-module-feature.config')) {
// This will reload the template that you registered
updateTemplates({ filter: t => t.filename === 'my-module-feature.mjs' })
}
})
```
## Add Type Declarations
You might also want to add a type declaration to the user's project (for example, to augment a Nuxt interface
or provide a global type of your own). For this, Nuxt provides the `addTypeTemplate` utility that both
writes a template to the disk and adds a reference to it in the generated `nuxt.d.ts` file.
If your module should augment types handled by Nuxt, you can use `addTypeTemplate` to perform this operation:
```js
import { addTemplate, addTypeTemplate, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
addTypeTemplate({
filename: 'types/my-module.d.ts',
getContents: () => `// Generated by my-module
interface MyModuleNitroRules {
myModule?: { foo: 'bar' }
}
declare module 'nitro/types' {
interface NitroRouteRules extends MyModuleNitroRules {}
interface NitroRouteConfig extends MyModuleNitroRules {}
}
export {}`,
})
},
})
```
If you need more granular control, you can use the `prepare:types` hook to register a callback that will inject your types.
```ts
const template = addTemplate({ /* template options */ })
nuxt.hook('prepare:types', ({ references }) => {
references.push({ path: template.dst })
})
```
## Extend TypeScript Config
There are multiple ways to extend the TypeScript configuration of the user's project from your module.
The simplest way is to modify the Nuxt configuration directly like this:
<!-- @case-police-ignore tsConfig -->
```ts
// extend tsconfig.app.json
nuxt.options.typescript.tsConfig.include ??= []
nuxt.options.typescript.tsConfig.include.push(resolve('./augments.d.ts'))
// extend tsconfig.shared.json
nuxt.options.typescript.sharedTsConfig.include ??= []
nuxt.options.typescript.sharedTsConfig.include.push(resolve('./augments.d.ts'))
// extend tsconfig.node.json
nuxt.options.typescript.nodeTsConfig.include ??= []
nuxt.options.typescript.nodeTsConfig.include.push(resolve('./augments.d.ts'))
// extend tsconfig.server.json
nuxt.options.nitro.typescript ??= {}
nuxt.options.nitro.typescript.tsConfig ??= {}
nuxt.options.nitro.typescript.tsConfig.include ??= []
nuxt.options.nitro.typescript.tsConfig.include.push(resolve('./augments.d.ts'))
```
Alternatively, you can use the `prepare:types` and `nitro:prepare:types` hooks to extend the TypeScript references for specific type contexts, or modify the TypeScript configuration similar to the example above.
```ts
nuxt.hook('prepare:types', ({ references, sharedReferences, nodeReferences }) => {
// extend app context
references.push({ path: resolve('./augments.d.ts') })
// extend shared context
sharedReferences.push({ path: resolve('./augments.d.ts') })
// extend node context
nodeReferences.push({ path: resolve('./augments.d.ts') })
})
nuxt.hook('nitro:prepare:types', ({ references }) => {
// extend server context
references.push({ path: resolve('./augments.d.ts') })
})
```
::note
TypeScript references add files to the type context [without being affected by the `exclude` option in `tsconfig.json`](https://www.typescriptlang.org/tsconfig/#exclude).
::
## Augment Types
Nuxt automatically includes your module's directories in the appropriate type contexts. To augment types from your module, all you need to do is place the type declaration file in the appropriate directory based on the augmented type context. Alternatively, you can [extend the TypeScript configuration](#extend-typescript-config) to augment from an arbitrary location.
- `my-module/runtime/` - app type context (except for the `runtime/server` directory)
- `my-module/runtime/server/` - server type context
- `my-module/` - node type context (except for the `runtime/` and `runtime/server` directories)
```bash [Directory Structure]
-| my-module/ # node type context
---| runtime/ # app type context
------| augments.app.d.ts
------| server/ # server type context
---------| augments.server.d.ts
---| module.ts
---| augments.node.d.ts
```
### Known Limitations
#### Type-Checking Server Routes in App Context
Server routes are also type-checked using `tsconfig.app.json` in addition to `tsconfig.server.json`.
This is required because Nuxt infers the return types of your server endpoints to provide response types in [`$fetch`](/docs/4.x/api/utils/dollarfetch) and [`useFetch`](/docs/4.x/api/composables/use-fetch).
::warning
This can cause issues when using **server-only types** in your route files. For example, if a module creates a server-only virtual file using [`addServerTemplate`](/docs/api/kit/templates#addservertemplate) and you declare types for it in `tsconfig.server.json`, those type declarations will only be available in the server context. When the app context type-checks your server routes, it won't recognize these server-only types and will report errors. To resolve this, you unfortunately need to declare such types in the app context as well.
::

View File

@@ -0,0 +1,76 @@
---
title: "Test Your Module"
description: "Learn how to test your Nuxt module with unit, integration and E2E tests."
---
Testing helps ensure your module works as expected given various setups. Find in this section how to perform various kinds of tests against your module.
## Write Unit Tests
::tip
We're still discussing and exploring how to ease unit and integration testing on Nuxt modules.
:br :br
[Check out this RFC to join the conversation](https://github.com/nuxt/nuxt/discussions/18399).
::
## Write E2E Tests
[Nuxt Test Utils](/docs/4.x/getting-started/testing) is the go-to library to help you test your module in an end-to-end way. Here's the workflow to adopt with it:
1. Create a Nuxt application to be used as a "fixture" inside `test/fixtures/*`
2. Setup Nuxt with this fixture inside your test file
3. Interact with the fixture using utilities from `@nuxt/test-utils` (e.g. fetching a page)
4. Perform checks related to this fixture (e.g. "HTML contains ...")
5. Repeat
In practice, the fixture:
```ts [test/fixtures/ssr/nuxt.config.ts]
// 1. Create a Nuxt application to be used as a "fixture"
import MyModule from '../../../src/module'
export default defineNuxtConfig({
ssr: true,
modules: [
MyModule,
],
})
```
And its test:
```ts [test/rendering.ts]
import { describe, expect, it } from 'vitest'
import { fileURLToPath } from 'node:url'
import { $fetch, setup } from '@nuxt/test-utils/e2e'
describe('ssr', async () => {
// 2. Setup Nuxt with this fixture inside your test file
await setup({
rootDir: fileURLToPath(new URL('./fixtures/ssr', import.meta.url)),
})
it('renders the index page', async () => {
// 3. Interact with the fixture using utilities from `@nuxt/test-utils`
const html = await $fetch('/')
// 4. Perform checks related to this fixture
expect(html).toContain('<div>ssr</div>')
})
})
// 5. Repeat
describe('csr', async () => { /* ... */ })
```
::tip
An example of such a workflow is available on [the module starter](https://github.com/nuxt/starter/blob/module/test/basic.test.ts).
::
## Test Manually
Having a playground Nuxt application to test your module when developing it is really useful. [The module starter integrates one for that purpose](/docs/4.x/guide/modules/getting-started#develop-your-module).
You can test your module with other Nuxt applications (applications that are not part of your module repository) locally. To do so, you can use [`npm pack`](https://docs.npmjs.com/cli/commands/npm-pack/) command, or your package manager equivalent, to create a tarball from your module. Then in your test project, you can add your module to `package.json` packages as: `"my-module": "file:/path/to/tarball.tgz"`.
After that, you should be able to reference `my-module` like in any regular project.

View File

@@ -0,0 +1,104 @@
---
title: "Follow Best Practices"
description: "Build performant and maintainable Nuxt modules with these guidelines."
---
With great power comes great responsibility. While modules are powerful, here are some best practices to keep in mind while authoring modules to keep applications performant and developer experience great.
## Handle Async Setup
As we've seen, Nuxt modules can be asynchronous. For example, you may want to develop a module that needs fetching some API or calling an async function.
However, be careful with asynchronous behaviors as Nuxt will wait for your module to setup before going to the next module and starting the development server, build process, etc. Prefer deferring time-consuming logic to Nuxt hooks.
::warning
If your module takes more than **1 second** to setup, Nuxt will emit a warning about it.
::
## Prefix Your Exports
Nuxt modules should provide an explicit prefix for any exposed configuration, plugin, API, composable, component, or server route to avoid conflicts with other modules, Nuxt internals, or user-defined code.
Ideally, prefix them with your module's name. For example, if your module is called `nuxt-foo`:
| Type | ❌ Avoid | ✅ Prefer |
| --- | --- | --- |
| Components | `<Button>`, `<Modal>` | `<FooButton>`, `<FooModal>` |
| Composables | `useData()`, `useModal()` | `useFooData()`, `useFooModal()` |
| Server routes | `/api/track`, `/api/data` | `/api/_foo/track`, `/api/_foo/data` |
### Server Routes
This is particularly important for server routes, where common paths like `/api/auth`, `/api/login`, or `/api/user` are very likely already used by the application.
Use a unique prefix based on your module name:
- `/api/_foo/...` (using underscore prefix)
- `/_foo/...` (for non-API routes)
## Use Lifecycle Hooks
When your module needs to perform one-time setup tasks (like generating configuration files, setting up databases, or installing dependencies), use lifecycle hooks instead of running the logic in your main `setup` function.
```ts
import { addServerHandler, defineNuxtModule } from 'nuxt/kit'
import semver from 'semver'
export default defineNuxtModule({
meta: {
name: 'my-database-module',
version: '1.0.0',
},
async onInstall (nuxt) {
// One-time setup: create database schema, generate config files, etc.
await generateDatabaseConfig(nuxt.options.rootDir)
},
async onUpgrade (nuxt, options, previousVersion) {
// Handle version-specific migrations
if (semver.lt(previousVersion, '1.0.0')) {
await migrateLegacyData()
}
},
setup (options, nuxt) {
// Regular setup logic that runs on every build
addServerHandler({ /* ... */ })
},
})
```
This pattern prevents unnecessary work on every build and provides a better developer experience. See the [lifecycle hooks documentation](/docs/4.x/api/kit/modules#using-lifecycle-hooks-for-module-installation-and-upgrade) for more details.
## Be TypeScript Friendly
Nuxt has first-class TypeScript integration for the best developer experience.
Exposing types and using TypeScript to develop modules benefits users even when not using TypeScript directly.
## Use ESM Syntax
Nuxt relies on native ESM. Please read [Native ES Modules](/docs/4.x/guide/concepts/esm) for more information.
## Document Your Module
Consider documenting module usage in the readme file:
- Why use this module?
- How to use this module?
- What does this module do?
Linking to the integration website and documentation is always a good idea.
## Provide a Demo
It's a good practice to make a minimal reproduction with your module and [StackBlitz](https://nuxt.new/s/v4) that you add to your module readme.
This not only provides potential users of your module a quick and easy way to experiment with the module but also an easy way for them to build minimal reproductions they can send you when they encounter issues.
## Stay Version Agnostic
Nuxt, Nuxt Kit, and other new toolings are made to have both forward and backward compatibility in mind.
Please use "X for Nuxt" instead of "X for Nuxt 3" to avoid fragmentation in the ecosystem and prefer using `meta.compatibility` to set Nuxt version constraints.
## Follow Starter Conventions
The module starter comes with a default set of tools and configurations (e.g. ESLint configuration). If you plan on open-sourcing your module, sticking with those defaults ensures your module shares a consistent coding style with other [community modules](/modules) out there, making it easier for others to contribute.

View File

@@ -0,0 +1,32 @@
---
title: "Publish & Share Your Module"
description: "Join the Nuxt module ecosystem and publish your module to npm."
---
The [Nuxt module ecosystem](/modules) represents more than 35 million monthly NPM downloads and provides extended functionalities and integrations with all sort of tools. You can be part of this ecosystem!
::tip{icon="i-lucide-video" to="https://vueschool.io/lessons/exploring-nuxt-modules-ecosystem-and-module-types?friend=nuxt" target="_blank"}
Watch Vue School video about Nuxt module types.
::
## Understand Module Types
**Official modules** are modules prefixed (scoped) with `@nuxt/` (e.g. [`@nuxt/content`](https://content.nuxt.com)). They are made and maintained actively by the Nuxt team. Like with the framework, contributions from the community are more than welcome to help make them better!
**Community modules** are modules prefixed (scoped) with `@nuxtjs/` (e.g. [`@nuxtjs/tailwindcss`](https://tailwindcss.nuxtjs.org)). They are proven modules made and maintained by community members. Again, contributions are welcome from anyone.
**Third-party and other community modules** are modules (often) prefixed with `nuxt-`. Anyone can make them, using this prefix allows these modules to be discoverable on npm. This is the best starting point to draft and try an idea!
**Private or personal modules** are modules made for your own use case or company. They don't need to follow any naming rules to work with Nuxt and are often seen scoped under an npm organization (e.g. `@my-company/nuxt-auth`)
## List Your Module
Any community modules are welcome to be listed on [the module list](/modules). To be listed, [open an issue in the nuxt/modules](https://github.com/nuxt/modules/issues/new?template=module_request.yml) repository. The Nuxt team can help you to apply best practices before listing.
## Join nuxt-modules
By moving your modules to [nuxt-modules](https://github.com/nuxt-modules), there is always someone else to help, and this way, we can join forces to make one perfect solution.
If you have an already published and working module, and want to transfer it to `nuxt-modules`, [open an issue in nuxt/modules](https://github.com/nuxt/modules/issues/new).
By joining `nuxt-modules` we can rename your community module under the `@nuxtjs/` scope and provide a subdomain (e.g. `my-module.nuxtjs.org`) for its documentation.

View File

@@ -0,0 +1,36 @@
---
title: 'Module Author Guide'
titleTemplate: '%s'
description: 'Learn how to create a Nuxt module to integrate, enhance or extend any Nuxt applications.'
navigation: false
surround: false
---
Nuxt's [configuration](/docs/4.x/api/nuxt-config) and [hooks](/docs/4.x/guide/going-further/hooks) systems make it possible to customize every aspect of Nuxt and add any integration you might need (Vue plugins, CMS, server routes, components, logging, etc.).
**Nuxt modules** are functions that sequentially run when starting Nuxt in development mode using `nuxt dev` or building a project for production with `nuxt build`.
With modules, you can encapsulate, properly test, and share custom solutions as npm packages without adding unnecessary boilerplate to your project, or requiring changes to Nuxt itself.
::card-group{class="sm:grid-cols-1"}
::card{icon="i-lucide-rocket" title="Create Your First Module" to="/docs/4.x/guide/modules/getting-started"}
Learn how to create your first Nuxt module using the official starter template.
::
::card{icon="i-lucide-box" title="Understand Module Structure" to="/docs/4.x/guide/modules/module-anatomy"}
Learn how Nuxt modules are structured and how to define them.
::
::card{icon="i-lucide-code" title="Add Plugins, Components & More" to="/docs/4.x/guide/modules/recipes-basics"}
Learn how to inject plugins, components, composables and server routes from your module.
::
::card{icon="i-lucide-layers" title="Use Hooks & Extend Types" to="/docs/4.x/guide/modules/recipes-advanced"}
Master lifecycle hooks, virtual files and TypeScript declarations in your modules.
::
::card{icon="i-lucide-test-tube" title="Test Your Module" to="/docs/4.x/guide/modules/testing"}
Learn how to test your Nuxt module with unit, integration and E2E tests.
::
::card{icon="i-lucide-medal" title="Follow Best Practices" to="/docs/4.x/guide/modules/best-practices"}
Build performant and maintainable Nuxt modules with these guidelines.
::
::card{icon="i-lucide-globe" title="Publish & Share Your Module" to="/docs/4.x/guide/modules/ecosystem"}
Join the Nuxt module ecosystem and publish your module to npm.
::
::

View File

@@ -0,0 +1,3 @@
title: Recipes
titleTemplate: '%s · Recipes'
icon: i-lucide-cooking-pot

View File

@@ -0,0 +1,181 @@
---
title: "Custom Routing"
description: "In Nuxt, your routing is defined by the structure of your files inside the pages directory. However, since it uses vue-router under the hood, Nuxt offers you several ways to add custom routes in your project."
---
## Adding custom routes
In Nuxt, your routing is defined by the structure of your files inside the [app/pages directory](/docs/4.x/directory-structure/app/pages). However, since it uses [vue-router](https://router.vuejs.org) under the hood, Nuxt offers you several ways to add custom routes in your project.
### Router Config
Using [router options](/docs/4.x/guide/recipes/custom-routing#router-options), you can optionally override or extend your routes using a function that accepts the scanned routes and returns customized routes.
If it returns `null` or `undefined`, Nuxt will fall back to the default routes (useful to modify input array).
```ts [router.options.ts]
import type { RouterConfig } from '@nuxt/schema'
export default {
// https://router.vuejs.org/api/interfaces/routeroptions#routes
routes: _routes => [
{
name: 'home',
path: '/',
component: () => import('~/pages/home.vue'),
},
],
} satisfies RouterConfig
```
::note
Nuxt will not augment any new routes you return from the `routes` function with metadata defined in `definePageMeta` of the component you provide. If you want that to happen, you should use the `pages:extend` hook which is [called at build-time](/docs/4.x/api/advanced/hooks#nuxt-hooks-build-time).
::
### Pages Hook
You can add, change or remove pages from the scanned routes with the `pages:extend` nuxt hook.
For example, to prevent creating routes for any `.ts` files:
```ts [nuxt.config.ts]
import type { NuxtPage } from '@nuxt/schema'
export default defineNuxtConfig({
hooks: {
'pages:extend' (pages) {
// add a route
pages.push({
name: 'profile',
path: '/profile',
file: '~/extra-pages/profile.vue',
})
// remove routes
function removePagesMatching (pattern: RegExp, pages: NuxtPage[] = []) {
const pagesToRemove: NuxtPage[] = []
for (const page of pages) {
if (page.file && pattern.test(page.file)) {
pagesToRemove.push(page)
} else {
removePagesMatching(pattern, page.children)
}
}
for (const page of pagesToRemove) {
pages.splice(pages.indexOf(page), 1)
}
}
removePagesMatching(/\.ts$/, pages)
},
},
})
```
### Nuxt Module
If you plan to add a whole set of pages related with a specific functionality, you might want to use a [Nuxt module](/modules).
The [Nuxt kit](/docs/4.x/guide/going-further/kit) provides a few ways [to add routes](/docs/4.x/api/kit/pages):
- [`extendPages`](/docs/4.x/api/kit/pages#extendpages) (callback: pages => void)
- [`extendRouteRules`](/docs/4.x/api/kit/pages#extendrouterules) (route: string, rule: NitroRouteConfig, options: ExtendRouteRulesOptions)
## Router Options
On top of customizing options for [`vue-router`](https://router.vuejs.org/api/interfaces/routeroptions), Nuxt offers [additional options](/docs/4.x/api/nuxt-config#router) to customize the router.
### Using `router.options`
This is the recommended way to specify [router options](/docs/4.x/api/nuxt-config#router).
```ts [app/router.options.ts]
import type { RouterConfig } from '@nuxt/schema'
export default {
} satisfies RouterConfig
```
It is possible to add more router options files by adding files within the `pages:routerOptions` hook. Later items in the array override earlier ones.
::callout
Adding a router options file in this hook will switch on page-based routing, unless `optional` is set, in which case it will only apply when page-based routing is already enabled.
::
```ts [nuxt.config.ts]
import { createResolver } from '@nuxt/kit'
export default defineNuxtConfig({
hooks: {
'pages:routerOptions' ({ files }) {
const resolver = createResolver(import.meta.url)
// add a route
files.push({
path: resolver.resolve('./runtime/router-options'),
optional: true,
})
},
},
})
```
### Using `nuxt.config`
**Note:** Only JSON serializable [options](/docs/4.x/api/nuxt-config#router) are configurable:
- `linkActiveClass`
- `linkExactActiveClass`
- `end`
- `sensitive`
- `strict`
- `hashMode`
- `scrollBehaviorType`
```ts [nuxt.config]
export default defineNuxtConfig({
router: {
options: {},
},
})
```
### Hash Mode (SPA)
You can enable hash history in SPA mode using the `hashMode` [config](/docs/4.x/api/nuxt-config#router). In this mode, router uses a hash character (#) before the actual URL that is internally passed. When enabled, the **URL is never sent to the server** and **SSR is not supported**.
```ts [nuxt.config.ts]
export default defineNuxtConfig({
ssr: false,
router: {
options: {
hashMode: true,
},
},
})
```
### Scroll Behavior for hash links
You can optionally customize the scroll behavior for hash links. When you set the [config](/docs/4.x/api/nuxt-config#router) to be `smooth` and you load a page with a hash link (e.g. `https://example.com/blog/my-article#comments`), you will see that the browser smoothly scrolls to this anchor.
```ts [nuxt.config.ts]
export default defineNuxtConfig({
router: {
options: {
scrollBehaviorType: 'smooth',
},
},
})
```
#### Custom History (advanced)
You can optionally override history mode using a function that accepts the base URL and returns the history mode. If it returns `null` or `undefined`, Nuxt will fallback to the default history.
```ts [router.options.ts]
import type { RouterConfig } from '@nuxt/schema'
import { createMemoryHistory } from 'vue-router'
export default {
// https://router.vuejs.org/api/interfaces/routeroptions
history: base => import.meta.client ? createMemoryHistory(base) : null, /* default */
} satisfies RouterConfig
```

View File

@@ -0,0 +1,110 @@
---
navigation.title: 'Vite Plugins'
title: Using Vite Plugins in Nuxt
description: Learn how to integrate Vite plugins into your Nuxt project.
---
While Nuxt modules offer extensive functionality, sometimes a specific Vite plugin might meet your needs more directly.
First, we need to install the Vite plugin, for our example, we'll use `@rollup/plugin-yaml`:
::code-group{sync="pm"}
```bash [npm]
npm install @rollup/plugin-yaml
```
```bash [yarn]
yarn add @rollup/plugin-yaml
```
```bash [pnpm]
pnpm add @rollup/plugin-yaml
```
```bash [bun]
bun add @rollup/plugin-yaml
```
```bash [deno]
deno add npm:@rollup/plugin-yaml
```
::
Next, we need to import and add it to our [`nuxt.config.ts`](/docs/4.x/directory-structure/nuxt-config) file:
```ts [nuxt.config.ts]
import yaml from '@rollup/plugin-yaml'
export default defineNuxtConfig({
vite: {
plugins: [
yaml(),
],
},
})
```
Now we installed and configured our Vite plugin, we can start using YAML files directly in our project.
For example, we can have a `config.yaml` that stores configuration data and import this data in our Nuxt components:
::code-group
```yaml [data/hello.yaml]
greeting: "Hello, Nuxt with Vite!"
```
```vue [app/components/Hello.vue]
<script setup>
import config from '~/data/hello.yaml'
</script>
<template>
<h1>{{ config.greeting }}</h1>
</template>
```
::
## Using Vite Plugins in Nuxt Modules
If you're developing a Nuxt module and need to add Vite plugins, you should use the [`addVitePlugin`](/docs/4.x/api/kit/builder#addviteplugin) utility:
```ts [modules/my-module.ts]
import { addVitePlugin, defineNuxtModule } from '@nuxt/kit'
import yaml from '@rollup/plugin-yaml'
export default defineNuxtModule({
setup () {
addVitePlugin(yaml())
},
})
```
For environment-specific plugins in Nuxt 5+, use the `applyToEnvironment()` method:
```ts [modules/my-module.ts]
import { addVitePlugin, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup () {
addVitePlugin(() => ({
name: 'my-client-plugin',
applyToEnvironment (environment) {
return environment.name === 'client'
},
// Plugin configuration
}))
},
})
```
::important
If you're writing code that needs to access resolved Vite configuration, you should use the `config` and `configResolved` hooks _within_ your Vite plugin, rather than using Nuxt's `vite:extend`, `vite:extendConfig` and `vite:configResolved`.
::
::read-more{to="/docs/4.x/api/kit/builder#addviteplugin"}
Read more about `addVitePlugin` in the Nuxt Kit documentation.
::

View File

@@ -0,0 +1,94 @@
---
navigation.title: 'Custom useFetch'
title: Custom useFetch in Nuxt
description: How to create a custom fetcher for calling your external API in Nuxt.
---
When working with Nuxt, you might be making the frontend and fetching an external API, and you might want to set some default options for fetching from your API.
The [`$fetch`](/docs/4.x/api/utils/dollarfetch) utility function (used by the [`useFetch`](/docs/4.x/api/composables/use-fetch) composable) is intentionally not globally configurable. This is important so that fetching behavior throughout your application remains consistent, and other integrations (like modules) can rely on the behavior of core utilities like `$fetch`.
However, Nuxt provides a way to create a custom fetcher for your API (or multiple fetchers if you have multiple APIs to call).
## Recipe: API Client with Auth
Let's say you have an external API at `https://api.nuxt.com` that requires JWT authentication via [nuxt-auth-utils](https://github.com/atinux/nuxt-auth-utils), and you want to redirect to `/login` on `401` responses.
```ts [app/composables/useAPI.ts]
export const useAPI = createUseFetch({
baseURL: 'https://api.nuxt.com',
onRequest ({ options }) {
const { session } = useUserSession()
if (session.value?.token) {
options.headers.set('Authorization', `Bearer ${session.value.token}`)
}
},
async onResponseError ({ response }) {
if (response.status === 401) {
await navigateTo('/login')
}
},
})
```
Now every call to `useAPI` automatically includes the auth header and handles 401 redirects:
```vue [app/pages/dashboard.vue]
<script setup lang="ts">
const { data: profile } = await useAPI('/me')
const { data: orders } = await useAPI('/orders')
</script>
```
:read-more{to="/docs/4.x/api/composables/create-use-fetch"}
## Recipe: Custom `$fetch` Instance
If you need lower-level control, you can create a custom `$fetch` instance with a [Nuxt plugin](/docs/4.x/directory-structure/app/plugins) and either use it with `useAsyncData` directly or pass it to `createUseFetch`.
::note
`$fetch` is a configured instance of [ofetch](https://github.com/unjs/ofetch) which supports adding the base URL of your Nuxt server as well as direct function calls during SSR (avoiding HTTP roundtrips).
::
```ts [app/plugins/api.ts]
export default defineNuxtPlugin((nuxtApp) => {
const { session } = useUserSession()
const api = $fetch.create({
baseURL: 'https://api.nuxt.com',
onRequest ({ request, options, error }) {
if (session.value?.token) {
options.headers.set('Authorization', `Bearer ${session.value?.token}`)
}
},
async onResponseError ({ response }) {
if (response.status === 401) {
await nuxtApp.runWithContext(() => navigateTo('/login'))
}
},
})
return {
provide: {
api,
},
}
})
```
You can then use the custom `$fetch` instance directly:
```vue [app/app.vue]
<script setup>
const { $api } = useNuxtApp()
const { data: modules } = await useAsyncData('modules', () => $api('/modules'))
</script>
```
::callout
Wrapping with [`useAsyncData`](/docs/4.x/api/composables/use-async-data) **avoids double data fetching when doing server-side rendering** (server & client on hydration).
::
:link-example{to="/docs/4.x/examples/advanced/use-custom-fetch-composable"}
:video-accordion{title="Watch a video about custom $fetch and Repository Pattern in Nuxt" videoId="jXH8Tr-exhI"}

View File

@@ -0,0 +1,217 @@
---
title: 'Sessions and Authentication'
description: "Authentication is an extremely common requirement in web apps. This recipe will show you how to implement basic user registration and authentication in your Nuxt app."
---
## Introduction
In this recipe we'll be setting up authentication in a full-stack Nuxt app using [Nuxt Auth Utils](https://github.com/Atinux/nuxt-auth-utils) which provides convenient utilities for managing client-side and server-side session data.
The module uses secured & sealed cookies to store session data, so you don't need to setup a database to store session data.
## Install nuxt-auth-utils
Install the `nuxt-auth-utils` module using the `nuxt` CLI.
```bash [Terminal]
npx nuxt module add auth-utils
```
::callout
This command will install `nuxt-auth-utils` as dependency and push it in the `modules` section of our `nuxt.config.ts`
::
## Cookie Encryption Key
As `nuxt-auth-utils` uses sealed cookies to store session data, session cookies are encrypted using a secret key from the `NUXT_SESSION_PASSWORD` environment variable.
::note
If not set, this environment variable will be added to your `.env` automatically when running in development mode.
::
```ini [.env]
NUXT_SESSION_PASSWORD=a-random-password-with-at-least-32-characters
```
::important
You'll need to add this environment variable to your production environment before deploying.
::
## Login API Route
For this recipe, we'll create a simple API route to sign-in a user based on static data.
Let's create a `/api/login` API route that will accept a POST request with the email and password in the request body.
```ts [server/api/login.post.ts]
import { z } from 'zod'
const bodySchema = z.object({
email: z.email(),
password: z.string().min(8),
})
export default defineEventHandler(async (event) => {
const { email, password } = await readValidatedBody(event, bodySchema.parse)
if (email === 'admin@admin.com' && password === 'iamtheadmin') {
// set the user session in the cookie
// this server util is auto-imported by the auth-utils module
await setUserSession(event, {
user: {
name: 'John Doe',
},
})
return {}
}
throw createError({
status: 401,
message: 'Bad credentials',
})
})
```
::callout
Make sure to install the `zod` dependency in your project (`npm i zod`).
::
::tip{to="https://github.com/atinux/nuxt-auth-utils#server-utils"}
Read more about the `setUserSession` server helper exposed by `nuxt-auth-utils`.
::
## Login Page
The module exposes a Vue composable to know if a user is authenticated in our application:
```vue
<script setup>
const { loggedIn, session, user, clear, fetch } = useUserSession()
</script>
```
Let's create a login page with a form to submit the login data to our `/api/login` route.
```vue [app/pages/login.vue]
<script setup lang="ts">
const { loggedIn, user, fetch: refreshSession } = useUserSession()
const credentials = reactive({
email: '',
password: '',
})
async function login () {
try {
await $fetch('/api/login', {
method: 'POST',
body: credentials,
})
// Refresh the session on client-side and redirect to the home page
await refreshSession()
await navigateTo('/')
} catch {
alert('Bad credentials')
}
}
</script>
<template>
<form @submit.prevent="login">
<input
v-model="credentials.email"
type="email"
placeholder="Email"
>
<input
v-model="credentials.password"
type="password"
placeholder="Password"
>
<button type="submit">
Login
</button>
</form>
</template>
```
## Protect API Routes
Protecting server routes is key to making sure your data is safe. Client-side middleware is helpful for the user, but without server-side protection your data can still be accessed. It is critical to protect any routes with sensitive data, we should return a 401 error if the user is not logged in on those.
The `auth-utils` module provides the `requireUserSession` utility function to help make sure that users are logged in and have an active session.
Let's create an example of a `/api/user/stats` route that only authenticated users can access.
```ts [server/api/user/stats.get.ts]
export default defineEventHandler(async (event) => {
// make sure the user is logged in
// This will throw a 401 error if the request doesn't come from a valid user session
const { user } = await requireUserSession(event)
// TODO: Fetch some stats based on the user
return {}
})
```
## Protect App Routes
Our data is safe with the server-side route in place, but without doing anything else, unauthenticated users would probably get some odd data when trying to access the `/users` page. We should create a [client-side middleware](https://nuxt.com/docs/4.x/directory-structure/app/middleware) to protect the route on the client side and redirect users to the login page.
`nuxt-auth-utils` provides a convenient `useUserSession` composable which we'll use to check if the user is logged in, and redirect them if they are not.
We'll create a middleware in the `/middleware` directory. Unlike on the server, client-side middleware is not automatically applied to all endpoints, and we'll need to specify where we want it applied.
```typescript [app/middleware/authenticated.ts]
export default defineNuxtRouteMiddleware(() => {
const { loggedIn } = useUserSession()
// redirect the user to the login screen if they're not authenticated
if (!loggedIn.value) {
return navigateTo('/login')
}
})
```
## Home Page
Now that we have our app middleware to protect our routes, we can use it on our home page that display our authenticated user information. If the user is not authenticated, they will be redirected to the login page.
We'll use [`definePageMeta`](/docs/4.x/api/utils/define-page-meta) to apply the middleware to the route that we want to protect.
```vue [app/pages/index.vue]
<script setup lang="ts">
definePageMeta({
middleware: ['authenticated'],
})
const { user, clear: clearSession } = useUserSession()
async function logout () {
await clearSession()
await navigateTo('/login')
}
</script>
<template>
<div>
<h1>Welcome {{ user.name }}</h1>
<button @click="logout">
Logout
</button>
</div>
</template>
```
We also added a logout button to clear the session and redirect the user to the login page.
## Conclusion
We've successfully set up a very basic user authentication and session management in our Nuxt app. We've also protected sensitive routes on the server and client side to ensure that only authenticated users can access them.
As next steps, you can:
- Add authentication using the [20+ supported OAuth providers](https://github.com/atinux/nuxt-auth-utils?tab=readme-ov-file#supported-oauth-providers)
- Add a database to store users, see [Nitro SQL Database](https://nitro.build/guide/database) or [NuxtHub SQL Database](https://hub.nuxt.com/docs/database)
- Let user signup with email & password using [password hashing](https://github.com/atinux/nuxt-auth-utils?tab=readme-ov-file#password-hashing)
- Add support for [WebAuthn / Passkeys](https://github.com/atinux/nuxt-auth-utils?tab=readme-ov-file#webauthn-passkey)
Checkout the open source [atidone repository](https://github.com/atinux/atidone) for a full example of a Nuxt app with OAuth authentication, database and CRUD operations.

View File

@@ -0,0 +1,3 @@
title: Going Further
titleTemplate: '%s · Nuxt Advanced'
icon: i-lucide-star

View File

@@ -0,0 +1,62 @@
---
title: "Creating Custom Events"
description: "Nuxt provides a powerful event system powered by hookable."
navigation.title: "Custom Events"
---
Using events is a great way to decouple your application and allow for more flexible and modular communication between different parts of your code. Events can have multiple listeners that do not depend on each other. For example, you may wish to send an email to your user each time an order has shipped. Instead of coupling your order processing code to your email code, you can emit an event which a listener can receive and use to dispatch an email.
The Nuxt event system is powered by [unjs/hookable](https://github.com/unjs/hookable), which is the same library that powers the Nuxt hooks system.
## Creating Events and Listeners
You can create your own custom events using the `hook` method:
```ts
const nuxtApp = useNuxtApp()
nuxtApp.hook('app:user:registered', (payload) => {
console.log('A new user has registered!', payload)
})
```
To emit an event and notify any listeners, use `callHook`:
```ts
const nuxtApp = useNuxtApp()
await nuxtApp.callHook('app:user:registered', {
id: 1,
name: 'John Doe',
})
```
You can also use the payload object to enable two-way communication between the emitter and listeners. Since the payload is passed by reference, a listener can modify it to send data back to the emitter.
```ts
const nuxtApp = useNuxtApp()
nuxtApp.hook('app:user:registered', (payload) => {
payload.message = 'Welcome to our app!'
})
const payload = {
id: 1,
name: 'John Doe',
}
await nuxtApp.callHook('app:user:registered', {
id: 1,
name: 'John Doe',
})
// payload.message will be 'Welcome to our app!'
```
::tip
You can inspect all events using the **Nuxt DevTools** Hooks panel.
::
::read-more{to="/docs/4.x/guide/going-further/hooks"}
Learn more about Nuxt's built-in hooks and how to extend them
::

View File

@@ -0,0 +1,968 @@
---
title: "Experimental Features"
description: "Enable Nuxt experimental features to unlock new possibilities."
---
Nuxt includes experimental features that you can enable in your configuration file.
Internally, Nuxt uses `@nuxt/schema` to define these experimental features. You can refer to the [API documentation](/docs/4.x/guide/going-further/experimental-features) or the [source code](https://github.com/nuxt/nuxt/blob/main/packages/schema/src/config/experimental.ts) for more information.
::note
Note that these features are experimental and could be removed or modified in the future.
::
## alwaysRunFetchOnKeyChange
Whether to run `useFetch` when the key changes, even if it is set to `immediate: false` and it has not been triggered yet.
`useFetch` and `useAsyncData` will always run when the key changes if `immediate: true` or if it has been already triggered.
This flag is disabled by default, but you can enable this feature:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
alwaysRunFetchOnKeyChange: true,
},
})
```
## appManifest
Use app manifests to respect route rules on client-side.
This flag is enabled by default, but you can disable this feature:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
appManifest: false,
},
})
```
## asyncContext
Enable native async context to be accessible for nested composables in Nuxt and in Nitro. This opens the possibility to use composables inside async composables and reduce the chance to get the `Nuxt instance is unavailable` error.
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
asyncContext: true,
},
})
```
::read-more{icon="i-simple-icons-github" to="https://github.com/nuxt/nuxt/pull/20918" target="_blank"}
See full explanation on the GitHub pull-request.
::
## asyncEntry
Enables generation of an async entry point for the Vue bundle, aiding module federation support.
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
asyncEntry: true,
},
})
```
## extractAsyncDataHandlers
Extracts handler functions from `useAsyncData` and `useLazyAsyncData` calls into separate chunks for improved code splitting and caching efficiency.
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
extractAsyncDataHandlers: true,
},
})
```
This feature transforms inline handler functions into dynamically imported chunks:
```vue
<!-- Before -->
<script setup>
const { data } = await useAsyncData('user', async () => {
return await $fetch('/api/user')
})
</script>
```
```vue
<!-- After transformation -->
<script setup>
const { data } = await useAsyncData('user', () =>
import('/generated-chunk.js').then(r => r.default()),
)
</script>
```
The benefit of this transformation is that we can split out data fetching logic &mdash; while still allowing the code to be loaded if required.
::important
This feature is only recommended for **static builds** with payload extraction, and where data does not need to be re-fetched at runtime.
::
## emitRouteChunkError
Emits `app:chunkError` hook when there is an error loading vite/webpack chunks. Default behavior is to perform a reload of the new route on navigation to a new route when a chunk fails to load.
By default, Nuxt will also perform a reload of the new route when a chunk fails to load when navigating to a new route (`automatic`).
Setting `automatic-immediate` will lead Nuxt to perform a reload of the current route right when a chunk fails to load (instead of waiting for navigation). This is useful for chunk errors that are not triggered by navigation, e.g., when your Nuxt app fails to load a [lazy component](/docs/4.x/directory-structure/app/components#dynamic-imports). A potential downside of this behavior is undesired reloads, e.g., when your app does not need the chunk that caused the error.
You can disable automatic handling by setting this to `false`, or handle chunk errors manually by setting it to `manual`.
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
emitRouteChunkError: 'automatic', // or 'automatic-immediate', 'manual' or false
},
})
```
## enforceModuleCompatibility
Whether Nuxt should throw an error (and fail to load) if a Nuxt module is incompatible.
This feature is disabled by default.
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
enforceModuleCompatibility: true,
},
})
```
## restoreState
Allows Nuxt app state to be restored from `sessionStorage` when reloading the page after a chunk error or manual [`reloadNuxtApp()`](/docs/4.x/api/utils/reload-nuxt-app) call.
To avoid hydration errors, it will be applied only after the Vue app has been mounted, meaning there may be a flicker on initial load.
::important
Consider carefully before enabling this as it can cause unexpected behavior,
and consider providing explicit keys to [`useState`](/docs/4.x/api/composables/use-state) as auto-generated keys may not match across builds.
::
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
restoreState: true,
},
})
```
## inlineRouteRules
Define route rules at the page level using [`defineRouteRules`](/docs/4.x/api/utils/define-route-rules).
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
inlineRouteRules: true,
},
})
```
Matching route rules will be created, based on the page's `path`.
::read-more{to="/docs/4.x/api/utils/define-route-rules" icon="i-lucide-square-function"}
Read more in `defineRouteRules` utility.
::
:read-more{to="/docs/4.x/guide/concepts/rendering#hybrid-rendering" icon="i-lucide-medal"}
## noVueServer
Disables Vue server renderer endpoint within Nitro.
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
noVueServer: true,
},
})
```
## parseErrorData
Whether to parse `error.data` when rendering a server error page.
This flag is enabled by default, but you can disable this feature:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
parseErrorData: false,
},
})
```
## payloadExtraction
Controls how payload data is delivered for prerendered and cached (ISR/SWR) pages.
- `'client'` - Payload is inlined in HTML for the initial server render, and extracted to `_payload.json` files for client-side navigation. This avoids a separate network request on initial load while still enabling efficient client-side navigation.
- `true` - Payload is extracted to a separate `_payload.json` file for both the initial server render and client-side navigation.
- `false` - Payload extraction is disabled entirely. Payload is always inlined in HTML and no `_payload.json` files are generated.
The default is `true`, or `'client'` when `compatibilityVersion: 5` is set.
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
// Inline payload in HTML, extract for client-side navigation only
payloadExtraction: 'client',
},
})
```
Payload extraction also works for routes using ISR (Incremental Static Regeneration) or SWR (Stale-While-Revalidate) caching strategies. This allows CDNs to cache payload files alongside HTML, improving client-side navigation performance for cached routes.
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
payloadExtraction: 'client',
},
routeRules: {
// Payload files will be generated for these cached routes
'/products/**': { isr: 3600 },
'/blog/**': { swr: true },
},
})
```
## clientNodePlaceholder
Uses comment nodes (`<!--placeholder-->`) instead of `<div>` elements as placeholders for client-only components during server-side rendering.
When enabled, `.client.vue` components and `createClientOnly()` wrappers render an HTML comment on the server instead of an empty `<div>`. This fixes a Vue hydration issue where scoped styles may not be applied when the placeholder `<div>` and the actual component root share the same tag name.
::warning
Enabling this means attributes (`class`, `style`, etc.) passed to `.client.vue` components will not appear in the SSR HTML. If you need styled placeholders to prevent layout shift, use `<ClientOnly>` with a `#fallback` slot instead.
::
This flag is enabled when `future.compatibilityVersion` is set to `5` or higher, but you can also enable it explicitly:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
clientNodePlaceholder: true,
},
})
```
## clientFallback
Enables the experimental [`<NuxtClientFallback>`](/docs/4.x/api/components/nuxt-client-fallback) component for rendering content on the client if there's an error in SSR.
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
clientFallback: true,
},
})
```
## crossOriginPrefetch
Enables cross-origin prefetch using the Speculation Rules API.
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
crossOriginPrefetch: true,
},
})
```
::read-more{icon="i-simple-icons-w3c" to="https://wicg.github.io/nav-speculation/prefetch.html" target="_blank"}
Read more about the **Speculation Rules API**.
::
## viewTransition
Enables View Transition API integration with client-side router.
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
viewTransition: true,
},
})
```
You can also pass an object to configure [view transition types](/docs/4.x/getting-started/transitions#view-transition-types), which allow different CSS animations based on the type of navigation:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
viewTransition: {
enabled: true,
types: ['slide'],
},
},
})
```
:link-example{to="https://stackblitz.com/edit/nuxt-view-transitions?file=app.vue" target="_blank"}
::read-more{icon="i-simple-icons-mdnwebdocs" to="https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API" target="_blank"}
Read more about the **View Transition API**.
::
::read-more{icon="i-simple-icons-google" to="https://developer.chrome.com/blog/view-transitions-update-io24" target="_blank"}
Read more about the **View Transition API**.
::
## writeEarlyHints
Enables writing of early hints when using node server.
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
writeEarlyHints: true,
},
})
```
## componentIslands
Enables experimental component islands support with [`<NuxtIsland>`](/docs/4.x/api/components/nuxt-island) and `.island.vue` files.
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
componentIslands: true, // false or 'local+remote'
},
})
```
:read-more{to="/docs/4.x/directory-structure/app/components#server-components"}
::read-more{icon="i-simple-icons-github" to="https://github.com/nuxt/nuxt/issues/19772" target="_blank"}
You can follow the server components roadmap on GitHub.
::
## localLayerAliases
Resolve `~`, `~~`, `@` and `@@` aliases located within layers with respect to their layer source and root directories.
This flag is enabled by default, but you can disable this feature:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
localLayerAliases: false,
},
})
```
## typedPages
Enable the new experimental typed router.
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
typedPages: true,
},
})
```
Out of the box, this will enable typed usage of [`navigateTo`](/docs/4.x/api/utils/navigate-to), [`<NuxtLink>`](/docs/4.x/api/components/nuxt-link), [`router.push()`](/docs/4.x/api/composables/use-router) and more.
You can even get typed params within a page by using `const route = useRoute('route-name')`.
:video-accordion{title="Watch a video from Daniel Roe explaining type-safe routing in Nuxt" videoId="SXk-L19gTZk"}
## watcher
Set an alternative watcher that will be used as the watching service for Nuxt.
Nuxt uses `chokidar-granular` by default, which will ignore top-level directories
(like `node_modules` and `.git`) that are excluded from watching.
You can set this instead to `parcel` to use `@parcel/watcher`, which may improve
performance in large projects or on Windows platforms.
You can also set this to `chokidar` to watch all files in your source directory.
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
watcher: 'chokidar-granular', // 'chokidar' or 'parcel' are also options
},
})
```
## sharedPrerenderData
Nuxt automatically shares payload *data* between pages that are prerendered. This can result in a significant performance improvement when prerendering sites that use `useAsyncData` or `useFetch` and fetch the same data in different pages.
You can disable this feature if needed.
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
sharedPrerenderData: false,
},
})
```
:video-accordion{title="Watch a video from Alexander Lichter about the experimental sharedPrerenderData" videoId="1jUupYHVvrU"}
It is particularly important when enabling this feature to make sure that any unique key of your data
is always resolvable to the same data. For example, if you are using `useAsyncData` to fetch
data related to a particular page, you should provide a key that uniquely matches that data. (`useFetch`
should do this automatically for you.)
```ts
// This would be unsafe in a dynamic page (e.g. `[slug].vue`) because the route slug makes a difference
// to the data fetched, but Nuxt can't know that because it's not reflected in the key.
const route = useRoute()
const { data } = await useAsyncData(async (_nuxtApp, { signal }) => {
return await $fetch(`/api/my-page/${route.params.slug}`, { signal })
})
// Instead, you should use a key that uniquely identifies the data fetched.
const { data } = await useAsyncData(route.params.slug, async (_nuxtApp, { signal }) => {
return await $fetch(`/api/my-page/${route.params.slug}`, { signal })
})
```
## clientNodeCompat
With this feature, Nuxt will automatically polyfill Node.js imports in the client build using [`unenv`](https://github.com/unjs/unenv).
::note
To make globals like `Buffer` work in the browser, you need to manually inject them.
```ts
import { Buffer } from 'node:buffer'
globalThis.Buffer ||= Buffer
```
::
## scanPageMeta
Nuxt exposing some route metadata defined in `definePageMeta` at build-time to modules (specifically `alias`, `name`, `path`, `redirect`, `props` and `middleware`).
This only works with static or strings/arrays rather than variables or conditional assignment. See [original issue](https://github.com/nuxt/nuxt/issues/24770) for more information and context.
By default page metadata is only scanned after all routes have been registered in `pages:extend`. Then another hook, `pages:resolved` will be called.
You can disable this feature if it causes issues in your project.
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
scanPageMeta: false,
},
})
```
## cookieStore
Enables CookieStore support to listen for cookie updates (if supported by the browser) and refresh `useCookie` ref values.
This flag is enabled by default, but you can disable this feature:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
cookieStore: false,
},
})
```
::read-more{icon="i-simple-icons-mdnwebdocs" to="https://developer.mozilla.org/en-US/docs/Web/API/CookieStore" target="_blank"}
Read more about the **CookieStore**.
::
## buildCache
Caches Nuxt build artifacts based on a hash of the configuration and source files.
This only works for source files within `srcDir` and `serverDir` for the Vue/Nitro parts of your app.
This flag is disabled by default, but you can enable it:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
buildCache: true,
},
})
```
When enabled, changes to the following files will trigger a full rebuild:
```bash [Directory structure]
.nuxtrc
.npmrc
package.json
package-lock.json
yarn.lock
pnpm-lock.yaml
tsconfig.json
bun.lock
bun.lockb
```
In addition, any changes to files within `srcDir` will trigger a rebuild of the Vue client/server bundle. Nitro will always be rebuilt (though work is in progress to allow Nitro to announce its cacheable artifacts and their hashes).
::note
A maximum of 10 cache tarballs are kept.
::
## checkOutdatedBuildInterval
Set the time interval (in ms) to check for new builds. Disabled when `experimental.appManifest` is `false`.
Set to `false` to disable.
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
checkOutdatedBuildInterval: 3600000, // 1 hour, or false to disable
},
})
```
## extraPageMetaExtractionKeys
The `definePageMeta()` macro is a useful way to collect build-time meta about pages. Nuxt itself provides a set list of supported keys which is used to power some of the internal features such as redirects, page aliases and custom paths.
This option allows passing additional keys to extract from the page metadata when using `scanPageMeta`.
```vue
<script lang="ts" setup>
definePageMeta({
foo: 'bar',
})
</script>
```
```ts
export default defineNuxtConfig({
experimental: {
extraPageMetaExtractionKeys: ['foo'],
},
hooks: {
'pages:resolved' (ctx) {
// ✅ foo is available
},
},
})
```
This allows modules to access additional metadata from the page metadata in the build context. If you are using this within a module, it's recommended also to [augment the `NuxtPage` types with your keys](/docs/4.x/directory-structure/app/pages#typing-custom-metadata).
## navigationRepaint
Wait for a single animation frame before navigation, which gives an opportunity for the browser to repaint, acknowledging user interaction.
It can reduce INP when navigating on prerendered routes.
This flag is enabled by default, but you can disable this feature:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
navigationRepaint: false,
},
})
```
## normalizeComponentNames
Nuxt updates auto-generated Vue component names to match the full component name you would use to auto-import the component.
If you encounter issues, you can disable this feature.
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
normalizeComponentNames: false,
},
})
```
By default, if you haven't set it manually, Vue will assign a component name that matches
the filename of the component.
```bash [Directory structure]
├─ components/
├─── SomeFolder/
├───── MyComponent.vue
```
In this case, the component name would be `MyComponent`, as far as Vue is concerned. If you wanted to use `<KeepAlive>` with it, or identify it in the Vue DevTools, you would need to use this component.
But in order to auto-import it, you would need to use `SomeFolderMyComponent`.
By setting `experimental.normalizeComponentNames`, these two values match, and Vue will generate a component name that matches the Nuxt pattern for component naming.
## normalizePageNames
Ensure that page component names match their route names. This sets the `__name` property on page components so that Vue's `<KeepAlive>` can correctly identify them by name.
By default, Vue assigns component names based on the filename. For example, `pages/foo/index.vue` and `pages/bar/index.vue` would both have the component name `index`. This makes name-based `<KeepAlive>` filtering unreliable because multiple pages share the same name.
With `normalizePageNames` enabled, page components are named after their route (e.g. `foo` and `bar`), so you can use `<KeepAlive>` with `include`/`exclude` without manually adding `defineOptions({ name: '...' })` to each page.
This flag is enabled when `future.compatibilityVersion` is set to `5` or higher, but you can disable this feature:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
normalizePageNames: false,
},
})
```
```vue [app.vue]
<template>
<NuxtPage :keepalive="{ include: ['foo'] }" />
</template>
```
## spaLoadingTemplateLocation
When rendering a client-only page (with `ssr: false`), we optionally render a loading screen (from `~/spa-loading-template.html`).
It can be set to `within`, which will render it like this:
```html
<div id="__nuxt">
<!-- spa loading template -->
</div>
```
Alternatively, you can render the template alongside the Nuxt app root by setting it to `body`:
```html
<div id="__nuxt"></div>
<!-- spa loading template -->
```
This avoids a white flash when hydrating a client-only page.
## browserDevtoolsTiming
Enables performance markers for Nuxt hooks in browser devtools. This adds performance markers that you can track in the Performance tab of Chromium-based browsers, which is useful for debugging and optimizing performance.
This is enabled by default in development mode. If you need to disable this feature, it is possible to do so:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
browserDevtoolsTiming: false,
},
})
```
::read-more{icon="i-simple-icons-github" color="gray" to="https://github.com/nuxt/nuxt/pull/29922" target="_blank"}
See PR #29922 for implementation details.
::
::read-more{icon="i-simple-icons-googlechrome" color="gray" to="https://developer.chrome.com/docs/devtools/performance/extension#tracks" target="_blank"}
Learn more about Chrome DevTools Performance API.
::
## debugModuleMutation
Records mutations to `nuxt.options` in module context, helping to debug configuration changes made by modules during the Nuxt initialization phase.
This is enabled by default when `debug` mode is enabled. If you need to disable this feature, it is possible to do so:
To enable it explicitly:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
debugModuleMutation: true,
},
})
```
::read-more{icon="i-simple-icons-github" color="gray" to="https://github.com/nuxt/nuxt/pull/30555" target="_blank"}
See PR #30555 for implementation details.
::
## lazyHydration
This enables hydration strategies for `<Lazy>` components, which improves performance by deferring hydration of components until they're needed.
Lazy hydration is enabled by default, but you can disable this feature:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
lazyHydration: false,
},
})
```
::read-more{icon="i-simple-icons-github" color="gray" to="/docs/4.x/directory-structure/app/components#delayed-or-lazy-hydration"}
Read more about lazy hydration.
::
## templateImportResolution
Disable resolving imports into Nuxt templates from the path of the module that added the template.
By default, Nuxt attempts to resolve imports in templates relative to the module that added them. Setting this to `false` disables this behavior, which may be useful if you're experiencing resolution conflicts in certain environments.
This flag is enabled by default, but you can disable this feature:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
templateImportResolution: false,
},
})
```
::read-more{icon="i-simple-icons-github" color="gray" to="https://github.com/nuxt/nuxt/pull/31175" target="_blank"}
See PR #31175 for implementation details.
::
## templateRouteInjection
By default the route object returned by the auto-imported `useRoute()` composable is kept in sync with the current page in view in `<NuxtPage>`. This is not true for `vue-router`'s exported `useRoute` or for the default `$route` object available in your Vue templates.
By enabling this option a mixin will be injected to keep the `$route` template object in sync with Nuxt's managed `useRoute()`.
This flag is enabled by default, but you can disable this feature:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
templateRouteInjection: false,
},
})
```
## decorators
This option enables decorator syntax across your entire Nuxt/Nitro app.
When using the Vite builder (default), decorators are lowered via [Babel](https://babeljs.io/) using [`@babel/plugin-proposal-decorators`](https://babeljs.io/docs/babel-plugin-proposal-decorators). When using the webpack or rspack builders, decorators are lowered via [esbuild](https://github.com/evanw/esbuild/releases/tag/v0.21.3).
For a long time, TypeScript has had support for decorators via `compilerOptions.experimentalDecorators`. This implementation predated the TC39 standardization process. Now, decorators are a [Stage 3 Proposal](https://github.com/tc39/proposal-decorators), and supported without special configuration in TS 5.0+ (see https://github.com/microsoft/TypeScript/pull/52582 and https://devblogs.microsoft.com/typescript/announcing-typescript-5-0-beta/#decorators).
Enabling `experimental.decorators` enables support for the TC39 proposal, **NOT** for TypeScript's previous `compilerOptions.experimentalDecorators` implementation.
::warning
Note that there may be changes before this finally lands in the JS standard.
::
### Usage
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
decorators: true,
},
})
```
When using the Vite builder or the Nitro server build, you will need to install additional Babel packages as dev dependencies:
::code-group
```bash [npm]
npm install -D @babel/plugin-proposal-decorators @babel/plugin-syntax-jsx
```
```bash [pnpm]
pnpm add -D @babel/plugin-proposal-decorators @babel/plugin-syntax-jsx
```
```bash [yarn]
yarn add -D @babel/plugin-proposal-decorators @babel/plugin-syntax-jsx
```
::
::tip
Nuxt will prompt you to install these automatically if they are not already present.
::
```ts [app/app.vue]
function something (_method: () => unknown) {
return () => 'decorated'
}
class SomeClass {
@something
public someMethod () {
return 'initial'
}
}
const value = new SomeClass().someMethod()
// this will return 'decorated'
```
## defaults
This allows specifying the default options for core Nuxt components and composables.
These options will likely be moved elsewhere in the future, such as into `app.config` or into the `app/` directory.
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
defaults: {
nuxtLink: {
componentName: 'NuxtLink',
prefetch: true,
prefetchOn: {
visibility: true,
},
},
useAsyncData: {
deep: true,
},
useState: {
resetOnClear: true,
},
},
},
})
```
The `useState.resetOnClear` option controls whether [`clearNuxtState`](/docs/4.x/api/utils/clear-nuxt-state) resets state to its initial value (provided by the `init` function of [`useState`](/docs/4.x/api/composables/use-state)) instead of setting it to `undefined`. This defaults to `true` with `compatibilityVersion: 5`.
## purgeCachedData
Whether to clean up Nuxt static and asyncData caches on route navigation.
Nuxt will automatically purge cached data from `useAsyncData` and `nuxtApp.static.data`. This helps prevent memory leaks and ensures fresh data is loaded when needed, but it is possible to disable it.
This flag is enabled by default, but you can disable this feature:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
purgeCachedData: false,
},
})
```
::read-more{icon="i-simple-icons-github" color="gray" to="https://github.com/nuxt/nuxt/pull/31379" target="_blank"}
See PR #31379 for implementation details.
::
## granularCachedData
Whether to call and use the result from `getCachedData` when refreshing data for `useAsyncData` and `useFetch` (whether by `watch`, `refreshNuxtData()`, or a manual `refresh()` call.
This flag is enabled by default, but you can disable this feature:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
granularCachedData: false,
},
})
```
::read-more{icon="i-simple-icons-github" color="gray" to="https://github.com/nuxt/nuxt/pull/31373" target="_blank"}
See PR #31373 for implementation details.
::
## headNext
Use head optimisations:
- Add the capo.js head plugin in order to render tags in of the head in a more performant way.
- Uses the hash hydration plugin to reduce initial hydration
This flag is enabled by default, but you can disable this feature:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
headNext: false,
},
})
```
## pendingWhenIdle
For `useAsyncData` and `useFetch`, whether `pending` should be `true` when data has not yet started to be fetched.
This flag is disabled by default, but you can enable this feature:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
pendingWhenIdle: true,
},
})
```
## entryImportMap
By default, Nuxt improves chunk stability by using an import map to resolve the entry chunk of the bundle.
This injects an import map at the top of your `<head>` tag:
```html
<script type="importmap">{"imports":{"#entry":"/_nuxt/DC5HVSK5.js"}}</script>
```
Within the script chunks emitted by Vite, imports will be from `#entry`. This means that changes to the entry will not invalidate chunks which are otherwise unchanged.
::note
Nuxt smartly disables this feature if you have configured `vite.build.target` to include a browser that doesn't support import maps, or if you have configured `vite.build.rolldownOptions.output.entryFileNames` to a value that does not include `[hash]`.
::
If you need to disable this feature you can do so:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
entryImportMap: false,
},
// or, better, simply tell vite your desired target
// which nuxt will respect
vite: {
build: {
target: 'safari13',
},
},
})
```
## typescriptPlugin
Enable enhanced TypeScript developer experience with the `@dxup/nuxt` module.
This experimental plugin provides improved TypeScript integration and development tooling for better DX when working with TypeScript in Nuxt applications.
This flag is disabled by default, but you can enable this feature:
```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
typescriptPlugin: true,
},
})
```
::important
To use this feature, you need to:
- Have `typescript` installed as a dependency
- Configure VS Code to use your workspace TypeScript version (see [VS Code documentation](https://code.visualstudio.com/docs/typescript/typescript-compiling#_using-the-workspace-version-of-typescript))
::
::read-more{icon="i-simple-icons-github" to="https://github.com/KazariEX/dxup" target="_blank"}
Learn more about **@dxup/nuxt**.
::

View File

@@ -0,0 +1,108 @@
---
title: "Features"
description: "Enable or disable optional Nuxt features to unlock new possibilities."
---
Some features of Nuxt are available on an opt-in basis, or can be disabled based on your needs.
## `features`
### devLogs
Stream server logs to the client as you are developing. These logs can be handled in the `dev:ssr-logs` hook.
By default, this is enabled in development (when test mode is not active).
If set to `silent`, the logs will not be printed to the browser console.
```ts [nuxt.config.ts]
export default defineNuxtConfig({
features: {
devLogs: true,
},
})
```
### inlineStyles
Inlines styles when rendering HTML. This is currently available only when using Vite.
You can also pass a function that receives the path of a Vue component and returns a boolean indicating whether to inline the styles for that component.
It defaults to `(id) => id.includes('.vue')`.
```ts [nuxt.config.ts]
export default defineNuxtConfig({
features: {
inlineStyles: false, // or a function to determine inlining
},
})
```
### noScripts
Turn off rendering of Nuxt scripts and JavaScript resource hints. Can also be configured granularly within `routeRules`.
You can also disable scripts more granularly within `routeRules`.
If set to 'production' or `true`, JavaScript will be disabled in production mode only. If set to 'all', JavaScript will be disabled in both development and production modes.
```ts [nuxt.config.ts]
export default defineNuxtConfig({
features: {
noScripts: true, // or 'production' | 'all' | false
},
})
```
## `future`
There is also a `future` namespace for early opting-in to new features that will become default in a future (possibly major) version of the framework.
### compatibilityVersion
This enables early access to Nuxt features or flags.
Setting `compatibilityVersion` to `5` changes defaults throughout your Nuxt configuration to opt in to Nuxt v5 behaviour, including enabling the [Vite Environment API](/docs/4.x/guide/going-further/experimental-features#viteenvironmentapi).
```ts
export default defineNuxtConfig({
future: {
compatibilityVersion: 5,
},
})
```
::read-more{to="/docs/4.x/getting-started/upgrade#testing-nuxt-5"}
Learn more about testing Nuxt 5.
::
### multiApp
This enables early access to the experimental multi-app support. You can follow the [tracker issue #21635](https://github.com/nuxt/nuxt/issues/21635) to see the progress of multi-app support in Nuxt.
```ts [nuxt.config.ts]
export default defineNuxtConfig({
future: {
multiApp: true,
},
})
```
### typescriptBundlerResolution
This enables 'Bundler' module resolution mode for TypeScript, which is the recommended setting for frameworks like Nuxt and [Vite](https://vite.dev/guide/performance#reduce-resolve-operations).
It improves type support when using modern libraries with `exports`.
See [the original TypeScript pull request](https://github.com/microsoft/TypeScript/pull/51669).
You can set it to false to use the legacy 'Node' mode, which is the default for TypeScript.
```ts [nuxt.config.ts]
export default defineNuxtConfig({
future: {
typescriptBundlerResolution: false,
},
})
```

Some files were not shown because too many files have changed in this diff Show More