如何在AWS Amplify托管的Next.js应用中为租户动态创建子域名?
方案一:Wildcard子域名+应用内路由(快速实现)
这种方案适合大多数场景,无需动态创建独立子域名,通过通配符覆盖所有子域名,再在应用内识别租户并加载对应内容。
1. 在Amplify配置Wildcard子域名
进入Amplify应用的「域名管理」,点击「添加域名」,输入*.yourdomain.com,按照提示完成DNS验证(在Route 53添加对应的CNAME或A记录,Amplify会给出具体值)。等待Amplify完成SSL证书生成和部署,通常需要10-20分钟。2. Next.js中解析子域名并路由
用Next.js的Middleware来拦截请求,提取子域名作为租户标识,再转发到对应页面:import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function middleware(request: NextRequest) { const hostname = request.headers.get('host') || ''; // 匹配子域名,排除主域名、www等保留前缀 const tenantMatch = hostname.match(/^([a-z0-9-]+)\.yourdomain\.com$/i); if (tenantMatch) { const tenantSlug = tenantMatch[1].toLowerCase(); // 把租户标识通过请求头传递给页面,或者直接重写路径 const modifiedHeaders = new Headers(request.headers); modifiedHeaders.set('x-tenant-slug', tenantSlug); // 重写请求到租户专属页面,比如/pages/tenant/[slug] return NextResponse.rewrite( new URL(`/tenant/${tenantSlug}`, request.url), { headers: modifiedHeaders } ); } // 主域名请求直接放行 return NextResponse.next(); } // 指定中间件生效范围,排除静态资源和API路由 export const config = { matcher: ['/', '/((?!api|_next/static|_next/image|favicon.ico).*)'], };在页面组件中,通过
headers().get('x-tenant-slug')获取租户标识,再从数据库加载对应租户的数据。3. 租户注册时的校验
注册页面需要检查租户名:- 只能包含小写字母、数字、连字符
- 不能是保留词(如www、admin、api)
- 在数据库中检查是否已被占用,确保唯一性
方案二:动态创建独立子域名(适合需单独配置的场景)
如果需要为每个租户单独配置SSL、分支映射等,可以结合Lambda+Route 53+Amplify API实现自动化创建。
1. 编写Lambda函数处理子域名创建
创建Lambda函数,负责租户注册后的子域名自动化流程:const AWS = require('aws-sdk'); const amplify = new AWS.Amplify(); const route53 = new AWS.Route53(); exports.handler = async (event) => { const { tenantSlug, mainDomain } = event; const hostedZoneId = 'YOUR_ROUTE53_HOSTED_ZONE_ID'; const amplifyAppId = 'YOUR_AMPLIFY_APP_ID'; const amplifyCloudfrontDomain = 'YOUR_AMPLIFY_CLOUDFRONT_DOMAIN'; // 从Amplify域名管理中获取 // 校验租户名合法性 if (!/^[a-z0-9-]+$/.test(tenantSlug)) { return { statusCode: 400, body: JSON.stringify({ error: '租户名只能包含小写字母、数字和连字符' }) }; } // 检查子域名是否已存在于Route 53 const existingRecords = await route53.listResourceRecordSets({ HostedZoneId: hostedZoneId, StartRecordName: `${tenantSlug}.${mainDomain}`, StartRecordType: 'CNAME' }).promise(); if (existingRecords.ResourceRecordSets.length > 0) { return { statusCode: 409, body: JSON.stringify({ error: '该租户名已被占用' }) }; } // 在Route 53添加CNAME记录 await route53.changeResourceRecordSets({ HostedZoneId: hostedZoneId, ChangeBatch: { Changes: [{ Action: 'CREATE', ResourceRecordSet: { Name: `${tenantSlug}.${mainDomain}`, Type: 'CNAME', TTL: 300, ResourceRecords: [{ Value: amplifyCloudfrontDomain }] } }] } }).promise(); // 在Amplify添加子域名映射 await amplify.createDomain({ appId: amplifyAppId, domainName: mainDomain, subDomainSettings: [{ prefix: tenantSlug, branchName: 'main' // 映射到你的主分支 }] }).promise(); return { statusCode: 200, body: JSON.stringify({ message: '子域名创建成功,几分钟后即可使用' }) }; };给Lambda配置权限:允许访问Route 53的记录读写、Amplify的域名管理操作。
2. 集成到租户注册流程
在Next.js的注册页面,通过API Gateway调用该Lambda函数,传入租户名和主域名。用户提交注册后,等待Lambda完成操作,Amplify会自动为子域名申请ACM证书,通常需要5-10分钟,之后租户即可使用<tenant>.yourdomain.com访问。3. 注销清理
当租户注销时,需要调用另一个Lambda函数,删除Route 53中的对应记录和Amplify中的子域名配置。
注意事项
- SSL证书:两种方案Amplify都会自动通过ACM申请免费SSL证书,无需手动处理
- 性能:Wildcard方案几乎无延迟,适合快速上线;动态方案需要等待证书生成,适合有特殊配置需求的场景
- 唯一性校验:必须在注册环节严格检查租户名,避免冲突
内容的提问来源于stack exchange,提问作者Syed Haider Ali Zaidi

