Acme

部署指南

生产环境部署完整指南,包含安全配置、平台部署和检查清单

本指南将帮助您安全、正确地将私有 Registry 部署到生产环境。

安全配置

环境变量

生产环境必须正确配置环境变量:

.env.local
# 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 文件:

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 速率限制,防止滥用:

lib/ratelimit.ts
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 路由中使用:

app/api/registry/[name]/route.ts
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 项目设置中:

  1. 进入 SettingsEnvironment Variables
  2. 添加以下变量:
    • REGISTRY_TOKEN:生产令牌
    • JWT_SECRET(可选)
    • DATABASE_URL(可选)
  3. 选择环境:Production、Preview、Development

构建配置

Vercel 会自动检测 Next.js 项目,无需额外配置。确保 package.json 中有:

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 main

Netlify 部署

配置环境变量

在 Netlify 中:

  1. Site settingsEnvironment variables
  2. 添加 REGISTRY_TOKEN 等变量

构建配置

创建 netlify.toml

netlify.toml
[build]
  command = "pnpm registry:build && pnpm build"
  publish = ".next"

[[plugins]]
  package = "@netlify/plugin-nextjs"

Docker 部署

Dockerfile 示例

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

docker-compose.yml
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
  • 每周:检查错误日志
  • 每月:审查访问模式和使用统计
  • 季度:更新依赖包

令牌轮换流程

  1. 生成新令牌
  2. 在部署平台添加新令牌(暂不删除旧令牌)
  3. 通知所有用户更新客户端令牌
  4. 等待 7 天过渡期
  5. 删除旧令牌

备份策略

定期备份:

  • registry.json 配置文件
  • registry/ 目录下的所有组件源码
  • 环境变量配置(加密存储)
  • 数据库(如使用)

故障恢复

如遇问题:

  1. 检查环境变量:确认 REGISTRY_TOKEN 正确设置
  2. 查看日志:检查部署平台的运行日志
  3. 回滚版本:使用 Git 回滚到上一个稳定版本
  4. 测试本地:在本地环境复现问题
  5. 联系支持:记录详细错误信息

性能优化

缓存策略

为组件 JSON 设置合适的缓存头:

app/api/registry/[name]/route.ts
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

安全最佳实践

核心原则

  1. 始终使用 HTTPS(生产环境)
  2. 强随机令牌(至少 32 字节)
  3. Middleware 保护(防止静态文件泄露)
  4. 环境变量隔离(不提交到 Git)
  5. 定期轮换令牌(每 3-6 个月)
  6. 实现速率限制(防止滥用)
  7. 监控异常访问(日志和告警)

常见安全问题

问题:静态文件可直接访问

症状

# 无需认证就能访问
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,但本地正常

解决

  1. 检查部署平台的环境变量配置
  2. 确认变量名称完全匹配
  3. 重新部署触发更新

测试脚本

部署后运行完整测试:

scripts/test-production.sh
#!/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 地址!