前端部署方案
一些约定
- 兼容目标部署平台:Railway,服务器 docker 容器,手动启动
- 兼容开发和生产环境
- 前端独立部署,且后端不开放跨域,处理好请求后端 API 代理问题
- 处理好前端 SPA 的路由问题
- 部署环境开启 gzip 压缩
公共设置
为了优化在本地测试构建 Dockerfile
的体验,以及构建出镜像的体积,我们默认使用分阶段构建。 同时将代理的路径和目标写入环境变量。
Dockerfile
FROM node:18-alpine as build-stage
WORKDIR /app
RUN corepack enable
COPY .npmrc package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
txt
node_modules
dist
.nitro
.output
txt
VITE_PROXY_TARGET="https://example.com/api/v1"
VITE_PROXY_PATH="/api"
Nginx
Nginx 是一个高性能的开源 Web 服务器,性能出色,配置文件标准丰富,是我们首选的前端部署方案。
配置示例
Dockerfile
FROM nginx:stable-alpine as production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
COPY --from=build-stage /app/nginx.conf /etc/nginx/conf.d/default.conf
COPY ./entrypoint.sh /app/entrypoint.sh
EXPOSE 80
RUN chmod +x /app/entrypoint.sh
CMD /app/entrypoint.sh
bash
# 为了避免在一些平台比如 Railway 需要手动设置 PORT 环境变量的问题,这里动态的修改 Nginx 配置的端口
p="${PORT:-80}"
sed -E -i.bak 's/listen[[:space:]]+[[:digit:]]+/listen '${p}'/' /etc/nginx/conf.d/default.conf
nginx -g "daemon off;"
ts
// 关于 `nginx.conf` 配置文件我们可以写一个 vite 插件来读取项目环境变量动态生成
import fs from "node:fs"
import type { Plugin } from "vite"
type ViteProxyPluginOptions = {
proxyPath?: string
proxyTarget?: string
}
function ViteProxyPlugin(options?: ViteProxyPluginOptions): Plugin {
const { proxyPath = "/api", proxyTarget = "http://localhost:3000" } =
options ?? {}
return {
name: "vite-proxy-plugin",
apply: "build",
closeBundle: () => {
fs.writeFileSync(
"nginx.conf",
`
server {
listen 80;
root /usr/share/nginx/html;
index index.html;
# 开启 gzip 压缩
gzip on;
gzip_types text/plain text/css application/javascript application/json;
location ^~${proxyPath}/ {
rewrite ^/(.*)$ /$1 break;
proxy_pass ${proxyTarget};
proxy_http_version 1.1;
}
location / {
try_files $uri $uri/ /index.html =404;
}
}`,
"utf-8",
)
},
}
}
ts
import { defineConfig, loadEnv } from "vite"
const env = loadEnv(
process.env["RAILWAY_ENVIRONMENT_NAME"] ??
process.env["NODE_ENV"] ??
"development",
process.cwd(),
)
const proxyPath = env["VITE_PROXY_PATH"]
const proxyTarget = env["VITE_PROXY_TARGET"]
if (!proxyPath || !proxyTarget) {
throw new Error("VITE_PROXY_PATH or VITE_PROXY_TARGET is not set")
}
export default defineConfig({
plugins: [
// ...
ViteProxyPlugin({
proxyPath,
proxyTarget,
}),
],
server: {
// 开发环境中我们借助 vite 来做 API 代理
proxy: {
[proxyPath]: {
target: proxyTarget,
secure: false,
changeOrigin: true,
},
},
},
})
Nitro
基于 Nitro 我们可以手动部署项目,并且直接支持部署到各种平台。
TIP
可以通过 vite-plugin-nitro-deploy 快速整合。
准备流程
安装 nitropack
sh
ni nitropack h3 defu
相关配置
json
{
"prepare": "nitro prepare",
"build": "vite build && nitropack build",
"start": "node .output/server/index.mjs"
}
ts
import type { NitroConfig } from "nitropack"
export default defineNitroConfig({
runtimeConfig: {
// 后端 API 代理
proxyTarget: "https://xxx.com/api/v1",
},
serverAssets: [
{
baseName: "dist",
dir: "./dist",
},
],
publicAssets: [
{
dir: "./dist",
},
],
})
json
{
"include": ["routes", "api", "./.nitro/types/nitro.d.ts"]
}
ts
export default defineEventHandler(async (event) => {
const index = await useStorage("assets/dist/").getItem("/index.html")
appendHeader(event, "Content-Type", "text/html")
return index as string
})
ts
export default defineEventHandler((event) => {
const proxyTarget =
(useRuntimeConfig(event)["proxyTarget"] as string) +
getRequestURL(event).pathname
return proxyRequest(event, proxyTarget)
})
Dockerfile
FROM node:18-alpine as production-stage
WORKDIR /app
COPY --from=build-stage /app/dist ./dist
COPY --from=build-stage /app/.output ./.output
CMD node .output/server/index.mjs
Next.js
Next.js 项目的部署主要分为 Vercel 托管以及自部署,参考官方文档给出的部署指南,其中需要注意自部署时一些额外的配置。