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

Lambda@Edge返回200但页面样式无法加载问题求助

问题分析与解决办法

核心问题排查

页面返回200但空白且无报错,结合代码逻辑,主要存在以下关键问题:

1. Content-Type 头部未动态更新

代码中staticHeaders的content-type值初始化后固定为text/html,处理JS/CSS文件时仅修改了contentType变量,但未同步更新staticHeaders内的对应值。浏览器收到JS/CSS文件时,头部仍标记为text/html,无法正确解析资源,导致页面依赖的脚本、样式失效,最终显示空白。

2. 异步处理逻辑错误(根路径HTML替换)

根路径处理时,代码错误地将replaceAll直接调用在Promise对象上:

contents = await fetchIndex('index.html').replaceAll("<script ", `<script nonce="${nonce}" `);

await仅作用于fetchIndex,但.replaceAll是直接在未解析的Promise对象上执行,导致contents变成无效的Promise对象,返回给浏览器的内容异常,页面空白。

3. 非根路径资源缺少Content-Type头部

最后一个else分支中,返回的响应头仅包含CSP,未设置content-type,浏览器无法识别资源类型,导致图片、其他页面资源无法正确渲染。

4. Async函数与Callback混用

Handler声明为async函数,但同时使用callback返回响应,可能导致Lambda@Edge的响应处理时序混乱,引发隐性异常。


代码修复方案

针对以上问题,修改后的完整代码如下:

import { GetObjectCommand, S3Client } from "@aws-sdk/client-s3";
import { randomBytes } from "crypto";

const region = "eu-west-2";
const s3Client = new S3Client({ region });
const bucketName = "mywebsitebucket";

// 生成通用CSP规则
const getGeneralCsp = () => "default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; object-src 'none'; base-uri 'self'; connect-src 'self' data: https://api.mywebsite https://cognito-idp.eu-west-2.amazonaws.com/ https://api.gleif.org; font-src 'self'; frame-src 'self'; img-src data: 'self'; manifest-src 'self'; media-src 'self'; form-action 'none'; frame-ancestors 'self';";

// 生成带Nonce的CSP规则
const generateCsp = () => {
  const nonce = randomBytes(16).toString("base64");
  return {
    csp: `default-src 'self'; script-src 'nonce-${nonce}' 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; object-src 'none'; base-uri 'self'; connect-src 'self' data: https://api.mywebsite https://cognito-idp.eu-west-2.amazonaws.com/ https://api.gleif.org; font-src 'self'; frame-src 'self'; img-src data: 'self'; manifest-src 'self'; media-src 'self'; form-action 'none'; frame-ancestors 'self';`,
    nonce,
  };
};

// 从S3获取文件内容并转为字符串
async function fetchIndex(key) {
  try{
    console.log(`Key is -------------${key}`);
    const command = new GetObjectCommand({ Bucket: bucketName, Key: key });
    const response = await s3Client.send(command);
    
    const streamToString = (stream) =>
      new Promise((resolve, reject) => {
        const chunks = [];
        stream.on('data', (chunk) => chunks.push(chunk));
        stream.on('error', reject);
        stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
      });

    return await streamToString(response.Body);
  } catch (err) {
    console.error('Error fetching file from S3:', err);
    throw err;
  }
}

export const handler = async (event) => {
  const request = event.Records[0].cf.request;
  const uri = request.uri;
  const modifiedUri = uri.startsWith("/") ? uri.substring(1) : uri;
  const {csp, nonce} = generateCsp();
  const generalCsp = getGeneralCsp();

  try {
    // 按资源类型分情况处理
    if (uri.endsWith('.js')) {
      const contents = await fetchIndex(modifiedUri);
      return {
        status: '200',
        body: contents,
        headers: {
          "content-type": [{key: "Content-Type", value: "application/javascript"}],
          "content-security-policy": [{key: "Content-Security-Policy", value: generalCsp}]
        }
      };
    } else if (uri.endsWith('.css')) {
      const contents = await fetchIndex(modifiedUri);
      return {
        status: '200',
        body: contents,
        headers: {
          "content-type": [{key: "Content-Type", value: "text/css"}],
          "content-security-policy": [{key: "Content-Security-Policy", value: generalCsp}]
        }
      };
    } else if(uri === "/") {
      const contents = await fetchIndex('index.html');
      // 先获取HTML内容再执行nonce替换
      const modifiedContents = contents.replaceAll("<script ", `<script nonce="${nonce}" `);
      return {
        status: '200',
        body: modifiedContents,
        headers: {
          "content-type": [{key: "Content-Type", value: "text/html"}],
          "content-security-policy": [{key: "Content-Security-Policy", value: csp}]
        }
      };
    } else {
      const contents = await fetchIndex(modifiedUri);
      // 根据后缀自动匹配Content-Type,可按需扩展
      let contentType = 'text/html';
      if(uri.endsWith('.png')) contentType = 'image/png';
      if(uri.endsWith('.jpg') || uri.endsWith('.jpeg')) contentType = 'image/jpeg';
      if(uri.endsWith('.svg')) contentType = 'image/svg+xml';

      return {
        status: '200',
        body: contents,
        headers: {
          "content-type": [{key: "Content-Type", value: contentType}],
          "content-security-policy": [{key: "Content-Security-Policy", value: generalCsp}]
        }
      };
    }
  } catch (error) {
    // 统一错误响应处理
    let errorMsg = 'Not Found';
    if(uri.endsWith('.js')) errorMsg = 'Not Found JS';
    else if(uri.endsWith('.css')) errorMsg = 'Not Found CSS';
    else if(uri === "/") errorMsg = 'Not Found Index';
    
    return {
      status: '404',
      body: errorMsg,
      headers: {
        "content-type": [{key: "Content-Type", value: "text/plain"}],
        "content-security-policy": [{key: "Content-Security-Policy", value: generalCsp}]
      }
    };
  }
};

验证步骤

  1. 部署修改后的Lambda@Edge函数到CloudFront,确保关联的是viewer request事件。
  2. 打开浏览器开发者工具的网络面板,检查每个资源的Content-Type头部是否与资源类型匹配。
  3. 查看控制台面板,确认是否存在脚本加载、解析类错误(修复后若有其他问题会显性展示)。
  4. 手动清除CloudFront缓存,避免旧的错误响应干扰测试结果。

内容的提问来源于stack exchange,提问作者ShanWave007

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 22:10:54