diff --git a/apps/web/content/study/clab-26-1/in-person/week11.mdx b/apps/web/content/study/clab-26-1/in-person/week11.mdx
index 026fade..dd6a63a 100644
--- a/apps/web/content/study/clab-26-1/in-person/week11.mdx
+++ b/apps/web/content/study/clab-26-1/in-person/week11.mdx
@@ -136,8 +136,8 @@ branch 작업 → PR 생성 → preview 배포 → 리뷰 → main 머지 → pr
이번 학기에는 HTML/CSS/JS/React의 기본 사용법을 비대면에서 배우고, 대면에서는
- 브라우저 렌더링, AI 시대 개발 방식, 프로젝트 구조, API 계약, 웹 보안, 팀 협업,
- 배포와 운영까지 큰 그림을 봤습니다.
+ 브라우저 렌더링, AI 시대 개발 방식, 프로젝트 구조, API 계약, Next.js와 웹
+ 렌더링, 팀 협업, 배포와 운영까지 큰 그림을 봤습니다.
diff --git a/apps/web/content/study/clab-26-1/in-person/week6-1.mdx b/apps/web/content/study/clab-26-1/in-person/week6-1.mdx
index 898870e..f099739 100644
--- a/apps/web/content/study/clab-26-1/in-person/week6-1.mdx
+++ b/apps/web/content/study/clab-26-1/in-person/week6-1.mdx
@@ -52,7 +52,7 @@ description: 4~6주차 공백 이후 진행하는 첫 번째 대면입니다. HT
- 7주차: 프론트엔드 프로젝트가 복잡해지는 이유 — 구조와 책임
분리
- 8주차: API와 통신 — 프론트엔드와 백엔드의 계약
-- 9주차: 웹 보안 — 브라우저에 있는 코드는 믿으면 안 된다
+- 9주차: Next.js 렌더링 진화와 웹 퍼포먼스
- 10주차: 팀 협업 — Git, PR, 컨벤션, 리뷰 문화
- 11주차: 배포와 운영 — localhost 밖의 세계
diff --git a/apps/web/content/study/clab-26-1/in-person/week8.mdx b/apps/web/content/study/clab-26-1/in-person/week8.mdx
index b023c63..ba04f47 100644
--- a/apps/web/content/study/clab-26-1/in-person/week8.mdx
+++ b/apps/web/content/study/clab-26-1/in-person/week8.mdx
@@ -8,9 +8,10 @@ tags:
- HTTP
- REST
- CORS
+ - 프록시
- 백엔드 협업
- 데이터 계약
-description: 비동기와 async/await를 배운 뒤, 대면에서는 API를 단순 호출 방법이 아니라 프론트엔드와 백엔드 사이의 계약으로 바라봅니다. HTTP, REST, CORS, 에러 응답, API 문서와 협업 방식을 다룹니다.
+description: 비동기와 async/await를 배운 뒤, 대면에서는 API를 단순 호출 방법이 아니라 프론트엔드와 백엔드 사이의 계약으로 바라봅니다. HTTP, REST, CORS, 프록시, API 클라이언트 구조, TanStack Query와 파일 배치까지 연결합니다.
---
1. 이번 주 사전학습과 오늘 대면의 연결
@@ -18,8 +19,14 @@ description: 비동기와 async/await를 배운 뒤, 대면에서는 API를 단
이번 주 비대면에서는 클로저, Promise, async/await를 배웠습니다. 이 문법들이
실제 웹에서 가장 많이 쓰이는 곳은 서버와 통신할 때입니다.
- 오늘은 `fetch` 문법을 다시 외우는 시간이 아니라, API를 팀 사이의 계약으로 보는
- 감각을 잡아보겠습니다.
+ 버튼을 눌렀을 때 서버에 요청을 보내고, 응답을 기다리고, 성공과 실패에 따라
+ 화면을 바꾸는 모든 과정이 비동기 위에 있습니다.
+
+
+
+ 오늘은 `fetch` 문법을 다시 외우는 시간이 아닙니다. API를{" "}
+ 프론트엔드와 백엔드 사이의 계약으로 바라보고, 그 계약을
+ 프로젝트 안에서 어디에 배치해야 유지보수하기 쉬운지까지 연결해보겠습니다.
---
@@ -43,6 +50,12 @@ GET /api/studies?page=1&size=20
올지, 빈 목록이면 어떤 응답이 올지 정해야 합니다.
+
+ 그래서 API를 잘 다룬다는 것은 단순히 요청을 보낼 수 있다는 뜻이 아닙니다.
+ 화면 요구사항을 요청과 응답의 언어로 바꾸고, 백엔드와 합의한 내용을 코드에
+ 안정적으로 반영할 수 있다는 뜻입니다.
+
+
---
3. 좋은 API 협업에서 꼭 맞춰야 하는 것들
@@ -121,41 +134,571 @@ GET /api/studies?page=1&size=20
---
-4. CORS — 브라우저가 끼어드는 보안 규칙
+4. CORS — 서버 에러처럼 보이는 브라우저 규칙
+
+
+ CORS는 처음 보면 백엔드 에러처럼 보이지만, 사실 브라우저의 보안 정책과
+ 관련이 있습니다. 브라우저는 기본적으로 같은 출처의 리소스만 자유롭게 읽을 수
+ 있게 하는 SOP를 가지고 있고, CORS는 다른 출처의 리소스를 안전하게 공유하기
+ 위한 예외 규칙입니다.
+
+
+
+ 여기서 출처(origin)는 보통 프로토콜, 호스트, 포트의 조합으로
+ 봅니다.
+
+
+```text
+http://localhost:5173
+http://localhost:8080
+```
+
+
+ 두 주소는 둘 다 localhost지만 포트가 다릅니다. 브라우저 입장에서는 다른
+ 출처입니다. 그래서 프론트 개발 서버에서 백엔드 개발 서버로 요청을 보낼 때
+ CORS 에러를 자주 만나게 됩니다.
+
- CORS는 처음 보면 백엔드 에러처럼 보이지만, 사실 브라우저의 보안 정책입니다.
- 다른 출처의 API를 마음대로 호출하지 못하게 막는 규칙입니다.
+ CORS의 핵심은 요청을 막는 주체가 서버가 아니라 브라우저라는 점입니다. 서버는
+ 응답을 보냈더라도, 브라우저가 응답 헤더를 보고 "이 출처에 공개해도 된다"는
+ 허가를 확인하지 못하면 JavaScript 코드에 응답을 넘기지 않습니다.
- 프론트엔드 개발자가 CORS를 알아야 하는 이유는, 이 문제가 생겼을 때 "프론트에서
- 코드 조금 바꾸면 되겠지"가 아니라 서버의 응답 헤더와 배포 환경을 함께 봐야
- 하기 때문입니다.
+ 더 자세한 흐름은{" "}
+
+ CORS란 무엇인가? (그만 괴롭혀..)
+
+ 글의 Origin, SOP, Preflight 설명을 함께 보면 좋습니다.
---
-5. API 문서는 협업 도구다
+5. CORS는 실제로 어떻게 확인되는가
+
+5-1. 기본 요청 흐름
- Swagger, Postman, Notion 문서 같은 API 문서는 단순 참고 자료가 아닙니다.
- 프론트와 백엔드가 같은 그림을 보고 있는지 확인하는 협업 도구입니다.
+ 브라우저는 다른 출처로 요청을 보낼 때 요청 헤더에 Origin을 담습니다. 서버는
+ 응답 헤더에 어떤 출처를 허용할지 적어 보냅니다. 브라우저는 이 둘을 비교합니다.
-좋은 API 문서에는 적어도 다음이 있어야 합니다.
+```http
+Origin: http://localhost:5173
+Access-Control-Allow-Origin: http://localhost:5173
+```
-- 요청 URL과 메서드
-- 요청 파라미터와 body
-- 성공 응답 예시
-- 에러 응답 예시
-- 인증 필요 여부
-- 빈 데이터 예시
-- 필드 설명과 가능한 enum 값
+
+ 값이 맞으면 브라우저는 응답을 JavaScript에 넘깁니다. 맞지 않으면 네트워크
+ 요청 자체가 성공했더라도 프론트엔드 코드에서는 응답을 사용할 수 없습니다.
+
+
+5-2. Preflight 요청
+
+
+ 실제 요청 전에 브라우저가 OPTIONS 요청을 먼저 보내는 경우가 있습니다. 이것을
+ Preflight라고 부릅니다. "이 메서드와 이 헤더로 요청해도 되는지"를 미리 물어보는
+ 절차입니다.
+
+
+```http
+OPTIONS /api/studies
+Origin: http://localhost:5173
+Access-Control-Request-Method: POST
+Access-Control-Request-Headers: content-type, authorization
+```
+
+
+ 서버가 허용하는 origin, method, headers를 응답하면 브라우저가 그제야 실제
+ 요청을 보냅니다. 우리가 흔히 쓰는 `Content-Type: application/json`이나
+ `Authorization` 헤더는 Preflight를 유발하는 경우가 많습니다.
+
+
+5-3. 인증 정보가 포함된 요청
+
+
+ 쿠키나 인증 헤더처럼 자격 증명이 포함되는 요청은 더 엄격합니다. 프론트에서는
+ `credentials: "include"` 또는 axios의 `withCredentials: true` 같은 설정이
+ 필요하고, 서버도 `Access-Control-Allow-Credentials: true`를 내려줘야 합니다.
+ 이때 `Access-Control-Allow-Origin: *`처럼 모든 출처를 허용하는 방식은 사용할
+ 수 없습니다.
+
---
-6. 프론트엔드가 백엔드에게 잘 질문하는 법
+6. CORS 해결 방법 — 서버 설정과 프록시
+
+6-1. 정석은 서버가 허용할 출처를 명시하는 것
+
+
+ 가장 정석적인 해결은 백엔드가 실제로 허용할 프론트엔드 출처를 응답 헤더에
+ 명시하는 것입니다.
+
+
+```http
+Access-Control-Allow-Origin: https://www.example.com
+Access-Control-Allow-Methods: GET, POST, PATCH, DELETE
+Access-Control-Allow-Headers: Content-Type, Authorization
+Access-Control-Allow-Credentials: true
+```
+
+
+ 운영 환경에서는 무작정 모든 origin을 열기보다, 실제 서비스 도메인과 개발
+ 도메인을 구분해서 관리해야 합니다. CORS는 귀찮은 에러가 아니라 "누가 이 응답을
+ 읽을 수 있는가"를 정하는 보안 경계입니다.
+
+
+6-2. 프론트 개발 서버 프록시
+
+
+ 개발 환경에서는 프론트 개발 서버가 백엔드로 요청을 대신 전달하는 프록시를
+ 자주 씁니다. 브라우저는 같은 출처인 프론트 개발 서버에 요청한다고 느끼고,
+ 프론트 개발 서버가 뒤에서 백엔드 서버로 요청을 넘깁니다.
+
+
+```ts
+// vite.config.ts
+export default defineConfig({
+ server: {
+ proxy: {
+ "/api": {
+ target: "http://localhost:8080",
+ changeOrigin: true,
+ },
+ },
+ },
+});
+```
+
+```ts
+// 브라우저에서는 같은 출처로 요청하는 것처럼 보인다.
+await fetch("/api/studies");
+```
+
+
+ 원리는 단순합니다. CORS는 브라우저가 다른 출처의 응답을 JavaScript에 넘길지
+ 판단하는 규칙입니다. 그런데 브라우저가 보는 요청 주소가 `/api/studies`처럼
+ 현재 프론트 서버와 같은 출처라면, 브라우저 입장에서는 교차 출처 요청이
+ 아닙니다. 실제 백엔드 호출은 브라우저 밖에 있는 개발 서버가 대신 수행합니다.
+
+
+프록시 방식의 장점은 다음과 같습니다.
+
+- 개발 환경에서 CORS 설정 때문에 막히지 않고 빠르게 화면 개발을 진행할 수 있습니다.
+- 프론트 코드의 API base URL을 `/api`처럼 단순하게 유지할 수 있습니다.
+- 로컬 개발 환경과 운영 환경의 URL 차이를 설정으로 흡수할 수 있습니다.
+
+단점도 분명합니다.
+
+- 개발 서버 프록시는 보통 로컬 개발용입니다. 운영 CORS 정책을 대신 설계해주지는 않습니다.
+- 프록시 설정이 운영 배포 구조와 다르면 로컬에서는 되는데 운영에서는 깨질 수 있습니다.
+- 인증 쿠키, 도메인, SameSite, HTTPS 조건이 얽히면 프록시만으로 문제를 해결할 수 없습니다.
+
+
+ 따라서 프록시는 "CORS를 없애는 마법"이 아니라{" "}
+ 브라우저가 보는 요청 경로를 같은 출처처럼 만들어주는 개발/배포 구조
+ 입니다. 운영에서는 Nginx, Next.js Route Handler, BFF, API Gateway 같은 계층이
+ 비슷한 역할을 맡을 수 있습니다.
+
+
+---
+
+7. HTTP에서 현대 API 연동까지
+
+
+ API 요청 도구를 고를 때 바로 axios, fetch, TanStack Query부터 비교하면 흐름이
+ 잘 보이지 않습니다. 먼저 웹 통신이 어떤 문제를 해결하며 발전해왔는지 봐야
+ 합니다.
+
+
+7-1. HTTP — 요청과 응답의 기본 약속
+
+
+ HTTP는 클라이언트가 요청하면 서버가 응답하는 규약입니다. 중요한 특징은
+ 무상태성입니다. 서버는 기본적으로 이전 요청을 기억하지
+ 않습니다. 그래서 로그인 이후의 요청에는 "내가 누구인지"를 알려주는 토큰이나
+ 쿠키 같은 인증 정보가 필요합니다.
+
+
+```http
+GET /api/users/123 HTTP/1.1
+Host: example.com
+Authorization: Bearer access-token
+```
+
+
+ API 협업에서 메서드도 약속입니다. 보통 GET은 조회, POST는 생성, PUT/PATCH는
+ 수정, DELETE는 삭제에 사용합니다. 이 의미가 맞아야 프론트엔드와 백엔드가 같은
+ 그림을 보고 개발할 수 있습니다.
+
+
+7-2. AJAX — 페이지 전체가 아니라 데이터만 바꾸기
+
+
+ 예전 웹은 버튼을 누르거나 링크를 클릭하면 페이지 전체를 다시 받아왔습니다.
+ AJAX가 등장하면서 페이지를 새로고침하지 않고 필요한 데이터만 받아와 화면의
+ 일부만 바꾸는 방식이 가능해졌습니다.
+
+
+```text
+AJAX 이전: 요청 → 새 HTML 전체 다운로드 → 페이지 전체 교체
+AJAX 이후: 요청 → JSON 데이터 다운로드 → 필요한 UI만 갱신
+```
+
+
+ 이 변화 덕분에 Gmail, Google Maps처럼 페이지 전환 없이 부드럽게 동작하는 웹
+ 서비스가 가능해졌습니다. 대신 비동기 요청이 많아지면서 콜백 지옥, 에러 처리,
+ 공통 요청 설정 같은 새로운 문제가 생겼습니다.
+
+
+7-3. 콜백에서 Promise, async/await으로
+
+
+ 비동기 요청은 응답이 언제 올지 알 수 없습니다. 처음에는 콜백 함수로 응답을
+ 처리했지만, 요청이 이어질수록 코드가 깊게 중첩됐습니다.
+
+
+```js
+getUser(userId, function (user) {
+ getOrders(user.id, function (orders) {
+ getOrderDetail(orders[0].id, function (orderDetail) {
+ // 계속 중첩됨
+ });
+ });
+});
+```
+
+
+ Promise와 async/await은 이 문제를 해결했습니다. 서버 통신 코드를 동기 코드처럼
+ 읽을 수 있게 만들었고, 에러 처리도 `try/catch`로 모을 수 있게 됐습니다.
+
+
+```ts
+async function loadUserOrderInfo(userId: number) {
+ try {
+ const user = await getUser(userId);
+ const orders = await getOrders(user.id);
+ const orderDetail = await getOrderDetail(orders[0].id);
+ return orderDetail;
+ } catch (error) {
+ console.error("요청 실패", error);
+ }
+}
+```
+
+7-4. Fetch API — 브라우저 내장 HTTP 클라이언트
+
+
+ fetch는 Promise 기반의 브라우저 내장 HTTP 클라이언트입니다. 별도 라이브러리 없이
+ 사용할 수 있고, XMLHttpRequest보다 문법이 훨씬 직관적입니다.
+
+
+```ts
+const response = await fetch("/api/users");
+const data = await response.json();
+```
+
+하지만 fetch에도 실무에서 불편한 지점이 있습니다.
+
+- 404, 500 같은 HTTP 에러가 자동으로 `catch`로 가지 않습니다.
+- JSON 변환을 매번 `response.json()`으로 직접 해야 합니다.
+- timeout, 공통 헤더, 인터셉터 같은 기능을 직접 만들어야 합니다.
+- 요청 취소는 `AbortController`를 알아야 합니다.
+
+```ts
+const response = await fetch("/api/users");
+
+if (!response.ok) {
+ throw new Error("HTTP error");
+}
+
+const data = await response.json();
+```
+
+7-5. Axios — 실무 편의 기능을 모은 HTTP 클라이언트
+
+
+ axios는 fetch의 부족한 부분을 보완하기 위해 많이 사용됩니다. 자동 JSON 변환,
+ timeout, 에러 처리, 인스턴스, 인터셉터 같은 기능을 제공합니다. 브라우저와
+ Node.js에서 비슷한 방식으로 쓸 수 있다는 점도 장점입니다.
+
+
+```ts
+const response = await axios.get("/api/users");
+const data = response.data;
+```
+
+
+ fetch와 달리 axios는 2xx가 아닌 HTTP 상태 코드를 에러로 처리합니다. 그래서
+ `try/catch` 안에서 성공과 실패를 나누기 쉽습니다.
+
+
+```ts
+try {
+ const response = await axios.get("/api/users");
+ console.log(response.data);
+} catch (error) {
+ if (axios.isAxiosError(error) && error.response) {
+ console.log(error.response.status);
+ console.log(error.response.data);
+ }
+}
+```
+
+7-6. 인터셉터 — 요청과 응답을 가로채는 공통 처리
+
+
+ 인터셉터는 모든 요청이나 모든 응답 앞뒤에 공통 로직을 끼워 넣는 기능입니다.
+ 실무에서는 토큰 자동 추가, 401 에러 처리, 공통 에러 변환에 많이 사용합니다.
+
+
+```ts
+const apiClient = axios.create({
+ baseURL: "/api",
+ timeout: 10000,
+ withCredentials: true,
+});
+
+apiClient.interceptors.request.use((config) => {
+ // 학습을 위한 단순 예시입니다.
+ // localStorage에 토큰을 저장하면 XSS 공격에 노출될 수 있습니다.
+ const token = localStorage.getItem("accessToken");
+
+ if (token) {
+ config.headers.Authorization = `Bearer ${token}`;
+ }
+
+ return config;
+});
+```
+
+
+ 인터셉터를 쓰면 모든 API 함수에서 토큰을 반복해서 꺼내 붙이지 않아도 됩니다.
+ 공통 로직은 한 곳에 모이고, 개별 API 함수는 "무슨 요청을 보낼지"에 집중할 수
+ 있습니다.
+
+
+
+ 다만 위 코드는 인터셉터 개념을 설명하기 위한 단순 예시입니다. 실무에서는
+ access token을 localStorage에 오래 보관하는 방식을 기본값으로 두면 안 됩니다.
+ localStorage는 JavaScript로 읽을 수 있기 때문에 XSS가 발생했을 때 토큰이 탈취될
+ 수 있습니다. 보안이 중요한 서비스라면 서버가 설정하는 HttpOnly,
+ Secure, SameSite 쿠키 기반 인증이나 그에 준하는
+ 안전한 저장 전략을 함께 검토해야 합니다.
+
+
+7-7. 토큰 인증 — HTTP의 무상태성을 보완하기
+
+
+ HTTP는 이전 요청을 기억하지 않기 때문에, 로그인 이후 요청마다 사용자를 증명할
+ 정보가 필요합니다. 이때 access token과 refresh token을 함께 쓰는 구조가 자주
+ 등장합니다.
+
+
+```text
+로그인 성공
+ → Access Token 발급: 짧은 수명, API 요청에 사용
+ → Refresh Token 발급: 긴 수명, Access Token 재발급에 사용
+```
+
+
+ 백엔드와 맞춰야 할 약속은 구체적이어야 합니다. 토큰을 어느 헤더에 넣는지,
+ `Bearer`를 붙이는지, 만료되면 어떤 상태 코드가 오는지, refresh API는 어떤 응답을
+ 주는지 확인해야 합니다.
+
+
+```http
+Authorization: Bearer {accessToken}
+```
+
+
+ 401 응답이 왔을 때 인터셉터에서 토큰을 갱신하고 원래 요청을 다시 보내는 패턴도
+ 자주 사용됩니다. 다만 동시에 여러 요청이 401을 받으면 refresh 요청이 중복될 수
+ 있으므로, 실무에서는 중복 갱신을 막는 설계도 필요합니다.
+
+
+
+ Refresh Token은 특히 더 조심해야 합니다. 브라우저 JavaScript가 직접 읽는 저장소에
+ 두기보다, 서버가 내려주는 HttpOnly + Secure 쿠키로 보관하는 방식을 많이
+ 사용합니다. 이 경우 프론트엔드는 토큰 문자열을 직접 다루지 않고, 브라우저가
+ 쿠키를 요청에 함께 보내도록 둡니다. 대신 쿠키 기반 인증에서는 SameSite 설정,
+ CSRF 대응, CORS credentials 설정을 백엔드와 함께 맞춰야 합니다.
+
+
+7-8. TanStack Query — HTTP 통신이 아니라 서버 상태 관리
+
+
+ 어느 순간부터 프론트엔드 개발자들은 HTTP 요청 자체보다 서버 데이터 상태 관리에
+ 더 많은 시간을 쓰고 있다는 걸 알게 됐습니다. 로딩, 에러, 캐싱, 중복 요청 제거,
+ 백그라운드 갱신, 낙관적 업데이트 같은 문제입니다.
+
+
+```tsx
+const [users, setUsers] = useState([]);
+const [loading, setLoading] = useState(false);
+const [error, setError] = useState(null);
+
+useEffect(() => {
+ setLoading(true);
+ axios
+ .get("/api/users")
+ .then((response) => setUsers(response.data))
+ .catch((error) => setError(error))
+ .finally(() => setLoading(false));
+}, []);
+```
+
+
+ TanStack Query는 이런 반복을 줄여주는 서버 상태 관리 도구입니다. axios가 서버에
+ 요청을 보내고 응답을 받아오는 역할이라면, TanStack Query는 받아온 데이터를
+ 캐시에 저장하고, 언제 새로 가져올지, 어떤 컴포넌트에 전달할지를 관리합니다.
+
+
+```tsx
+const { data, isLoading, error } = useQuery({
+ queryKey: ["users"],
+ queryFn: () => apiClient.get("/users").then((res) => res.data),
+ staleTime: 5 * 60 * 1000,
+});
+```
+
+
+ 핵심은 역할 구분입니다. axios는 택배 기사처럼 데이터를 가져오고,
+ TanStack Query는 창고 관리자처럼 그 데이터를 보관하고 갱신합니다.
+
+
+7-9. Query와 Mutation
+
+
+ TanStack Query에서는 데이터를 읽는 작업과 쓰는 작업을 구분합니다. Query는 서버
+ 데이터를 읽는 작업이고, Mutation은 생성, 수정, 삭제처럼 서버 데이터를 바꾸는
+ 작업입니다.
+
+
+| 구분 | Query | Mutation |
+| --- | --- | --- |
+| 용도 | 데이터 읽기 | 데이터 생성/수정/삭제 |
+| 주로 쓰는 HTTP | GET | POST, PUT, PATCH, DELETE |
+| 실행 시점 | 컴포넌트 마운트 시 자동 | `mutate` 호출 시 수동 |
+| 캐싱 | 자동 캐싱 | 직접 무효화/갱신 필요 |
+
+```tsx
+const queryClient = useQueryClient();
+
+const createUser = useMutation({
+ mutationFn: (newUser) => apiClient.post("/users", newUser),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ["users"] });
+ },
+});
+```
+
+
+ 새 사용자를 만든 뒤 사용자 목록 query를 무효화하면, TanStack Query가 목록을 다시
+ 가져옵니다. 직접 `setUsers`로 상태를 여기저기 수정하지 않아도 서버 데이터와
+ 화면을 다시 맞출 수 있습니다.
+
+
+---
+
+8. 7주차 파일 구조와 API 파일 배치 연결하기
+
+
+ 지난 대면에서 UI, 상태, 서버 데이터, 비즈니스 규칙을 나눠서 봤습니다. API
+ 관련 파일도 같은 기준으로 배치하면 됩니다. 핵심은 "서버와 통신하는 코드"와
+ "화면을 그리는 코드"를 섞지 않는 것입니다.
+
+
+작은 프로젝트에서는 아래처럼 시작할 수 있습니다.
+
+```text
+src/
+ api/
+ client.ts
+ studies.ts
+ pages/
+ StudyListPage.tsx
+ components/
+ StudyCard.tsx
+```
+
+
+ `client.ts`에는 base URL, credentials, 공통 헤더, 에러 변환 같은 HTTP 공통
+ 정책을 둡니다. `studies.ts`에는 스터디 도메인의 API 함수들을 둡니다. 화면은
+ `getStudies()` 같은 함수를 호출할 뿐, URL 문자열과 HTTP 세부 설정을 직접 알지
+ 않는 편이 좋습니다.
+
+
+```ts
+// api/studies.ts
+export async function getStudies(params: GetStudiesParams) {
+ const response = await api.get("studies", { searchParams: params }).json();
+ return parseStudiesResponse(response);
+}
+```
+
+프로젝트가 커지면 기능 단위로 가까이 두는 방식도 좋습니다.
+
+```text
+src/
+ features/
+ studies/
+ api/
+ studyApi.ts
+ studyQueries.ts
+ model/
+ studyTypes.ts
+ studyPolicy.ts
+ ui/
+ StudyCard.tsx
+ StudyList.tsx
+ pages/
+ StudyListPage.tsx
+```
+
+
+ 이 구조에서는 스터디 기능과 관련된 API, query key, 타입, 정책, UI가 한 기능
+ 안에 모입니다. 대신 공통 HTTP 클라이언트는 여전히 바깥에 둡니다.
+
+
+```text
+src/
+ shared/
+ api/
+ httpClient.ts
+ apiError.ts
+ features/
+ studies/
+ api/
+ studyApi.ts
+ studyQueries.ts
+```
+
+
+ `studyApi.ts`는 실제 HTTP 요청 함수, `studyQueries.ts`는 TanStack Query의 query
+ key와 query option을 담는 식으로 나누면 역할이 선명해집니다.
+
+
+```ts
+// features/studies/api/studyQueries.ts
+export const studyQueries = {
+ all: ["studies"] as const,
+ list: (params: GetStudiesParams) => ({
+ queryKey: [...studyQueries.all, "list", params] as const,
+ queryFn: () => studyApi.getStudies(params),
+ }),
+};
+```
+
+
+ 이렇게 해두면 화면 컴포넌트는 "스터디 목록을 가져온다"는 의도만 드러내고,
+ HTTP 세부사항과 query key 규칙은 API 계층에 남습니다.
+
+
+---
+
+9. 프론트엔드가 백엔드에게 잘 질문하는 법
"API 언제 돼요?"보다 좋은 질문은 구체적입니다.
@@ -170,12 +713,36 @@ GET /api/studies?page=1&size=20
개발자는 화면 요구사항을 API 계약 언어로 바꿔 말할 수 있어야 합니다.
+실제로는 이런 질문들도 자주 필요합니다.
+
+- 이 API는 로그인하지 않은 사용자도 호출할 수 있나요?
+- 실패할 때 code 값은 어떤 목록 중 하나인가요?
+- 빈 목록은 200과 빈 배열인가요, 아니면 404인가요?
+- 페이지 번호는 0부터 시작하나요, 1부터 시작하나요?
+- CORS 허용 origin에 로컬 개발 주소와 배포 주소가 모두 들어가 있나요?
+
+---
+
+10. 오늘 정리
+
+
+ API는 데이터를 가져오는 기술이면서 동시에 팀 사이의 약속입니다. CORS는 그
+ 약속이 브라우저 보안 규칙과 만나는 지점이고, HTTP 클라이언트와 TanStack Query는
+ API 코드를 프로젝트 구조 안에 안정적으로 배치하기 위한 도구입니다.
+
+
+
+ 7주차에서 배운 책임 분리를 떠올리면 오늘 내용도 같은 흐름입니다. UI는 UI답게,
+ API 함수는 API답게, 서버 상태 관리는 서버 상태 관리답게 두어야 프로젝트가
+ 커져도 변경 범위를 좁힐 수 있습니다.
+
+
---
-7. 다음 주 안내
+11. 다음 주 안내
- 다음 대면에서는 웹 보안을 다룹니다. 특히 브라우저에 있는 코드는 사용자가 볼 수
- 있고 조작할 수 있다는 전제에서, 프론트엔드가 어디까지 믿어도 되는지
- 이야기합니다.
+ 다음 대면에서는 Next.js와 웹 렌더링을 다룹니다. React로 화면을
+ 만드는 것에서 한 걸음 더 나아가, 브라우저 렌더링, 서버 렌더링, 정적 생성,
+ 하이드레이션이 왜 등장했는지 큰 흐름을 봅니다.
diff --git a/apps/web/content/study/clab-26-1/week9.mdx b/apps/web/content/study/clab-26-1/week9.mdx
index cf1f0d2..9c0e3a7 100644
--- a/apps/web/content/study/clab-26-1/week9.mdx
+++ b/apps/web/content/study/clab-26-1/week9.mdx
@@ -34,11 +34,11 @@ description: 다음 주부터 React를 시작합니다. React 코드에서 매
- 이번 주 대면에서는 웹 보안을 다룹니다. 비대면에서 배우는 옵셔널 체이닝,
- try/catch, 모듈, API 응답 처리 문법은 단순히 React를 편하게 읽기 위한
- 도구이기도 하지만, "외부에서 들어온 값을 어디까지 믿을 수 있는가"를 판단하는
- 기초이기도 합니다. 특히 API 응답은 항상 예상한 형태로 온다고 믿지 말고,
- 안전하게 접근하고 실패를 처리하는 습관을 같이 가져가면 좋습니다.
+ 이번 주 대면에서는 Next.js 렌더링 진화와 웹 퍼포먼스를 다룹니다. 비대면에서
+ 배우는 map/filter, 구조 분해, 스프레드, 모듈 문법은 React 컴포넌트를 읽기 위한
+ 기초이면서, Next.js에서 서버 컴포넌트와 클라이언트 컴포넌트를 나누고 데이터를
+ 화면에 연결할 때도 계속 등장합니다. 문법을 외우기보다, 이 문법들이 데이터를
+ UI로 바꾸는 과정에서 어떤 역할을 하는지 같이 보면 좋습니다.
---