Lab 03: 프레젠테이션 계층 — 화면을 S3로
이 Lab의 흐름
같은 앱을 세 계층으로 다시 세웁니다. 화면부터 떼어냅니다.
Lab 02에서 정적 배포와 동적 배포의 차이를 봤습니다. 여기서 하는 것은 정적 배포입니다.
학습 목표
여기서는 AI를 설계 도구로 씁니다. 앞에서는 이걸 해줘였다면, 여기서는 구조를 정해놓고 그 구조대로 만들게 합니다.
- 실행되는 앱과 올려두는 파일의 차이를 손으로 확인합니다
- 계층을 나누면 왜 서로의 주소를 알아야 하는지 이해합니다
- 화면만 있고 서버가 없으면 무엇이 안 되는지 직접 겪습니다
화면을 먼저 올리는 이유가 있습니다. 세 계층 중 가장 먼저 눈에 보이는 것이 화면이고, 그 화면이 혼자서는 아무것도 못 한다는 사실이 나머지 두 계층을 만들 이유가 됩니다.
Step 1: 화면이 어떻게 달라졌는지 보기
3.structured-quote-app/client 는 한 덩어리 앱에서 화면만 남은 Next.js입니다. 1-Tier와 같은 프레임워크인데 결정적으로 다른 점이 있습니다.
API 라우트가 없습니다. 데이터를 직접 갖지 않고, 서버에 /api/text 를 호출할 뿐입니다.
next.config.js 를 보면 output: "export" 가 있습니다. 이 설정 때문에 npm run build 결과가 실행 가능한 서버가 아니라 out/ 폴더 안의 정적 파일 묶음이 됩니다. 서버가 필요 없으니 S3에 그냥 올려두면 됩니다.
| 1-Tier의 화면 | 3-Tier의 화면 | |
|---|---|---|
| 결과물 | 실행되는 Next.js 서버 | out/ 안의 HTML·JS 파일 |
| 사는 곳 | EC2 | S3 |
| 배포 방식 | 동적 배포 | 정적 배포 |
| API 위치 | 자기 안 (/api/text) | 남의 서버 (http://IP:8000/api/text) |
Lab 01에서 게임 HTML 하나를 공유 사이트에 올렸던 것과 같은 방식입니다. 그때는 파일이 하나였고 지금은 폴더 하나라는 차이뿐입니다.
Step 2: 미션 ② — 빌드해서 S3에 올리기
이 Step은 둘로 나뉩니다. 서버 주소는 손으로 적고, 배포는 Kiro에게 시킵니다.
서버 주소는 손으로 적습니다
cd /home/ec2-user/kiro-on-cloud/3.structured-quote-app/client
cp .env.example .env.env 를 편집기에서 열고 서버 주소를 채웁니다. 내 EC2의 퍼블릭 IP에 8000 포트입니다.
SERVER_URL=http://내-EC2-퍼블릭-IP:8000이 한 줄이 화면과 서버를 잇는 유일한 연결입니다. 나중에 화면에 명언이 안 나오면 가장 먼저 열어볼 파일이 여기고, EC2 IP가 바뀌면 고쳐야 할 곳도 여기입니다. 어디를 봐야 하는지 알려면 한 번은 직접 적어봐야 합니다.
서버는 아직 없습니다. 주소만 먼저 정해두는 것입니다. 그 자리에 들어올 서버는 다음 Lab에서 만듭니다.
배포는 시킵니다
빌드와 업로드는 절차입니다. 정해진 순서를 정확히 반복하는 일이라 AI가 가장 잘하는 영역입니다.
`3.structured-quote-app/client`를 프로덕션 빌드해서
S3 버킷 `summer-ht-__-app`에 정적 웹사이트 호스팅으로 올려줘.
완성된 S3 웹사이트 URL을 알려줘.버킷 이름의 summer-ht-__ 자리에는 내 IAM 사용자명을 넣습니다. 다른 이름으로는 생성이 거부됩니다.
Kiro가 실행하는 명령은 대략 이렇습니다.
npm install
npm run build # out/ 폴더가 생긴다
aws s3 sync out/ s3://summer-ht-__-app/ --delete내가 정할 것과 맡길 것이 갈립니다. 주소는 내 환경에 달린 값이라 내가 정하고, 빌드와 업로드는 정해진 절차라 맡깁니다. AI를 쓴다는 건 전부 넘기는 게 아니라 이 선을 긋는 일입니다.
-
client/.env에 서버 주소를 직접 적었다 - S3 웹사이트 URL을 받았다
Step 3: 열어서 확인하기
받은 S3 URL로 접속합니다.
화면은 뜹니다. 제목도, 버튼도, 입력칸도 전부 그대로입니다. EC2에서 아무 프로세스도 띄우지 않았는데 화면이 나옵니다. 정적 배포이기 때문입니다.
이제 다른 명언 보기 를 눌러봅니다. 아무것도 오지 않습니다.
F12로 개발자도구의 Network 탭을 열고 다시 눌러봅니다.

빨간 X가 붙은 text 를 클릭하면 Request URL이 나옵니다. http://44.201.3.52:8000/api/text, 조금 전 .env 에 적은 그 주소입니다. 요청이 안 나간 게 아니라 나갔는데 실패한 것입니다.
주소는 정확합니다. 화면이 부르는 곳도 맞습니다. 다만 그 주소에 아직 아무것도 없습니다. 서버를 띄우지 않았고, 8000 포트도 열지 않았습니다. 둘 다 다음 Lab에서 합니다.
화면만으로는 아무것도 못 합니다. 명언을 꺼내오는 일도, 새로 저장하는 일도 서버가 대신 해줘야 합니다. 나눈다는 건 각자 자기 몫만 갖는다는 뜻입니다.
- S3 URL로 화면이 열렸다
- Network 탭에서 Request URL이 내 EC2의
:8000인 것과, 그 요청이 실패한 것을 확인했다
핵심: 화면은 왜 서버의 주소를 알아야 하는가
오늘 워크숍에서 가장 중요한 부분입니다.
사용자는 S3 화면을 엽니다. 그 화면이 다시 서버를 부릅니다. 두 번째 화살표에 이름이 붙어 있는 이유가 있습니다. 부를 곳을 누가 알려주지 않으면 화면은 움직이지 못합니다.
1-Tier에서는 주소가 필요 없었습니다. 화면과 API가 같은 앱이라 /api/text 라는 상대경로면 끝이었습니다. 브라우저는 지금 보고 있는 사이트에 그대로 요청하면 됐습니다.
S3로 떼어내는 순간 브라우저는 서버가 어디 있는지 전혀 모릅니다. S3에는 화면 파일만 있고 API가 없습니다. 그래서 빌드할 때 주소를 파일 안에 박아 넣어야 합니다.
동작 방식은 이렇습니다. app/page.js 가 process.env.SERVER_URL 을 읽고, Next.js가 .env 에 적힌 값을 빌드 시점에 문자열로 치환합니다. 빌드가 끝난 out/_next/static/ 안의 JS 파일에는 IP가 그대로 들어가 있습니다. 방금 손으로 적은 한 줄이 여기까지 옵니다.
정리하면 계층을 나눈다는 건 서로의 주소를 알아야 한다는 뜻입니다. .env 에 IP를 적은 것도, 다음 Lab에서 8000 포트를 열어야 하는 것도 전부 나눴기 때문에 생긴 일입니다.
주의: EC2를 중지했다 켜면 퍼블릭 IP가 바뀝니다. 그러면 S3 화면이 옛 IP를 부릅니다. 파일 교체로는 해결되지 않고 재빌드 후 재업로드해야 합니다. IP가
out/_next/static/안의 JS에 박혀 있기 때문입니다.
다음 단계
화면은 자기 자리를 찾았습니다. 부를 상대가 없을 뿐입니다.
Lab 04에서 그 상대를 만듭니다. 데이터가 살아남을 곳(MySQL)을 먼저 세우고, 그 위에 서버(FastAPI)를 올린 다음, 8000 포트를 엽니다. S3는 다시 건드리지 않습니다. 주소는 이미 박혀 있으니, 그 주소에서 서버가 응답하기 시작하면 화면이 저절로 살아납니다.
NxtCloud Workshop