学完这篇,你能从零跑起一个 Nuxt 3 项目,把它打包成 .output 产物,并用 PM2 + Nginx 部署到自己的服务器上正常访问。
第一步:确认环境,创建项目
Nuxt 3 要求 Node 18 以上,建议直接用 20 或 22 的 LTS 版本:
node -v
npm -v
然后创建项目(全程用 npx 拉起官方脚手架,不需要全局安装):
npx nuxi@latest init nuxt-ssr-demo
cd nuxt-ssr-demo
npm install
npm run dev
打开 http://localhost:3000,看到 Nuxt 欢迎页就说明跑通了。
注意:nuxi init 会让你选包管理器(npm/pnpm/yarn),选了什么后面就一直用它。中途换管理器容易因为 lock 文件冲突装不上依赖。
第二步:认识三个关键目录
Nuxt 3 是约定式框架,新手先记住三处:
pages/:目录里放什么文件,就有什么路由。pages/index.vue 是首页,pages/post/[id].vue 就是 /post/123。
server/api/:写后端接口,文件名即路径。新建 server/api/hello.get.ts:
export default defineEventHandler(() => {
return { time: new Date().toISOString() }
})
访问 /api/hello 就能拿到 JSON,前后端在同一个项目里,不用另起服务。
nuxt.config.ts:全局配置。SSR 默认是开的(ssr: true),一般不用动。
第三步:写第一个 SSR 页面并验证
把 pages/index.vue 改成:
<script setup>
const { data } = await useFetch('/api/hello')
</script>
<template>
<div>
<h1>当前时间:{{ data?.time }}</h1>
</div>
</template>
想确认“真的是服务端渲染”,看网页源代码(Ctrl+U)或者:
curl http://localhost:3000
如果返回的 HTML 里已经带着渲染好的时间和文字,说明服务端生成成功,不是空壳。
注意:服务端没有 window、document、localStorage。需要浏览器的代码放进 onMounted(),或者用 if (import.meta.client) { ... } 包起来,否则构建时报 window is not defined。
注意:useFetch 只在 setup 顶层用,会帮你把数据注入 SSR;事件回调里要用 $fetch。两者混着用同一个接口,会出现服务端请求一次、客户端再请求一次的重复请求。
第四步:配置 SEO 元信息
SSR 最大的价值就是让爬虫拿到带内容的 HTML,元信息也要一起配:
<script setup>
useSeoMeta({
title: '首页 - 我的站点',
description: '这是用 Nuxt 3 搭建的服务端渲染站点',
ogTitle: '首页 - 我的站点'
})
</script>
useSeoMeta 会在服务端直出 <title> 和 <meta>,不需要等 JS 执行。
第五步:构建产物
npm run build
构建完会生成 .output/ 目录,里面是可直接运行的 Node 服务。本地先验证一遍:
PORT=3001 node .output/server/index.mjs
访问 http://localhost:3001 正常,就可以准备上服务器了。
注意:构建机和运行机的 Node 大版本要一致(比如都是 20.x),跨大版本可能因原生模块不兼容起不来。
第六步:服务器部署(PM2 + Nginx)
把整个项目目录(至少要有 .output/、package.json)上传到服务器,然后:
npm i -g pm2
cd /www/wwwroot/nuxt-ssr-demo
PORT=3000 pm2 start .output/server/index.mjs --name nuxt-ssr
pm2 save
pm2 startup
pm2 startup 输出一条命令,复制执行一次,开机就会自动拉起。
接着用 Nginx 反代到 3000 端口:
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
nginx -t 检查后 reload。HTTPS 证书在面板里申请即可,Nuxt 侧不用改代码,靠 X-Forwarded-Proto 识别真实协议。
注意:proxy_set_header Host 这几行别省。少了它,站内生成的绝对链接、跳转地址可能变成 127.0.0.1:3000。
第七步:处理环境变量
数据库地址、密钥这类东西放 nuxt.config.ts 的 runtimeConfig:
export default defineNuxtConfig({
runtimeConfig: {
dbHost: 'localhost', // 服务端私有
public: { siteName: '我的站' } // 客户端也能读
}
})
运行时用 NUXT_DB_HOST=xxx 这种带 NUXT_ 前缀的环境变量覆盖,页面里通过 useRuntimeConfig() 读取。
注意:别在服务端代码里直接读 process.env 又指望运行时能改——构建时若被静态替换就改不动了。老老实实走 runtimeConfig。
小结
- Node 18+,
npx nuxi@latest init 建项目,npm run dev 起开发服务
pages/ 定路由、server/api/ 写接口、nuxt.config.ts 管配置
- SSR 是否生效,看
curl 返回的 HTML 里有没有内容,别只看浏览器
- 浏览器专属 API 放
onMounted,取数用 useFetch(setup)/ $fetch(事件)
npm run build 出 .output/,node .output/server/index.mjs 启动,PM2 守护
- Nginx 反代要带
Host 和 X-Forwarded-Proto,端口用环境变量传,别写死