全栈开发个人博客10.CDN存储

109 阅读2分钟

选择AWS S3 ,5G存储,免费12个月。其实主要原因是,Vercel 托管项目,免费用户每个月100g流量,超出后不交钱账号都不能用了,必须要加cdn。

1. 注册AWS账号,创建S3 Bucket,具体步骤不说了,拿到以下凭证,粘贴到.env文件中

APP_AWS_ACCESS_KEY = 'xxx'
APP_AWS_SECRET_KEY = 'xxx+xxx'
APP_AWS_REGION = 'ap-southeast-2'
AWS_S3_BUCKET_NAME_ASSETS = 'thisiscz-assets'

NEXT_PUBLIC_AWS_S3_BUCKET_NAME_ASSETS = 'thisiscz-assets'
NEXT_PUBLIC_AWS_S3_ASEETSPREFIX = 'https://thisiscz-assets.s3.ap-southeast-2.amazonaws.com'

2. S3 设置访问权限和CORS

  • 允许所有公开访问

  • 存储桶策略

    {
      "Version": "2008-10-17",
      "Statement": [
          {
              "Sid": "AllowPublicRead",
              "Effect": "Allow",
              "Principal": {
                  "AWS": "*"
              },
              "Action": "s3:GetObject",
              "Resource": "arn:aws:s3:::thisiscz-assets/*"
          }
      ]
    }
    
  • CORS 配置

    允许自己的域名访问

    [  {    "AllowedHeaders": [        "*"    ],
        "AllowedMethods": [
            "PUT",
            "POST",
            "DELETE"
        ],
        "AllowedOrigins": [
            "http://localhost:3000",
            "https://thisiscz.vercel.app"
        ],
        "ExposeHeaders": []
      }
    ]
    

3. Next.js 构建后将静态资源上传到 S3 bucket 中

// package.json
    "build": "next build && node uploadToS3.js",

可以将 public 和 构建后的静态资源 .next/static/ 都传过去

// upload-to-s3.js
const AWS = require('aws-sdk')
const fs = require('fs')
const path = require('path')
require('dotenv').config()

// 添加获取Content-Type的函数
function getContentType(filePath) {
  const ext = path.extname(filePath).toLowerCase()
  const contentTypes = {
    '.css': 'text/css',
    '.js': 'application/javascript',
    '.html': 'text/html',
    '.json': 'application/json',
    '.png': 'image/png',
    '.jpg': 'image/jpeg',
    '.jpeg': 'image/jpeg',
    '.gif': 'image/gif',
    '.svg': 'image/svg+xml',
    '.ico': 'image/x-icon',
    '.woff': 'font/woff',
    '.woff2': 'font/woff2',
    '.ttf': 'font/ttf',
    '.eot': 'application/vnd.ms-fontobject',
    '.otf': 'font/otf',
  }
  return contentTypes[ext] || 'application/octet-stream'
}

const s3 = new AWS.S3({
  accessKeyId: process.env.APP_AWS_ACCESS_KEY,
  secretAccessKey: process.env.APP_AWS_SECRET_KEY,
  region: process.env.APP_AWS_REGION,
})

async function uploadDir(dirPath, s3Path = '') {
  const files = fs.readdirSync(dirPath)

  for (const file of files) {
    const filePath = path.join(dirPath, file)
    const s3Key = path.join(s3Path, path.relative(process.cwd(), filePath))
    const fileStat = fs.statSync(filePath)

    if (fileStat.isDirectory()) {
      await uploadDir(filePath, s3Path)
    } else {
      const fileContent = fs.readFileSync(filePath)

      const params = {
        Bucket: process.env.AWS_S3_BUCKET_NAME_ASSETS,
        Key: s3Key.replace('.next', '_next'),
        Body: fileContent,
        ContentType: getContentType(filePath), // 添加ContentType
      }

      try {
        await s3.upload(params).promise()
        console.log(
          `Uploaded ${filePath} to s3://${params.Bucket}/${params.Key} with Content-Type: ${params.ContentType}`,
        )
      } catch (err) {
        console.error(`Error uploading ${filePath}:`, err)
      }
    }
  }
}

const assetsDir = path.join(process.cwd(), '.next/static/')
const publicDir = path.join(process.cwd(), 'public/')

uploadDir(assetsDir)
  .then(() => {
    console.log('Upload assets complete!')
  })
  .catch((err) => {
    console.error('Upload assets failed:', err)
  })

uploadDir(publicDir)
  .then(() => {
    console.log('Upload public files complete!')
  })
  .catch((err) => {
    console.error('Upload public files failed:', err)
  })

4. 生产环境调整静态资源的 assetPrefix

// next.config.ts
const nextConfig: NextConfig = {
  /* config options here */
  assetPrefix: __IS_PROD__ ? NEXT_PUBLIC_AWS_S3_ASEETSPREFIX : undefined
}