From 1e801687a5d765b284164622acfb4b908635b578 Mon Sep 17 00:00:00 2001
From: Minhyeok Kim
Date: Tue, 26 May 2026 19:14:51 +0900
Subject: [PATCH] =?UTF-8?q?docs(4=EC=A3=BC=EC=B0=A8):=20=EC=84=9C=EB=B2=84?=
=?UTF-8?q?=20=EC=82=AC=EC=9D=B4=EB=93=9C=20=EB=A0=8C=EB=8D=94=EB=A7=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
"week-04/\353\257\274\355\230\201.md" | 416 ++++++++++++++++++++++++++
1 file changed, 416 insertions(+)
create mode 100644 "week-04/\353\257\274\355\230\201.md"
diff --git "a/week-04/\353\257\274\355\230\201.md" "b/week-04/\353\257\274\355\230\201.md"
new file mode 100644
index 0000000..96a4efe
--- /dev/null
+++ "b/week-04/\353\257\274\355\230\201.md"
@@ -0,0 +1,416 @@
+# SPA와 서버 사이드 렌더링
+
+## 싱글 페이지 애플리케이션(SPA)
+
+싱글 페이지 애플리케이션(Single Page Application; SPA)이란 렌더링과 라우팅에 필요한 대부분의 기능을 서버가 아닌 **브라우저의 JavaScript에 의존하는 방식**이다.
+
+최초에 필요한 HTML, JavaScript, CSS 리소스를 내려받은 이후에는 서버에서 새로운 HTML을 매번 받아오지 않고, 하나의 페이지 안에서 화면 전환과 사용자 interaction을 처리한다.
+
+즉, 페이지 이동이 발생해도 실제로는 `` 전체를 새로 내려받는 것이 아니라, 브라우저에서 JavaScript가 필요한 화면만 바꿔주는 방식에 가깝다.
+
+예를 들어 Gmail 같은 서비스는 최초 진입 시 필요한 리소스를 불러오고, 이후 메일 목록 이동이나 메일 상세 확인 같은 동작을 브라우저 안에서 빠르게 처리한다. 이때 필요한 데이터나 일부 리소스는 Lazy Loading 방식으로 나중에 불러올 수 있다.
+
+## SPA의 장점
+
+SPA는 최초 로딩 이후에는 서버를 거쳐 새로운 HTML을 받아올 일이 적다.
+
+그래서 한 번 로딩된 이후에는 페이지 전환이 빠르고, 사용자에게 앱과 비슷한 UI/UX를 제공할 수 있다.
+
+## SPA의 단점
+
+SPA는 최초에 로딩해야 할 JavaScript 리소스가 커질 수 있다.
+
+또한 렌더링을 브라우저의 JavaScript가 담당하기 때문에, 사용자 기기의 성능에 영향을 받을 수 있다.
+
+---
+
+# 서버 사이드 렌더링
+
+서버 사이드 렌더링(Server Side Rendering; SSR)이란 최초에 사용자에게 보여줄 페이지를 **서버에서 렌더링한 뒤**, 완성된 HTML을 브라우저에 전달하는 방식이다.
+
+SPA는 사용자에게 제공되는 JavaScript 번들에서 렌더링을 담당한다.
+
+반면 서버 사이드 렌더링은 렌더링에 필요한 작업을 서버에서 수행한 뒤 HTML을 내려준다.
+
+따라서 사용자 기기의 성능에 비교적 덜 의존하고, 안정적으로 초기 화면을 제공할 수 있다.
+
+---
+
+# 서버 사이드 렌더링의 장점
+
+## 1. 최초 페이지 진입이 비교적 빠르다
+
+서버 사이드 렌더링은 서버에서 렌더링된 HTML을 브라우저에 전달한다.
+
+브라우저가 JavaScript를 다운로드하고 실행한 뒤 화면을 만드는 SPA 방식보다, 초기 화면을 더 빠르게 보여줄 수 있는 경우가 있다.
+
+특히 화면 렌더링이 API 요청에 의존적이거나, 렌더링해야 할 HTML의 크기가 크다면 서버에서 미리 HTML을 만들어 내려주는 방식이 더 유리할 수 있다.
+
+## 2. 검색 엔진과 SNS 공유 등 메타데이터 제공이 쉽다
+
+검색 엔진 로봇이나 SNS 공유 봇은 페이지의 HTML을 읽어 검색 정보나 공유 정보를 가져간다.
+
+이때 HTML 내부의 오픈 그래프(Open Graph), 메타(Meta) 태그 정보를 기반으로 제목, 설명, 썸네일 등의 정보를 확인한다.
+
+SPA는 대부분의 화면 생성이 JavaScript에 의존한다.
+
+반면 서버 사이드 렌더링은 최초 렌더링 작업이 서버에서 일어나기 때문에, 검색 엔진이나 SNS에 제공할 정보를 HTML 응답에 미리 포함할 수 있다.
+
+따라서 검색 엔진 최적화(SEO)나 SNS 공유 정보 제공에 대응하기 쉽다.
+
+## 3. 누적 레이아웃 이동이 적다
+
+누적 레이아웃 이동(Cumulative Layout Shift)이란 사용자에게 페이지를 보여준 이후에 뒤늦게 HTML 정보가 추가되거나 삭제되어 화면이 덜컥거리는 것처럼 보이는 현상이다.
+
+SPA에서는 콘텐츠가 API 요청 결과에 따라 나중에 채워지는 경우가 많다.
+
+이때 API 응답 속도가 제각각이면 화면 요소의 위치가 뒤늦게 바뀌면서 누적 레이아웃 이동 문제가 발생할 수 있다.
+
+반면 서버 사이드 렌더링은 필요한 요청이 완료된 이후 완성된 HTML을 제공할 수 있기 때문에, 상대적으로 이러한 문제를 줄일 수 있다.
+
+## 4. 보안에 좀 더 안전하다
+
+서버 사이드 렌더링은 인증이나 민감한 데이터 처리를 서버에서 수행하고, 브라우저에는 결과만 전달할 수 있다.
+
+따라서 클라이언트에 노출되면 안 되는 로직이나 데이터를 서버에 둘 수 있다는 장점이 있다.
+
+---
+
+# 서버 사이드 렌더링의 단점
+
+## 1. 항상 서버 환경을 고려해야 한다
+
+서버 사이드 렌더링에서는 코드가 브라우저가 아니라 서버에서도 실행될 수 있다.
+
+따라서 `window`, `document`, `sessionStorage`, `localStorage`처럼 브라우저에만 존재하는 전역 객체를 사용할 때 주의해야 한다.
+
+이런 코드는 클라이언트에서만 실행되도록 분리해야 한다.
+
+다만 클라이언트에서만 실행되는 코드가 많아질수록 서버 사이드 렌더링의 장점을 일부 잃을 수 있다.
+
+## 2. 적절한 서버가 구축되어 있어야 한다
+
+SPA는 정적 HTML, JavaScript, CSS 리소스를 제공할 수 있는 환경만 있어도 동작할 수 있다.
+
+반면 서버 사이드 렌더링은 사용자의 요청을 받아 페이지를 렌더링할 서버가 필요하다.
+
+즉, 서버 운영, 배포, 캐싱, 장애 대응 등을 함께 고려해야 한다.
+
+## 3. 서비스 지연에 따른 문제가 생길 수 있다
+
+서버 사이드 렌더링은 서버에서 렌더링이 완료된 뒤 HTML을 전달한다.
+
+따라서 최초 렌더링 과정에서 지연이 발생하면, 렌더링이 끝나기 전까지 사용자가 빈 화면을 보게 될 수 있다.
+
+특히 서버에서 처리해야 할 데이터 요청이 느리거나, 렌더링 작업이 무거우면 초기 응답 시간이 길어질 수 있다.
+
+---
+
+# Next.js
+
+Next.js는 React 기반 프레임워크로, SPA 방식의 클라이언트 렌더링뿐만 아니라 서버 사이드 렌더링, 정적 사이트 생성, API Route, 파일 기반 라우팅 등을 제공한다.
+
+최초 진입 시 서버에서 미리 렌더링된 HTML을 제공할 수 있고, 이후 Hydration이 완료되면 브라우저의 JavaScript를 바탕으로 SPA처럼 페이지 전환을 처리할 수 있다.
+
+즉, 초기 로딩은 서버 렌더링의 장점을 활용하고, 이후 사용자 인터랙션과 라우팅은 클라이언트 측 JavaScript를 활용하는 구조라고 볼 수 있다.
+
+---
+
+## swcMinify
+
+책에서는 `next.config.js`에 다음과 같이 작성하는 예시가 나온다.
+
+> 도서 기준 297페이지
+
+```js
+// next.config.js
+module.exports = {
+ swcMinify: true,
+};
+```
+
+하지만 최신 Next.js에서는 이 설정을 직접 작성하지 않아도 된다.
+
+Next.js는 SWC 기반 minification을 기본으로 사용한다.
+
+또한 Next.js 15부터는 `next.config.js`에서 `swcMinify` 옵션을 설정할 수 없고, 해당 옵션은 제거되었다.
+
+따라서 최신 버전에서는 `swcMinify: true;` 옵션을 설정할 필요는 없다.
+
+출처: [공식 문서](https://nextjs.org/docs/architecture/nextjs-compiler?utm_source=chatgpt.com#minification)
+
+
+---
+
+# App Router의 404 처리
+
+App Router에서는 `app/not-found.tsx` 파일을 만들면 404 페이지를 커스텀할 수 있다.
+
+```tsx
+// app/not-found.tsx
+
+export default function NotFound() {
+ return
페이지를 찾을 수 없습니다.
;
+}
+```
+
+특정 페이지에서 의도적으로 404를 띄우고 싶다면 `notFound()` 함수를 호출하면 된다.
+
+```tsx
+import { notFound } from 'next/navigation';
+
+export default async function Page() {
+ const data = await getData();
+
+ if (!data) {
+ notFound();
+ }
+
+ return {data.title}
;
+}
+```
+
+---
+
+# App Router의 에러 처리
+
+App Router에서 일반적인 런타임 에러는 `error.tsx`로 처리한다.
+
+```tsx
+// app/error.tsx
+
+'use client';
+
+export default function Error() {
+ return 문제가 발생했습니다.
;
+}
+```
+
+`error.tsx`는 해당 route segment에서 발생한 에러를 처리하는 Error Boundary 역할을 한다.
+
+예상치 못한 에러나 500 에러에 가까운 상황도 `error.tsx`를 통해 사용자에게 별도의 에러 화면을 보여줄 수 있다.
+
+루트 레이아웃까지 포함한 전역 에러를 처리하고 싶다면 `global-error.tsx`를 사용할 수 있다.
+
+---
+
+# getServerSideProps란?
+
+`getServerSideProps`는 Pages Router에서 사용하는 서버 사이드 렌더링 함수다.
+
+페이지 요청이 들어올 때마다 서버에서 데이터를 가져오고, 그 데이터를 기반으로 HTML을 생성한다.
+
+```tsx
+// pages/posts/[id].tsx
+
+export async function getServerSideProps() {
+ const data = await fetchData();
+
+ return {
+ props: {
+ data,
+ },
+ };
+}
+```
+
+즉, `getServerSideProps`는 요청 시점에 필요한 데이터를 서버에서 가져와 페이지 컴포넌트에 props로 전달하는 역할을 한다.
+
+---
+
+# App Router에서 getServerSideProps의 변화
+
+App Router에서는 `getServerSideProps`를 사용하지 않는다.
+
+대신 Server Component에서 `async` 함수를 사용해 직접 데이터를 가져올 수 있다.
+
+```tsx
+// app/posts/[id]/page.tsx
+
+export default async function Page() {
+ const data = await fetchData();
+
+ return {data.title}
;
+}
+```
+
+Pages Router에서는 서버 데이터를 가져오기 위해 `getServerSideProps` 같은 별도의 함수를 사용했다.
+
+하지만 App Router에서는 기본적으로 컴포넌트가 Server Component이기 때문에, 컴포넌트 안에서 직접 `await`를 사용할 수 있다.
+
+---
+
+# pages/api와 Route Handler
+
+Pages Router에서는 `pages/api` 폴더 안에 API Route를 만들 수 있다.
+
+```ts
+// pages/api/hello.ts
+
+export default function handler(req, res) {
+ res.status(200).json({ message: 'hello' });
+}
+```
+
+`pages/api`는 React 페이지를 반환하는 것이 아니라, API 응답을 반환하는 서버 함수다.
+
+App Router에서는 `pages/api` 대신 Route Handler를 사용한다.
+
+Route Handler는 `route.ts` 파일로 작성한다.
+
+```ts
+// app/api/hello/route.ts
+
+export async function GET() {
+ return Response.json({ message: 'hello' });
+}
+```
+
+즉, App Router에서는 API 응답을 만들고 싶을 때 `app/api/.../route.ts` 파일을 사용한다.
+
+---
+
+# getStaticPaths
+
+`getStaticPaths`는 Pages Router에서 동적 라우트의 어떤 URL을 빌드 시점에 미리 만들지 정하는 함수다.
+
+예를 들어 파일이 다음과 같이 있다고 해보자.
+
+```tsx
+pages/posts/[id].tsx
+```
+
+이 파일 하나로 여러 URL이 만들어질 수 있다.
+
+```txt
+/posts/1
+/posts/2
+/posts/3
+```
+
+그런데 Next.js 입장에서는 빌드 시점에 어떤 `id` 페이지를 미리 만들어야 하는지 알 수 없다.
+
+그래서 `getStaticPaths`로 미리 생성할 경로를 알려줘야 한다.
+
+```tsx
+export async function getStaticPaths() {
+ return {
+ paths: [
+ { params: { id: '1' } },
+ { params: { id: '2' } },
+ { params: { id: '3' } },
+ ],
+ fallback: false,
+ };
+}
+```
+
+App Router에서는 `getStaticPaths`를 사용하지 않는다.
+
+---
+
+# generateStaticParams
+
+App Router에서는 `getStaticPaths` 대신 `generateStaticParams`를 사용한다.
+
+`generateStaticParams`는 동적 라우트에서 어떤 URL을 빌드 시점에 미리 생성할지 정하는 함수다.
+
+예를 들어 App Router에서 파일 구조가 다음과 같다고 해보자.
+
+```tsx
+app/posts/[id]/page.tsx
+```
+
+이때 `/posts/1`, `/posts/2`, `/posts/3` 페이지를 미리 만들고 싶다면 다음과 같이 작성할 수 있다.
+
+```tsx
+export async function generateStaticParams() {
+ return [
+ { id: '1' },
+ { id: '2' },
+ { id: '3' },
+ ];
+}
+
+export default function Page({ params }: { params: { id: string } }) {
+ return 게시글 ID: {params.id}
;
+}
+```
+
+`generateStaticParams`는 Pages Router의 `getStaticPaths`와 비슷한 역할을 한다.
+
+다만 App Router에서는 `paths` 객체를 반환하지 않고, params 객체 배열을 반환한다.
+
+---
+
+# getStaticProps
+
+`getStaticProps`는 Pages Router에서 정적 페이지 생성을 위해 사용하는 함수다.
+
+빌드 시점에 데이터를 가져오고, 그 데이터를 기반으로 HTML을 미리 만들어둔다.
+
+```tsx
+export async function getStaticProps() {
+ const data = await fetchData();
+
+ return {
+ props: {
+ data,
+ },
+ };
+}
+```
+
+`getStaticProps`는 빌드 시점에 실행되기 때문에, 자주 바뀌지 않는 데이터를 가진 페이지에 적합하다.
+
+App Router에서는 `getStaticProps`를 사용하지 않는다.
+
+대신 Server Component의 `fetch` 옵션이나 route segment config를 통해 정적 생성, 캐싱, 재검증 방식을 제어한다.
+
+---
+
+# Server Component에서 데이터 캐싱 제어
+
+App Router에서는 Server Component에서 데이터를 가져올 때 `fetch` 옵션을 사용해 캐싱 방식을 제어할 수 있다.
+
+## revalidate
+
+```tsx
+const res = await fetch('https://example.com/api/posts', {
+ next: {
+ revalidate: 60,
+ },
+});
+```
+
+`revalidate: 60`은 데이터를 60초 동안 캐싱하고, 이후 다시 요청이 들어오면 데이터를 재검증할 수 있게 한다.
+
+즉, 정적 페이지처럼 동작하면서도 일정 시간이 지나면 데이터를 새로 갱신할 수 있다.
+
+## cache: 'no-store'
+
+```tsx
+const res = await fetch('https://example.com/api/posts', {
+ cache: 'no-store',
+});
+```
+
+`cache: 'no-store'`는 요청 결과를 캐싱하지 않고, 매 요청마다 새 데이터를 가져오도록 한다.
+
+즉, Pages Router의 `getServerSideProps`처럼 요청 시점의 최신 데이터가 필요한 경우에 사용할 수 있다.
+
+---
+
+# Pages Router와 App Router 비교
+
+| 개념 | Pages Router | App Router |
+| --- | --- | --- |
+| 페이지 위치 | `pages` | `app` |
+| API 작성 | `pages/api` | `app/api/.../route.ts` |
+| 404 페이지 | `pages/404.tsx` | `app/not-found.tsx` |
+| 에러 페이지 | `pages/500.tsx`, `_error.tsx` | `error.tsx`, `global-error.tsx` |
+| 서버 사이드 렌더링 | `getServerSideProps` | Server Component + async/await |
+| 정적 데이터 생성 | `getStaticProps` | Server Component + fetch cache |
+| 동적 정적 경로 생성 | `getStaticPaths` | `generateStaticParams` |
+
+---