You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 06:50:31