React & Next.js 활용해보기 / CSR vs SSR
SPA란?
SPA (Single Page Application)
말 그대로 한 개의 페이지로 구성된 애플리케이션
✅ 기존 방식 (MPA: Multi Page Application)
- 페이지 이동 시마다 새 HTML을 서버로부터 받아옴
- 페이지마다 서버가 새로 해석 후 렌더링
- 느리고, 화면 전환 시 깜빡임 발생
✅ CSR 기반 SPA 방식
- 처음에 한 번만 HTML과 JS를 받아옴
- 이후에는 브라우저에서 필요한 부분만 업데이트
- 서버는 데이터를 제공하고, 뷰 렌더링은 클라이언트(브라우저)에서 처리
React와 CSR (Client-Side Rendering)
React는 SPA 구현을 위한 대표적인 라이브러리
✅ CSR 특징
- 초기 HTML은 거의 비어 있음
- 브라우저에서 JS가 실행되며 React가 컴포넌트를 렌더링
- **가상 돔(Virtual DOM)**을 통해 필요한 부분만 업데이트 → 실제 DOM 반영
✅ 라우팅
- 여러 주소에서 다른 화면을 보여주는 기능
- React Router를 사용하여 구현
- <Routes>, <Route> 사용
- 중첩 라우팅 시 <NavLink> 등으로 처리
- 클라이언트 사이드에서 작동하는 라우팅
- => 서버에 새로 요청하지 않고, JS로 미리 불러온 컴포넌트만 바꿔 보여주는 방식
Next.js와 SSR (Server-Side Rendering)
Next.js는 기본적으로 SSR 기반의 프레임워크입니다.
✅ SSR이란?
클라이언트가 페이지를 요청할 때, 서버에서 HTML을 완성해서 보내주는 방식
✅ Next.js의 SSR 동작 방식
- 모든 컴포넌트가 기본적으로 서버 사이드에서 먼저 렌더링
- 초기 로딩 속도가 빠르고, SEO에 유리
✅ SSR의 장점
- 페이지 로딩 속도가 CSR보다 빠름 (초기 페이지)
- 검색 엔진 최적화(SEO) 가능
❗ SSR의 단점
- 초기 로딩 이후 페이지 이동 시 서버 요청이 계속 발생 → 다소 느릴 수 있음
SSR에서 CSR로 전환이 필요한 경우
Next.js는 기본이 SSR이지만,=> 특정 상황에서는 CSR로 전환!!!
✅ 대표적인 경우: 훅(Hook) 사용 시
- useEffect, useState 같은 React 훅은 브라우저 환경에서만 작동
- 이 때는 CSR 방식으로 처리되어야 정상 작동
CSR vs SSR 정리
CSR | SSR | |
설명 | 초기 HTML은 비어 있고, JS가 실행되며 React 컴포넌트를 렌더링 |
클라이언트 요청마다 서버에서 새로운 화면(View)을 만들어 전달 |
대표 프레임워크 | CRA (Create React App) 기반 React | Next.js |
초기 로딩 속도 | 느림 =>전체 파일을 다운로드한 후 렌더링 |
빠름 =>HTML이 서버에서 생성되어 바로 전달 |
인터렉션 속도 | 빠름 =>브라우저에서 렌더링하므로 빠른 반응성 제공 |
느릴 수 있음 =>페이지 이동 시마다 서버 요청이 필요 |
새로고침/페이지 이동 | 새로고침 없음 =>네이티브 앱과 비슷한 사용자 경험 제공 |
페이지 이동 시 새 요청 발생 가능 |
서버 트래픽 | 감소 => 클라이언트에서 View 렌더링 처리 |
증가 => 요청마다 서버에서 HTML 생성 |
SEO (검색 엔진 최적화) |
불리함 =>JS를 무시하면 화면을 수집하지 못함 → sitemap 등 보완 필요 |
유리함 =>검색엔진이 HTML 구조를 바로 인식 |
JS 비활성 시 동작 | X => JS가 없으면 화면이 보이지 않음 |
O =>서버에서 생성된 HTML이 바로 보임 |
기타 참고 사항 | 구글은 JS 렌더링을 지원하지만, 다른 검색엔진은 그렇지 않을 수 있음 |
- |
.
Next.js 수동 설치하기
프로젝트 이름은 소문자로만, 두 단어 이상일 때는 케밥 케이스로 만든다.
- npm init -y
- npm i react next react-dom
- package.json을 열어 test 지우고 dev 스크립트 추가하기
"scripts": {
"dev": "next dev"
}
=> Next.js를 사용할 경우엔 react-scripts 대신 Next.js가 제공하는 명령어
4. npm run dev 실행
Next.js 자동 설치하기
설치 순서
- 가장 최근 버전으로 생성 → npx create-next-app
- npm으로 설치하기 → npx create-next-app --use-npm
- 특정 버전으로 설치하기 → npx create-next-app@버전 --use-npm
- 설치 옵션 선택
=> import alias 설정 여부 선택
=> Next.js에서는 기본적으로 @를 src/ 폴더에 매핑 => tsconfig.json에서 변경 가능
=> Next.js는 버전에 따라 내부적으로 사용하는 **빌드 도구(번들러)**가 바뀜
=> 예전에는 Webpack, 지금은 Turbopack
📁 Next.js 디렉토리 구조 정리
my-next-app/
│
├── public/ # 정적 파일(이미지, 아이콘, 폰트 등)을 위한 디렉토리
│ # 접근 경로: /이미지파일명
│
├── src/ # 주요 소스 코드가 포함된 디렉토리
│ ├── app/ # App Router 기반 라우팅 설정 (Root segment)
│ │ # → 페이지 구성, 라우팅 처리 (SSR/CSR 지원)
│ │
│ ├── components/ # 재사용 가능한 UI 컴포넌트 모음
│ │ # → 버튼, 카드, 폼 등 공통 컴포넌트
│ │
│ ├── services/ # Ajax 요청(API 호출)을 위한 파일
│ │ # → axios, fetch 등 비즈니스 로직 처리
│ │
│ ├── store/ # 전역 상태 관리(store)를 위한 디렉토리
│ │ # → Redux, Zustand 등 전역 상태 저장
│ │
│ ├── styles/ # CSS, SCSS, 모듈 스타일 등을 위한 디렉토리
│ │ # → 전역 스타일, 변수 설정 등
│ │
│ ├── types/ # TypeScript의 타입 정의를 위한 디렉토리
│ │ # → 인터페이스, 타입 정의 모음
│ │
│ └── utils/ # 자주 사용하는 유틸 함수들을 위한 디렉토리
│ # → 날짜 포맷팅, 숫자 변환, 공통 로직 등
│
├── .next/ # Next.js가 빌드한 결과물이 저장되는 디렉토리
│ # → 자동 생성됨. 배포 시 포함 X
│
├── package.json # 프로젝트 메타정보 및 의존성
└── next.config.js # Next.js 설정 파일 (필요 시 생성)
✅Next.js에서 이미지 다루기
Next.js는 기본이 SSR이기 때문에, 이미지 파일은 반드시 public 폴더에 넣어야 한다.
🔍 왜?
- SSR에서는 서버가 HTML을 생성해서 클라이언트로 보내기 때문에,
- **이미지를 import하는 방식(CRA 스타일)**이 아닌, 경로 기반 접근이 필요
- 따라서 이미지 파일은 **정적 자원(static assets)**으로 처리되어야 하고,
- 이를 위해 Next.js는 /public 폴더를 기본 정적 파일 경로로 사용
✅ 브라우저 자동으로 열기 설정 (package.json)
next dev 실행 시 브라우저가 자동으로 열리게 하기 위해, package.json에 스크립트를 추가
- package.json 파일
- 아래와 같이 스크립트를 추가
"scripts": {
"dev": "npm run open-browser && next dev",
"open-browser": "start http://localhost:3000"
}
✅ Next.js App Router: 폴더 = 경로(Path)
Next.js(App Router 구조, src/app/)에서는 디렉토리 구조가 곧 URL 경로가 됩니다.
📁 폴더 = 경로
📄 page.tsx 또는 page.jsx = 해당 경로의 페이지

page.tsx 파일은 필수!!!
해당 폴더가 라우팅 가능한 페이지가 되려면 반드시 필요
"use client"; // 클라이언트 컴포넌트임을 명시
import React from "react";
import styles from "./Navigation.module.css";
import Link from "next/link";
const Navigation = () => {
return (
<nav className={styles.nav}>
<ul className={styles.menu}>
<li><Link className={styles.menu_item} href="/">Home</Link></li>
<li><Link className={styles.menu_item} href="/about-us">AboutUs</Link></li>
<li><Link className={styles.menu_item} href="/book">Book</Link></li>
<li><Link className={styles.menu_item} href="/book/book-insert">book insert</Link></li>
</ul>
</nav>
);
};
export default Navigation;
✅className={styles.menu_item} 왜 이렇게 쓰는 걸까?
1. CSS Module 방식이라서
Next.js에서는 *.module.css 파일을 쓰면 CSS Module로 인식
=> 클래스명이 자동으로 고유한 이름으로 바뀌어서 충돌 없이 사용
2. 일반적인 클래스와 비교해보면?
일반 CSS | className="menu_item" | 전역 클래스. 다른 컴포넌트와 충돌 가능성 있음 |
CSS Module | className={styles.menu_item} | 컴포넌트 단위로 분리. 클래스명이 내부적으로 유니크하게 변환됨 |
✅페이지 이동: a 태그 대신 Link 태그 사용
Next.js에서는 클라이언트 사이드 라우팅을 위해 반드시 <Link> 컴포넌트를 사용
<Link href="/about-us">About Us</Link>
파일명 | 설명 |
page.tsx | 해당 경로에 대한 페이지 컴포넌트 (필수) |
layout.tsx | 해당 경로 이하에 공통으로 적용되는 레이아웃 |
loading.tsx | 페이지가 로딩 중일 때 보여줄 컴포넌트 |
error.tsx | 렌더링 도중 오류 발생 시 보여줄 에러 컴포넌트 |
not-found.tsx | 404(존재하지 않는 경로) 접근 시 보여줄 컴포넌트 / app>not-found.tsx 배치 : 전역 설정 |
Private Directory란?
라우팅과 관계없는 디렉토리임을 명시하기 위해 디렉토리 이름 앞에 _(언더스코어)를 붙이는 관례
형식] _directory-name
(home) 폴더 사용하기
— App Router에서 특수 폴더 이름
(home) 폴더는 실제 라우팅 경로에 영향을 주지 않는 그룹 폴더입니다.
즉, 내부에는 여러 파일이 들어갈 수 있지만
URL 경로에서는 전혀 드러나지 않는다!!!!!
Dynamic Routes (동적 라우팅)
개념
**Path Variable(경로 변수)**를 사용해서,
동적으로 특정 데이터를 표시하는 페이지를 만들고 싶을 때 사용하는 라우팅 방식
예를 들어 /book/12345처럼 특정 isbn 값에 따라 상세 페이지를 렌더링할 때 사용
디렉토리 구조
src/
└── app/
└── book/
└── [isbn]/ ✅ Path Variable로 처리
└── page.tsx
📂 대괄호 [ ] 안에 들어간 이름이 URL에서 변수처럼 작동합니다.
파일 내용 ([isbn]/page.tsx)
import React from "react";
// params로부터 Path Variable 'isbn' 추출
const BookDetail = ({ params: { isbn } }: { params: { isbn: string } }) => {
console.log("isbn", isbn);
return (
<div>
<h1>Book Detail : {isbn}</h1>
</div>
);
};
export default BookDetail;
실제 URL 호출 예
http://localhost:3000/book/12345 | isbn 값으로 12345 전달 |
http://localhost:3000/book/98765 | isbn 값으로 98765 전달 |
=> 위 URL들은 모두 동일한 [isbn]/page.tsx 파일을 사용하여 페이지가 동적으로 생성
동적 라우팅 활용
import Link from "next/link";
import React from "react";
// 소문자로 시작하는 컴포넌트는 비권장
const book = () => {
return (
<div>
<div>
<Link href="./book/1">1111</Link>
</div>
<div>
<Link href="./book/2">2222</Link>
</div>
</div>
);
};
export default book;
- /book 경로에서
./book/1, ./book/2 링크 클릭 시 각각 book/1, book/2 경로로 이동 - 해당 경로에 있는 동적 라우트:
src/app/book/[isbn]/page.tsx 에서 isbn 값을 받아 상세 페이지 출력
Next.js에서 훅 사용
✅ 훅(Hooks) 사용 = CSR이 필요!
Next.js는 기본적으로 SSR(Server Side Rendering) 기반 프레임워크
즉, 컴포넌트는 기본적으로 서버에서 먼저 렌더링
하지만!
useState, useEffect, usePathname 같은 **React 훅(Hook)**은 브라우저에서만 동작
그래서 이런 훅을 사용하는 컴포넌트는 **클라이언트 사이드 렌더링(CSR)**이 되어야 한다!!!1
이를 Next.js에 명시적으로 알려주는 지시어
"use client";
✅ 예제 코드: 현재 경로(path)에 따라 메뉴 강조하기
"use client"; // 클라이언트 컴포넌트로 선언 (CSR 전환)
import React from "react";
import styles from "./Navigation.module.css";
import Link from "next/link";
import { usePathname } from "next/navigation";
const Navigation = () => {
const path = usePathname(); // 현재 브라우저의 경로 가져오기
return (
<nav className={styles.nav}>
<ul className={styles.menu}>
<li>
<Link className={styles.menu_item} href="/">
Home{path === "/" ? "🔥" : ""}
</Link>
</li>
<li>
<Link className={styles.menu_item} href="/about-us">
AboutUs{path === "/about-us" ? "😍" : ""}
</Link>
</li>
...
</ul>
</nav>
);
};
export default Navigation;
✅ 동작 방식!!!!!!
- usePathname() 훅을 통해 현재 주소를 알아냄
- 그 주소와 메뉴 항목 경로가 같으면 이모지를 붙여 강조
- 이 동작은 브라우저에서만 가능하기 때문에 use client가 꼭 필요
✅JS를 끄면 어떻게 될까?
CSR은 브라우저에서 JS가 실행되어야만 렌더링이 완료
=> 따라서 브라우저에서 JavaScript를 꺼버리면
- 화면이 안 뜨거나 비정상 렌더링
- usePathname이 작동하지 않음 (경로 강조 기능도 안 됨)
- 기본 HTML만 보일 수 있음
반대로 SSR은 JS 없이도 기본 화면은 보여줄 수 있다!!(서버가 HTML을 만들어서 보내니까)