Nuxt.js极简入门
本文面向有 Vue.js 基础的开发者,通过通俗易懂的方式介绍 Nuxt.js 框架。我们会从一个简单的例子开始,逐步理解为什么需要 Nuxt、它解决了什么问题、以及如何使用它。
# 一、从一个问题开始
# 普通 Vue 项目的痛点
假设你用 Vue 3 开发了一个博客网站,遇到了这些问题:
<!-- 普通 Vue 项目 -->
<template>
<div>
<h1>{{ post.title }}</h1>
<p>{{ post.content }}</p>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const post = ref(null)
// 问题1:数据在客户端获取
onMounted(async () => {
const response = await fetch('/api/posts/1')
post.value = await response.json()
})
</script>
你会遇到的问题:
- SEO 差:搜索引擎爬虫访问你的网站时,看到的是空白页面(因为数据要等 JavaScript 执行后才加载)
- 首屏慢:用户要等 JavaScript 下载→执行→获取数据→渲染,才能看到内容
- 路由麻烦:需要手动配置 Vue Router,写一堆路由代码
- 目录混乱:项目大了之后,不知道文件该放哪里
# Nuxt 如何解决
<!-- Nuxt 项目 -->
<template>
<div>
<h1>{{ post.title }}</h1>
<p>{{ post.content }}</p>
</div>
</template>
<script setup>
// Nuxt 在服务器端就把数据准备好了
const { data: post } = await useFetch('/api/posts/1')
</script>
Nuxt 的优势:
- ✅ SEO 好:服务器直接返回完整 HTML,搜索引擎能看到内容
- ✅ 首屏快:用户立即看到内容,无需等待 JavaScript
- ✅ 自动路由:创建
pages/about.vue文件,自动生成/about路由 - ✅ 约定优于配置:清晰的目录结构,不用纠结文件放哪
# 二、Nuxt.js 是什么
# 简单理解
Nuxt.js = Vue.js + 服务端渲染 + 最佳实践
如果你熟悉 Vue.js,建议先阅读 Vue.js快速入门。
打个比方:
- Vue.js:像是一套工具(锤子、螺丝刀、扳手)
- Nuxt.js:像是整套装修方案(工具 + 施工流程 + 设计规范)
# Nuxt 做了什么
- 自动配置:帮你配置好 Vue Router、Vuex/Pinia、构建工具等
- 服务端渲染(SSR):在服务器上运行 Vue,生成 HTML 返回给浏览器
- 文件路由:根据文件结构自动生成路由,不用手写路由配置
- 自动导入:组件、函数自动导入,不用写
import - 全栈能力:可以在同一个项目里写前端和后端 API
# 一个直观的对比
普通 Vue 项目:
// 需要手动配置路由
import { createRouter } from 'vue-router'
import Home from './pages/Home.vue'
import About from './pages/About.vue'
const router = createRouter({
routes: [
{ path: '/', component: Home },
{ path: '/about', component: About }
]
})
Nuxt 项目:
只需要创建文件:
pages/
index.vue → 自动生成 / 路由
about.vue → 自动生成 /about 路由
# 三、创建第一个 Nuxt 项目
# 1. 安装和创建
# 创建项目(会问你一些问题,一路回车就行)
npx nuxi@latest init my-blog
# 进入项目
cd my-blog
# 安装依赖
npm install
# 启动开发服务器
npm run dev
打开浏览器访问 http://localhost:3000,你会看到 Nuxt 的欢迎页面。
# 2. 项目结构(只关注核心)
my-blog/
├── pages/ # 👈 页面文件(自动生成路由)
│ └── index.vue
├── components/ # 👈 组件(自动导入)
├── public/ # 👈 静态文件(如图片)
├── server/ # 👈 后端 API(可选)
│ └── api/
├── app.vue # 👈 应用入口
└── nuxt.config.ts # 👈 配置文件
初学者只需关注这几个目录:
pages/:放页面components/:放组件public/:放图片等静态文件
# 四、理解服务端渲染(SSR)
# 传统方式(客户端渲染)
1. 浏览器请求页面
↓
2. 服务器返回几乎空白的 HTML
<div id="app"></div>
↓
3. 浏览器下载 Vue.js 和你的代码
↓
4. Vue 开始运行,获取数据
↓
5. 渲染页面
↓
6. 用户终于看到内容 😰(慢)
# Nuxt 方式(服务端渲染)
1. 浏览器请求页面
↓
2. 服务器运行 Vue,获取数据,生成完整 HTML
↓
3. 浏览器接收完整 HTML
↓
4. 用户立即看到内容 😊(快)
↓
5. Vue 接管页面,变成可交互的应用
# 实际例子
访问一个博客文章页面:
传统方式(CSR):
<!-- 浏览器收到的 HTML -->
<div id="app"></div>
<script src="app.js"></script>
<!-- 用户看到:空白 → 转圈 → 内容出现 -->
Nuxt 方式(SSR):
<!-- 浏览器收到的 HTML -->
<div id="app">
<h1>为什么学习 Nuxt.js</h1>
<p>Nuxt.js 是一个基于 Vue.js 的全栈框架...</p>
</div>
<script src="app.js"></script>
<!-- 用户看到:内容立即出现 ✨ -->
# 五、核心功能(通过例子学习)
# 1. 文件路由(最简单)
只需要创建文件,路由自动生成:
pages/
├── index.vue → /
├── about.vue → /about
├── blog/
│ ├── index.vue → /blog
│ └── [id].vue → /blog/123(动态路由)
└── user/
└── [name].vue → /user/alice
示例:创建关于页面
<!-- pages/about.vue -->
<template>
<div>
<h1>关于我们</h1>
<p>这是关于页面</p>
</div>
</template>
访问 http://localhost:3000/about,立即生效!
示例:动态路由
<!-- pages/blog/[id].vue -->
<template>
<div>
<h1>博客文章 {{ id }}</h1>
</div>
</template>
<script setup>
const route = useRoute()
const id = route.params.id // 获取 URL 中的 id
</script>
访问 http://localhost:3000/blog/123,id 就是 123。
# 2. 页面跳转
<template>
<div>
<!-- 使用 NuxtLink 组件跳转 -->
<NuxtLink to="/">首页</NuxtLink>
<NuxtLink to="/about">关于</NuxtLink>
<NuxtLink to="/blog/123">文章123</NuxtLink>
</div>
</template>
<script setup>
// 或者用代码跳转
const router = useRouter()
function goToAbout() {
router.push('/about')
}
</script>
# 3. 获取数据(核心)
这是 Nuxt 最重要的功能之一。
错误示例(Vue 的方式,在 Nuxt 中不推荐):
<script setup>
import { ref, onMounted } from 'vue'
const posts = ref([])
// ❌ 不推荐:数据在客户端获取,SEO 不友好
onMounted(async () => {
const response = await fetch('/api/posts')
posts.value = await response.json()
})
</script>
正确示例(Nuxt 的方式):
<script setup>
// ✅ 推荐:数据在服务器端获取,SEO 友好
const { data: posts } = await useFetch('/api/posts')
</script>
<template>
<div>
<ul>
<li v-for="post in posts" :key="post.id">
{{ post.title }}
</li>
</ul>
</div>
</template>
useFetch 的魔法:
- 在服务器上:发送真实的 HTTP 请求获取数据
- 在浏览器上:复用服务器获取的数据(不会重复请求)
# 4. 自动导入组件
创建组件后,不需要手动 import。
<!-- components/MyButton.vue -->
<template>
<button class="my-button">
<slot />
</button>
</template>
<style scoped>
.my-button {
background: blue;
color: white;
padding: 10px 20px;
}
</style>
<!-- pages/index.vue -->
<template>
<div>
<!-- 直接使用,无需 import! -->
<MyButton>点击我</MyButton>
</div>
</template>
# 5. 布局(Layouts)
想要多个页面共享相同的头部和底部?使用布局!
<!-- layouts/default.vue -->
<template>
<div>
<!-- 网站头部 -->
<header>
<nav>
<NuxtLink to="/">首页</NuxtLink>
<NuxtLink to="/about">关于</NuxtLink>
</nav>
</header>
<!-- 页面内容显示在这里 -->
<main>
<slot />
</main>
<!-- 网站底部 -->
<footer>
<p>© 2024 我的博客</p>
</footer>
</div>
</template>
所有页面自动使用这个布局!
# 6. SEO 优化
给每个页面设置标题和描述:
<!-- pages/about.vue -->
<script setup>
useHead({
title: '关于我们',
meta: [
{ name: 'description', content: '了解我们团队的故事' }
]
})
</script>
<template>
<div>
<h1>关于我们</h1>
</div>
</template>
浏览器标签页会显示"关于我们",搜索引擎也能看到这些信息。
# 六、创建一个简单博客
让我们用 Nuxt 创建一个完整的小博客。
# 1. 创建首页
<!-- pages/index.vue -->
<script setup>
// 获取文章列表
const { data: posts } = await useFetch('/api/posts')
</script>
<template>
<div class="home">
<h1>我的博客</h1>
<div class="posts">
<div v-for="post in posts" :key="post.id" class="post-card">
<h2>
<NuxtLink :to="`/posts/${post.id}`">
{{ post.title }}
</NuxtLink>
</h2>
<p>{{ post.summary }}</p>
</div>
</div>
</div>
</template>
<style scoped>
.home {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.post-card {
margin-bottom: 30px;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
}
.post-card h2 a {
color: #42b983;
text-decoration: none;
}
</style>
# 2. 创建文章详情页
<!-- pages/posts/[id].vue -->
<script setup>
const route = useRoute()
const id = route.params.id
// 获取文章详情
const { data: post } = await useFetch(`/api/posts/${id}`)
// 设置页面标题
useHead({
title: post.value?.title || '文章详情'
})
</script>
<template>
<div class="post-detail">
<h1>{{ post.title }}</h1>
<p class="date">发布于: {{ post.date }}</p>
<div class="content">
{{ post.content }}
</div>
<NuxtLink to="/" class="back-link">← 返回首页</NuxtLink>
</div>
</template>
<style scoped>
.post-detail {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.date {
color: #666;
font-size: 14px;
}
.content {
margin: 30px 0;
line-height: 1.6;
}
.back-link {
color: #42b983;
text-decoration: none;
}
</style>
# 3. 创建后端 API
在同一个项目里写后端接口!
// server/api/posts.ts
export default defineEventHandler(() => {
// 这里可以连接数据库,这里为了演示用假数据
return [
{
id: 1,
title: '学习 Nuxt.js',
summary: 'Nuxt.js 让 Vue 开发更简单',
content: 'Nuxt.js 是一个很棒的框架...',
date: '2024-01-15'
},
{
id: 2,
title: '服务端渲染的好处',
summary: 'SEO 和性能的双重提升',
content: '服务端渲染可以...',
date: '2024-01-16'
}
]
})
// server/api/posts/[id].ts
export default defineEventHandler((event) => {
const id = getRouterParam(event, 'id')
// 模拟从数据库获取
const posts = {
'1': {
id: 1,
title: '学习 Nuxt.js',
content: 'Nuxt.js 是一个基于 Vue.js 的全栈框架,它让服务端渲染变得简单...',
date: '2024-01-15'
},
'2': {
id: 2,
title: '服务端渲染的好处',
content: '服务端渲染可以提升 SEO,加快首屏加载...',
date: '2024-01-16'
}
}
return posts[id]
})
# 4. 添加布局
<!-- layouts/default.vue -->
<template>
<div class="layout">
<header class="header">
<nav>
<NuxtLink to="/" class="logo">我的博客</NuxtLink>
<div class="nav-links">
<NuxtLink to="/">首页</NuxtLink>
<NuxtLink to="/about">关于</NuxtLink>
</div>
</nav>
</header>
<main class="main">
<slot />
</main>
<footer class="footer">
<p>© 2024 我的博客. Powered by Nuxt.js</p>
</footer>
</div>
</template>
<style scoped>
.layout {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.header {
background: #42b983;
color: white;
padding: 1rem 2rem;
}
.header nav {
max-width: 1200px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
}
.logo {
font-size: 1.5rem;
font-weight: bold;
color: white;
text-decoration: none;
}
.nav-links a {
margin-left: 2rem;
color: white;
text-decoration: none;
}
.main {
flex: 1;
padding: 2rem;
}
.footer {
background: #f5f5f5;
padding: 2rem;
text-align: center;
color: #666;
}
</style>
现在运行 npm run dev,你就有了一个完整的博客!
# 七、常用功能
# 1. 环境变量
# .env
NUXT_PUBLIC_API_BASE=https://api.example.com
<script setup>
const config = useRuntimeConfig()
const apiBase = config.public.apiBase
</script>
# 2. 状态管理(跨组件共享数据)
// composables/useUser.ts
export const useUser = () => {
// 创建全局共享的状态
const user = useState('user', () => ({
name: '',
email: ''
}))
const login = (name: string, email: string) => {
user.value = { name, email }
}
const logout = () => {
user.value = { name: '', email: '' }
}
return {
user,
login,
logout
}
}
<!-- 任何组件中使用 -->
<script setup>
const { user, login } = useUser()
</script>
<template>
<div v-if="user.name">
欢迎, {{ user.name }}
</div>
</template>
# 3. 中间件(路由守卫)
// middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
const { user } = useUser()
// 如果用户未登录,跳转到登录页
if (!user.value.name) {
return navigateTo('/login')
}
})
<!-- pages/admin.vue -->
<script setup>
// 使用中间件保护这个页面
definePageMeta({
middleware: 'auth'
})
</script>
<template>
<div>
<h1>管理后台</h1>
</div>
</template>
# 4. 加载状态
<script setup>
const { data: posts, pending, error } = await useFetch('/api/posts')
</script>
<template>
<div>
<!-- 加载中 -->
<div v-if="pending">加载中...</div>
<!-- 错误 -->
<div v-else-if="error">出错了: {{ error.message }}</div>
<!-- 数据 -->
<div v-else>
<div v-for="post in posts" :key="post.id">
{{ post.title }}
</div>
</div>
</div>
</template>
# 八、部署上线
# 1. 构建项目
# 生成生产版本
npm run build
# 预览生产版本
npm run preview
# 2. 部署到 Vercel(最简单)
- 把代码推送到 GitHub
- 访问 vercel.com (opens new window)
- 导入你的 GitHub 仓库
- 点击部署,完成!
Vercel 会自动识别 Nuxt 项目并正确部署。
# 3. 部署到服务器
# 构建
npm run build
# 上传 .output 文件夹到服务器
# 在服务器上运行
node .output/server/index.mjs
# 九、常见问题
# 1. 什么时候用 Nuxt?
适合用 Nuxt:
- ✅ 需要 SEO(博客、电商、新闻网站)
- ✅ 需要快速首屏加载
- ✅ 想要约定式的项目结构
- ✅ 需要全栈能力(前端 + API)
不适合用 Nuxt:
- ❌ 纯后台管理系统(不需要 SEO)
- ❌ 对服务器资源有严格限制
- ❌ 只需要简单的静态网站
# 2. SSR vs SSG vs SPA
// nuxt.config.ts
export default defineNuxtConfig({
// SSR: 每次请求都在服务器渲染(默认)
ssr: true,
// SSG: 构建时生成静态 HTML
// 适合博客、文档站
// SPA: 关闭 SSR,变成普通 Vue 应用
ssr: false
})
# 3. 只在客户端运行的代码
有些代码只能在浏览器运行(如操作 window):
<script setup>
onMounted(() => {
// ✅ onMounted 只在客户端执行
console.log(window.innerWidth)
})
</script>
<template>
<!-- ✅ ClientOnly 组件包裹的内容只在客户端渲染 -->
<ClientOnly>
<MapComponent />
</ClientOnly>
</template>
# 4. 数据获取的最佳实践
<script setup>
// ✅ 推荐:使用 useFetch
const { data } = await useFetch('/api/posts')
// ❌ 不推荐:在 onMounted 中获取
onMounted(async () => {
const response = await fetch('/api/posts')
// ...
})
// ✅ 推荐:带参数的请求
const page = ref(1)
const { data } = await useFetch('/api/posts', {
query: { page } // page 变化时自动重新请求
})
</script>
# 十、学习路径
# 1. 基础阶段(你现在在这里)
- ✅ 理解为什么需要 Nuxt
- ✅ 掌握文件路由
- ✅ 学会用
useFetch获取数据 - ✅ 完成一个简单项目
# 2. 进阶阶段
- 学习状态管理(Pinia)
- 理解服务端渲染原理
- 掌握中间件和插件
- 学习性能优化
# 3. 高级阶段
- 自定义服务器
- 深入理解 Nitro 引擎
- 模块开发
- 复杂的部署场景
# 推荐资源
- Nuxt 3 官方文档 (opens new window):非常详细易懂
- Nuxt 3 中文文档 (opens new window)
- Vue.js快速入门:补充 Vue 基础
- Vite极简入门:了解构建工具
# 十一、总结
# Nuxt 的核心价值
- 简化开发:自动路由、自动导入,专注业务逻辑
- 性能优化:服务端渲染,首屏秒开
- SEO 友好:搜索引擎能看到完整内容
- 全栈能力:一个项目搞定前后端
# 快速回顾
创建项目 npx nuxi init my-app
启动开发 npm run dev
创建页面 pages/about.vue → /about 路由
获取数据 await useFetch('/api/data')
页面跳转 <NuxtLink to="/about">
SEO 优化 useHead({ title: '标题' })
后端 API server/api/hello.ts
# 下一步
- 动手实践:创建一个真实项目(博客、作品集、商城)
- 阅读文档:Nuxt 官方文档写得很好,值得细读
- 学习 Vue:如果 Vue 基础不够扎实,先补充 Vue 知识
- 关注社区:查看优秀的开源 Nuxt 项目
记住:Nuxt 不是魔法,它只是让 Vue 开发更简单。先理解 Vue,再用 Nuxt,就会发现一切都很自然。
祝你变得更强!