使用AWS CodePipeline和CDK填充React静态前端config.json的问题
问题
我正在用CDK Pipeline部署静态React前端应用,后端基于AWS Lambda、API Gateway和CloudFront托管。需要像手动部署那样,把API相关信息写入前端的config.json文件,但目前遇到流程顺序问题:
CDK Pipeline初始化时会先执行ShellStep(如npm i、npm run build)生成前端分发文件夹和CloudFormation模板,之后才会部署测试、生产等Stage。但包含API信息的CfnOutputs要等Stage部署完成后才会生成,导致config.json提前生成时是空值。
我知道CodeBuild Pipeline有envFromCfnOutputs参数可用,但Stage是在Pipeline初始化之后才定义的,TypeScript会报错。相关代码如下:
import * as cdk from "aws-cdk-lib"; import { Construct } from "constructs"; import * as pipelines from "aws-cdk-lib/pipelines"; import * as codecommit from "aws-cdk-lib/aws-codecommit"; import { Stages } from "./stages"; import { Stack, Stage } from "aws-cdk-lib"; interface PipelineStackProps extends cdk.StackProps { env: { account: string; region: string; stage: string; }; } export class PipelineStack extends cdk.Stack { constructor(scope: Construct, id: string, props: PipelineStackProps) { super(scope, id, props); /************ Grab Repo ************/ const source = codecommit.Repository.fromRepositoryName( this, "PreCallbackSMSSolution", "PreCallbackSMSSolution" ); /************ Define Pipeline & Build ShellStep (for Frontend) ************/ const Pipeline = new pipelines.CodePipeline(this, "Pipeline", { pipelineName: `CodePipeline`, selfMutation: true, crossAccountKeys: true, synthCodeBuildDefaults: { rolePolicy: [ new cdk.aws_iam.PolicyStatement({ actions: [ "codeartifact:GetAuthorizationToken", "codeartifact:GetRepositoryEndpoint", "codeartifact:ReadFromRepository", ], resources: ["*"], }), new cdk.aws_iam.PolicyStatement({ actions: ["sts:GetServiceBearerToken"], resources: ["*"], conditions: { StringEquals: { "sts:AWSServiceName": "codeartifact.amazonaws.com", }, }, }), ], }, synth: new pipelines.ShellStep("Synth", { input: pipelines.CodePipelineSource.codeCommit(source, "master"), installCommands: [ "cd $CODEBUILD_SRC_DIR/deployment", "npm install -g typescript", "npm run co:login", "npm i", ], env: { stage: props.env.stage, }, envFromCfnOutputs: { // TODO: cfn outputs need to go here! // CcpUrlOutput: TestStage.CcpUrlOutput, // loginUrlOutput: TestStage.LoginUrlOutput, // regionOutput: TestStage.RegionOutput, // apiOutput: TestStage.ApiOutput }, commands: [ "cd $CODEBUILD_SRC_DIR/frontend", "pwd", "apt-get install jq -y", "chmod +x ./generate-config.sh", "npm i", "npm run build-prod", "pwd", "cat ./src/config-prod.json", "cd ../deployment", "npx cdk synth", ], primaryOutputDirectory: "$CODEBUILD_SRC_DIR/deployment/cdk.out", }), }); /************ Initialize Test Stack & Add Stage************/ const TestStage = new Stages(this, "TestStage", { env: { account: "***********", region: "us-east-1", stage: "test" }, }); Pipeline.addStage(TestStage); /************ Initialize Prod Stack & Add Stage ************/ const ProdStage = new Stages(this, "ProdStage", { env: { account: "***********", region: "us-east-1", stage: "prod" }, }); Pipeline.addStage(ProdStage); /************ Build Pipeline ************/ Pipeline.buildPipeline(); /************ Manual Approve Stage ************/ const ApproveStage = Pipeline.pipeline.addStage({ stageName: "PromoteToProd", placement: { justAfter: Pipeline.pipeline.stage("TestStage"), }, }); ApproveStage.addAction( new cdk.aws_codepipeline_actions.ManualApprovalAction({ actionName: "Approve", additionalInformation: "Approve this deployment for production.", }) ); } }
解决方案
核心思路是把前端构建流程从Synth阶段移到对应Stage的后端部署完成之后,确保能拿到后端的CfnOutputs来生成正确的config.json,同时解决TypeScript的顺序报错问题。
1. 精简Pipeline的Synth阶段
把原来Synth里的前端构建命令移除,让Synth只负责CDK依赖安装和生成CloudFormation模板:
synth: new pipelines.ShellStep("Synth", { input: pipelines.CodePipelineSource.codeCommit(source, "master"), installCommands: [ "cd $CODEBUILD_SRC_DIR/deployment", "npm install -g typescript", "npm run co:login", "npm i", ], env: { stage: props.env.stage, }, commands: [ "cd ../deployment", "npx cdk synth", ], primaryOutputDirectory: "$CODEBUILD_SRC_DIR/deployment/cdk.out", }),
2. 在Stage中拆分部署流程,插入前端构建步骤
修改Stages类,先部署后端栈并暴露CfnOutputs,再添加ShellStep用后端输出构建前端,最后部署前端栈:
export class Stages extends cdk.Stage { public readonly apiOutput: cdk.CfnOutput; public readonly ccpUrlOutput: cdk.CfnOutput; constructor(scope: Construct, id: string, props: cdk.StageProps) { super(scope, id, props); // 1. 部署后端栈,输出API信息 const backendStack = new BackendStack(this, "BackendStack", props); this.apiOutput = backendStack.apiOutput; this.ccpUrlOutput = backendStack.ccpUrlOutput; // 2. 添加前端构建步骤,用后端输出生成config.json const buildFrontend = new pipelines.ShellStep("BuildFrontend", { input: pipelines.CodePipelineSource.codeCommit(source, "master"), commands: [ "cd $CODEBUILD_SRC_DIR/frontend", "apt-get install jq -y", "chmod +x ./generate-config.sh", "npm i", // 通过环境变量注入API信息到config.json "./generate-config.sh $API_OUTPUT $CCP_URL_OUTPUT $REGION", "npm run build-prod", ], envFromCfnOutputs: { API_OUTPUT: this.apiOutput, CCP_URL_OUTPUT: this.ccpUrlOutput, REGION: new cdk.CfnOutput(this, "RegionOutput", { value: props.env?.region || "" }), }, }); // 3. 部署前端栈,依赖构建好的前端文件 new FrontendStack(this, "FrontendStack", { ...props, frontendBuildDir: buildFrontend.outputDirectory, }); } }
3. 调整前端栈接收构建输出
修改FrontendStack,读取构建完成的前端文件并部署到S3/CloudFront:
export class FrontendStack extends cdk.Stack { constructor(scope: Construct, id: string, props: { frontendBuildDir: string; // 其他必要参数 }) { super(scope, id, props); // 创建前端存储桶 const frontendBucket = new cdk.aws_s3.Bucket(this, "FrontendBucket", { publicReadAccess: false, encryption: cdk.aws_s3.BucketEncryption.S3_MANAGED, }); // 部署构建好的前端文件到S3 new cdk.aws_s3_deployment.BucketDeployment(this, "DeployFrontend", { sources: [cdk.aws_s3_deployment.Source.asset(props.frontendBuildDir)], destinationBucket: frontendBucket, }); // 配置CloudFront分发(省略具体配置) new cdk.aws_cloudfront.Distribution(this, "FrontendDist", { defaultBehavior: { origin: new cdk.aws_cloudfront_origins.S3Origin(frontendBucket) }, }); } }
关键说明
- 每个Stage会按「后端部署 → 前端构建(注入API信息)→ 前端部署」的顺序执行,保证
config.json能获取到正确的后端输出。 - 彻底避免了在Synth阶段依赖未部署资源的问题,解决TypeScript报错。
内容的提问来源于stack exchange,提问作者Seb
相关产品推荐
相关产品推荐

