Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |
Tags
- Security
- Performance
- database
- Infra
- backend
- Ops
- SRE
- JavaScript
- Operations
- observability
- version-control
- aws
- reliability
- API
- HTTP
- react
- web
- frontend
- DevOps
- CSS
- 성능
- Git
- CI
- Debugging
- 버전관리
- Kubernetes
- Microservices
- architecture
- NextJS
- auth
Archives
- Today
- Total
고민보단 실천을
번외: Next.js Route Handler로 S3 Presigned URL 만들기 (서버만으로 presign API) 본문
카테고리 없음
번외: Next.js Route Handler로 S3 Presigned URL 만들기 (서버만으로 presign API)
Just-Do-It 2026. 2. 27. 14:59번외: Next.js Route Handler로 S3 Presigned URL 만들기 (서버만으로 presign API)
이 글은 시리즈의 번외(4단계)입니다. Spring Boot 대신 Next.js 서버에서 presigned PUT URL을 발급하는 예시를 제공합니다. 업로드(클라이언트 PUT)는 3단계 React 패턴을 그대로 사용하면 됩니다.
옵션/핵심 요소(3~6개)
| 항목 | 의미 | 언제 쓰는지(실무 상황) |
|---|---|---|
| runtime=nodejs | Edge 런타임이 아닌 Node.js 런타임 사용 | AWS SDK를 Node 환경에서 안정적으로 사용(플랫폼 제약 회피) |
| expiresIn | URL 만료(초 단위) | 짧게(예: 600초) 유지하고 재발급하도록 설계 |
| key 생성 | 서버가 업로드 경로/파일명 생성 | 클라이언트가 임의 key를 올리지 못하게 방지 |
| ContentType 고정 | PUT 업로드 타입을 서명에 포함 | 클라이언트 PUT 시 동일한 Content-Type을 요구(불일치 시 403) |
| 인증/인가 | presign 발급 권한 통제 | 로그인 사용자만 발급, 사용자별 prefix로 분리 |
패키지 설치
npm i @aws-sdk/client-s3 @aws-sdk/s3-request-presigner환경변수
AWS_REGION=ap-northeast-2
S3_BUCKET=your-bucket-name
AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...Route Handler 예시
파일 위치 예시: app/api/uploads/presign-put/route.ts
import { NextResponse } from 'next/server'
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
import crypto from 'crypto'
export const runtime = 'nodejs'
function sanitizeFilename(name: string) {
const safe = name.trim().toLowerCase().replace(/[^a-z0-9._-]/g, '-').slice(0, 120)
return safe || 'file'
}
export async function POST(req: Request) {
const body = await req.json().catch(() => ({} as any))
const filename = String(body.filename || '')
const contentType = String(body.contentType || '')
if (!filename || !contentType) {
return NextResponse.json({ message: 'filename and contentType are required' }, { status: 400 })
}
// TODO: 반드시 인증/인가를 걸어야 함
const region = process.env.AWS_REGION
const bucket = process.env.S3_BUCKET
if (!region || !bucket) {
return NextResponse.json({ message: 'Missing AWS_REGION or S3_BUCKET' }, { status: 500 })
}
const safeName = sanitizeFilename(filename)
const now = new Date()
const yyyy = now.getUTCFullYear()
const mm = String(now.getUTCMonth() + 1).padStart(2, '0')
const dd = String(now.getUTCDate()).padStart(2, '0')
const id = crypto.randomBytes(16).toString('hex')
const key = `uploads/${yyyy}/${mm}/${dd}/${id}-${safeName}`
const s3 = new S3Client({ region })
const command = new PutObjectCommand({ Bucket: bucket, Key: key, ContentType: contentType })
const expiresIn = 60 * 10
const url = await getSignedUrl(s3, command, { expiresIn })
return NextResponse.json({ key, url, expiresIn })
}문제 상황(정확히 1개)
상황: 배포 후 presign API가 500을 반환하고, 응답 메시지에 Missing AWS_REGION or S3_BUCKET가 찍힌다.
원인: 배포 환경(Vercel/컨테이너 등)에 AWS_REGION/S3_BUCKET 환경변수가 주입되지 않았거나 이름이 다르다.
해결: 배포 환경의 환경변수 설정에 AWS_REGION과 S3_BUCKET을 추가하고, 런타임이 Node.js인지(export const runtime = 'nodejs') 확인한다.
예방 팁: 헬스체크나 부팅 시점에 필수 env를 검증하고, 누락 시 명확한 에러를 내도록 고정합니다.
참고/출처
Comments
