部署指南
生产环境部署完整指南,包含安全配置、平台部署和检查清单
本指南将帮助您安全、正确地将私有 Registry 部署到生产环境。
安全配置
环境变量
生产环境必须正确配置环境变量:
# Registry 认证令牌(必需)
REGISTRY_TOKEN=your_production_token_here
# JWT 密钥(可选,用于高级认证)
JWT_SECRET=your_jwt_secret_here
# 数据库连接(可选,用于数据库验证)
DATABASE_URL=postgresql://...生成安全令牌
使用以下命令生成强随机令牌:
# 使用 Node.js(推荐)
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
# 使用 OpenSSL
openssl rand -hex 32
# 使用 UUID
uuidgen重要提醒:
- 令牌长度至少 32 字节
- 生产环境和开发环境使用不同的令牌
- 定期轮换令牌(建议每 3-6 个月)
- 不要将令牌提交到版本控制
静态文件保护
关键安全措施:Registry 的 JSON 文件存放在 public/r/ 目录。默认情况下,Next.js 会将这些文件作为静态资源直接提供,完全绕过认证!
必须使用 Middleware 保护
确保项目根目录存在 middleware.ts 文件:
import { NextRequest, NextResponse } from "next/server";
export function middleware(request: NextRequest) {
// 拦截所有 /r/ 路径的请求
if (request.nextUrl.pathname.startsWith("/r/")) {
// 获取认证令牌
const authHeader = request.headers.get("authorization");
const bearerToken = authHeader?.replace("Bearer ", "");
const apiKey = request.headers.get("x-api-key");
const queryToken = request.nextUrl.searchParams.get("token");
const token = bearerToken || apiKey || queryToken;
// 验证令牌
if (!token || token !== process.env.REGISTRY_TOKEN) {
return NextResponse.json(
{ error: "Unauthorized", message: "认证失败" },
{ status: 401 }
);
}
// 允许访问
return NextResponse.next();
}
return NextResponse.next();
}
export const config = {
matcher: ["/r/:path*"],
};安全漏洞警告:如果没有 Middleware 保护,任何人都可以直接访问 /r/*.json 绕过所有认证!这会导致:
- ❌ 任何人可访问所有组件源码
- ❌ 私有 Registry 完全失效
- ❌ 敏感代码可能泄露
HTTPS 配置
生产环境必须使用 HTTPS:
- ✅ 加密传输,防止中间人攻击
- ✅ 保护认证令牌不被窃取
- ✅ 符合安全最佳实践
大多数部署平台(Vercel、Netlify 等)自动提供 HTTPS。
速率限制(推荐)
实现 API 速率限制,防止滥用:
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
export const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, "10 s"),
});在 API 路由中使用:
import { ratelimit } from "@/lib/ratelimit";
export async function GET(request: NextRequest) {
const ip = request.ip || "anonymous";
const { success } = await ratelimit.limit(ip);
if (!success) {
return NextResponse.json(
{ error: "Too Many Requests" },
{ status: 429 }
);
}
// 继续处理...
}平台部署
Vercel 部署
配置环境变量
在 Vercel 项目设置中:
- 进入 Settings → Environment Variables
- 添加以下变量:
REGISTRY_TOKEN:生产令牌JWT_SECRET(可选)DATABASE_URL(可选)
- 选择环境:Production、Preview、Development
构建配置
Vercel 会自动检测 Next.js 项目,无需额外配置。确保 package.json 中有:
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"registry:build": "shadcn build"
}
}部署前准备
# 本地构建测试
pnpm registry:build
pnpm build
# 确认 public/r/ 目录有生成的 JSON 文件
ls public/r/部署
# 使用 Vercel CLI
vercel
# 或推送到 Git(自动部署)
git push origin mainNetlify 部署
构建配置
创建 netlify.toml:
[build]
command = "pnpm registry:build && pnpm build"
publish = ".next"
[[plugins]]
package = "@netlify/plugin-nextjs"Docker 部署
Dockerfile 示例
FROM node:20-alpine AS base
# 依赖安装
FROM base AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm && pnpm install --frozen-lockfile
# 构建
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm install -g pnpm && \
pnpm registry:build && \
pnpm build
# 运行
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
EXPOSE 3000
ENV PORT=3000
CMD ["node", "server.js"]Docker Compose
version: '3.8'
services:
registry:
build: .
ports:
- "3000:3000"
environment:
- REGISTRY_TOKEN=${REGISTRY_TOKEN}
- NODE_ENV=production
restart: unless-stopped部署:
# 设置环境变量
export REGISTRY_TOKEN=your_token
# 启动
docker-compose up -d部署检查清单
部署前检查 ✓
安全配置
- 生成强随机令牌(至少 32 字节)
- 在部署平台配置
REGISTRY_TOKEN - 确认
.env.local不在版本控制中 -
middleware.ts文件存在且正确配置 - 启用 HTTPS(生产环境)
功能测试
- 运行
pnpm registry:build成功 -
public/r/目录包含所有组件 JSON - 本地测试认证功能正常
- 运行测试脚本
scripts/test-auth.sh全部通过
构建验证
-
pnpm build构建成功 - 无 TypeScript 错误
- 无 ESLint 错误
文档更新
- README.md 包含正确的部署 URL
- 文档中的示例 URL 已更新
- 客户端配置文档准确
部署后验证 ✓
立即验证(部署后 5 分钟内)
# 设置变量
export DOMAIN="https://your-domain.com"
export TOKEN="your_production_token"
# 测试无认证(应返回 401)
curl $DOMAIN/api/registry/button
# 测试有效认证(应返回 200)
curl -H "Authorization: Bearer $TOKEN" \
$DOMAIN/api/registry/button
# 测试静态文件保护(应返回 401)
curl $DOMAIN/r/button.json
# 测试静态文件认证访问(应返回 200)
curl -H "Authorization: Bearer $TOKEN" \
$DOMAIN/r/button.json- 无认证访问被拒绝(401)
- 有效令牌可以访问(200)
- 静态文件被 Middleware 保护
- HTTPS 正常工作
- 文档页面可访问
24 小时内检查
- 查看错误日志,无异常
- 监控 API 响应时间正常
- 无安全告警
- 客户端可以正常安装组件
一周内检查
- 分析使用数据
- 收集用户反馈
- 优化发现的性能问题
- 更新文档(如需要)
监控和日志 ✓
日志记录
确保记录关键事件:
// 记录认证失败
console.warn('Authentication failed', {
component: componentName,
ip: request.ip,
timestamp: new Date().toISOString(),
tokenPrefix: token?.substring(0, 8) + '...' // 不记录完整令牌
});
// 记录组件访问
console.log('Component accessed', {
component: componentName,
timestamp: new Date().toISOString()
});监控指标
建议监控:
- API 响应时间(目标 < 200ms)
- 错误率(目标 < 1%)
- 认证失败次数
- 活跃用户/令牌数量
告警规则
设置告警:
- 错误率突然升高
- 响应时间超过阈值
- 大量认证失败(可能的攻击)
- 服务不可用
运维
定期任务
- 每月:轮换
REGISTRY_TOKEN - 每周:检查错误日志
- 每月:审查访问模式和使用统计
- 季度:更新依赖包
令牌轮换流程
- 生成新令牌
- 在部署平台添加新令牌(暂不删除旧令牌)
- 通知所有用户更新客户端令牌
- 等待 7 天过渡期
- 删除旧令牌
备份策略
定期备份:
registry.json配置文件registry/目录下的所有组件源码- 环境变量配置(加密存储)
- 数据库(如使用)
故障恢复
如遇问题:
- 检查环境变量:确认
REGISTRY_TOKEN正确设置 - 查看日志:检查部署平台的运行日志
- 回滚版本:使用 Git 回滚到上一个稳定版本
- 测试本地:在本地环境复现问题
- 联系支持:记录详细错误信息
性能优化
缓存策略
为组件 JSON 设置合适的缓存头:
return NextResponse.json(component, {
headers: {
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=86400',
},
});CDN 配置
使用 CDN 加速静态文件访问:
- Vercel:自动提供 Edge Network
- Cloudflare:配置 CDN 规则
- 其他:配置 CDN 服务
构建优化
# 生产构建优化
NEXT_TELEMETRY_DISABLED=1 pnpm build
# 分析构建大小
pnpm build && pnpm analyze安全最佳实践
核心原则
- ✅ 始终使用 HTTPS(生产环境)
- ✅ 强随机令牌(至少 32 字节)
- ✅ Middleware 保护(防止静态文件泄露)
- ✅ 环境变量隔离(不提交到 Git)
- ✅ 定期轮换令牌(每 3-6 个月)
- ✅ 实现速率限制(防止滥用)
- ✅ 监控异常访问(日志和告警)
常见安全问题
问题:静态文件可直接访问
症状:
# 无需认证就能访问
curl https://your-domain.com/r/component.json
# 返回:组件内容(不安全!)解决:确保 middleware.ts 正确配置并拦截 /r/* 路径。
问题:令牌在日志中泄露
症状:日志中出现完整令牌字符串
解决:
// ❌ 错误
console.log('Token:', token);
// ✅ 正确
console.log('Token present:', !!token);
console.log('Token prefix:', token?.substring(0, 8) + '...');问题:环境变量未生效
症状:部署后返回 401,但本地正常
解决:
- 检查部署平台的环境变量配置
- 确认变量名称完全匹配
- 重新部署触发更新
测试脚本
部署后运行完整测试:
#!/bin/bash
# 设置变量
DOMAIN="https://your-domain.com"
TOKEN="${REGISTRY_TOKEN}"
echo "🧪 Testing production deployment..."
# 测试 1: 无认证
echo "Test 1: No auth (should fail)"
curl -s -o /dev/null -w "%{http_code}" $DOMAIN/api/registry/button
# 测试 2: 有效认证
echo "Test 2: Valid auth (should succeed)"
curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer $TOKEN" \
$DOMAIN/api/registry/button
# 测试 3: 静态文件保护
echo "Test 3: Static file protection (should fail)"
curl -s -o /dev/null -w "%{http_code}" $DOMAIN/r/button.json
# 测试 4: 静态文件认证
echo "Test 4: Static file with auth (should succeed)"
curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer $TOKEN" \
$DOMAIN/r/button.json
echo "✅ Tests completed"相关资源
部署成功后,记得更新文档中的示例 URL,并通知团队成员新的 Registry 地址!