Intro: 아침이 힘든 사람

2주간의 사전교육기간이 이렇게 종료됐다. 정말 방대하고 또 어려운 지식을 2주동안 머리에 쑤셔 넣으려니 머리에 쥐가 날 것 같지만(이미 난 것 같다), 그만큼 또 많이 배워간다고 생각하니 내심 뿌듯하기도 하다.

이번주에는 Next.js에 대해 제대로 톺아보는 시간을 가져봤다. Next.js가 무엇인지, 그리고 어떻게 활용하는지 등을 세세하게 살펴보았다. 교육기간동안 배운 내용을 통해 프로젝트를 잘 진행하고, Next.js로 만든 내 개인 블로그를 보다 나은 방향으로 리팩토링할 계획이다.

2주차 학습내용

SSR과 CSR

서버 컴포넌트 / 클라이언트 컴포넌트와 함께 나를 가장 헷갈리게 했던 개념이다. 이 둘에 대해 알아보기 전에, 먼저 하이드레이션에 대해서 알아보자.

하이드레이션(Hydration)은 서버에서 렌더링된 정적 HTML 콘텐츠를 클라이언트 측에서 동적인 웹 애플리케이션으로 활성화하는 과정을 말한다. 일반적으로 다음과 같은 과정을 거치며 하이드레이션이 일어난다.

  1. 서버가 초기 HTML을 생성해 클라이언트에 전송한다.
  2. 클라이언트가 HTML을 받아 페이지로 표시한다.
  3. JS 번들이 로드되고 실행된다.
  4. JS가 기존 HTML 구조에 이벤트 리스너를 연결하고 필요한 상태를 초기화한다.
  5. 동적 페이지가 완성된다.

하이드레이션은 초기 로딩 속도를 개선해 사용자가 빠르게 컨텐츠를 볼 수 있고, 검색 엔진이 초기 HTML 콘텐츠를 크롤링해 SEO가 향상되며, 서버와 클라이언트 간의 작업을 효율적으로 분배해 성능이 최적화된다는 장점을 가지고 있다. 그렇다면 SSR와 CSR은 하이드레이션과 어떤 연관성을 가지고 있을까?

CSR

HTML과 CSS, JavaScript를 불러오면 이후에 화면을 렌더링하는 방식이다. JavaScript를 한번 불러오면 페이지 간의 이동이 빠르지만, 대신 JS를 불러올 때 로딩이 오래 걸리면 사용자는 초기 페이지를 늦게 확인할 수 밖에 없다.

SSR

그러한 CSR의 단점을 개선하기 위해서, SSR은 HTML과 CSS를 서버에서 만들어 사용자(클라이언트)에게 보내준다. 이후 하이드레이션 과정을 거쳐 클라이언트에서 JavaScript를 불러오면, 최종적으로 정적인 웹 콘텐츠가 동적으로 변환된다. 따라서 SSR을 사용하면 사용자는 화면을 조금 더 일찍 확인할 수 있다.

Next.js의 기본 개념

Next.js는 바로 이러한 서버 사이드 렌더링 기반의 프레임워크이다. 기존 React CSR의 한계를 극복하고, 개발자가 쉽게 SSR을 구현할 수 있도록 도와주며 다양한 성능 최적화 기법을 지원한다.

서버 컴포넌트와 클라이언트 컴포넌트

서버 컴포넌트와 클라이언트 컴포넌트는 React 18에서 도입된 새로운 개념인 RSC 등을 기반으로 한다.

서버 컴포넌트(Server Component)는 서버에서 렌더링되고 실행되는 컴포넌트로, 클라이언트로 전송되는 JS 번들의 크기를 줄이며 서버의 리소스에 직접 접근할 수 있다. 따라서 클라이언트에서 상호작용 하지 않아도 되는 UI를 구성하는데 적합하며, 기본적으로 Next.js의 컴포넌트는 모두 서버 컴포넌트의 형태로 구성된다.

서버에서 직접 DB나 파일 시스템에 접근할 수 있고, API 키나 토큰같은 민감한 정보를 클라이언트에 노출하지 않고 사용할 수 있다는 장점을 가지고 있다. 다만 클라이언트 컴포넌트와 다르게 상태를 가지거나 이벤트 핸들러와 브라우저 API를 사용할 수 없다.

반면 클라이언트 컴포넌트(Client Component)는 기존의 리액트 컴포넌트처럼 리액트의 모든 기능을 사용할 수 있으며, 상호작용이 필요한 UI 부분에 사용하는 것이 적합하다. 따라서 이를 적절하게 사용하는 것이 Next.js를 잘 활용하는 방법이라고 할 수 있다.

Next.js의 라우팅

Next.js의 라우팅에 대해 설명하기에 앞서, 라우트, 라우팅, 라우터를 짧게 정의하고 넘어가자.

  • 라우트(Route): 특정 URL 패턴과 그에 대응하는 컴포넌트 또는 함수를 연결하는 규칙
  • 라우팅(Routing): 웹 애플리케이션에서 URL을 해석하고 그에 따라 적절한 페이지나 컴포넌트를 표시하는 과정
  • 라우터(Router): 라우팅 기능을 구현하는 소프트웨어 컴포넌트

Next.js는 파일 기반 라우팅을 지원한다. 말 그대로 내가 Next.js를 사용한 프로젝트에서 만드는 폴더와 파일을 통해 라우팅을 하게 된다는 것이다. 간단한 예시를 살펴보자.

app/
├── page.tsx
├── layout.tsx
├── blog/
│   ├── page.tsx
│   └── posts/
│       └── [slug]/
│           └── page.tsx
└── components/
    ├── Header.tsx
    └── Footer.tsx

이렇게 구성된 프로젝트 구조는 다음과 같은 라우팅을 지원한다:

  • /
  • /blog
  • blog/posts
  • blog/posts/게시글-slug

Next.js는 이러한 라우팅 방식을 Page Router와 App Router라는 라우터로 처리하고 있다. 기존 Page Router에서 발전된 방식을 사용하고 있는 것이 App Router이며, Next.js도 현재 App Router를 기반으로 프로젝트를 만드는 것을 권장하고 있다. 사실 블로그를 만들 때에도 Page Router가 아닌 App Router를 사용했는데, 왜 Vercel은 Next.js에 App Router라는 새로운 라우팅 시스템을 도입했는지 궁금했었다. 정리하자면 다음과 같다.

App Router와 Page Router

먼저 Page Router는 pages 디렉토리 내의 파일 구조가 곧 라우트 구조가 되는 방식이다. 또한 공통의 레이아웃을 만들기 위해서는 컴포넌트를 수동으로 중첩해야 한다는 문제가 있었다. 데이터 페칭 방법으로는 getStaticProps, getServerSideProps, getInitialProps등을 사용했다.

App Router는 모든 컴포넌트가 기본적으로 서버 컴포넌트이며, 필요에 따라 'use client'를 명시함으로써 클라이언트 컴포넌트로 전환이 가능하다. 데이터 페칭 역시 컴포넌트 내에서 직접 비동기 함수를 사용할 수 있으며, 레이아웃의 경우 자동 중첩 레이아웃을 지원한다. 이외에도 병렬 라우트와 같은 고급 라우팅 패턴을 지원하며, 스트리밍 기법을 통해 내장된 최적화를 지원한다.

개인적으로 스트리밍 기법이 흥미롭게 느껴졌는데, 이를 통해 보다 나은 SSR을 지원할 수 있다. 스트리밍 기법은 전체 페이지가 서버에서 렌더링될 때까지 기다리지 않고, 준비된 부분부터 점진적으로 클라이언트에 전송하는 기술이다.

예를 들어 위와 같은 페이지가 있을 경우, Next.js의 스트리밍 기법은 준비가 완료되는 컴포넌트를 순차적으로 클라이언트에 보내준다. 이를 통해 초기 페이지 로딩 시간을 단축할 수 있고, 사용자는 일부 컨텐츠를 먼저 확인할 수 있다는 장점이 있다. App Router에서는 Suspense를 활용해 특정 컴포넌트에 스트리밍 기법을 적용할 수 있다.

다양한 라우팅 방법

Next.js는 App Router를 기반으로 다양한 파일 기반 라우팅 방법을 지원한다.

app/
├── page.tsx
├── about/
│   └── page.tsx
├── contact/
│   └── page.tsx
└── products/
    └── page.tsx

먼저 가장 기본적인 라우팅 기법이다. 폴더와 페이지를 사용해 간편하게 만들 수 있다.

app/
├── page.tsx
├── products/
│   ├── page.tsx
│   └── [id]/
│       └── page.tsx
└── users/
    └── [userId]/
        └── posts/
            └── [postId]/
                └── page.tsx

동적 라우팅 기법은 위와 같다. [id], [userId], [postId]가 동적인 세그먼트를 기반으로 라우팅을 지원한다.

Next.js의 캐싱

이번주에 Next.js를 학습하면서 가장 어려웠던 부분이라고 생각하는데, 바로 캐싱과 관련된 부분이다. 사실 지금도 이해가 완벽하게 되진 않아서 블로그에 기록한 뒤 계속해서 다시 읽어보려고 한다.

miss: 특정 메커니즘에 대한 캐시가 존재하지 않음. 원본 데이터 소스에서 데이터를 가져와야 함.
hit: 요청된 데이터가 캐시에 있어 즉시 사용 가능함
set: 새로운 데이터를 캐시에 저장함. 일반적으로 miss 이후에 발생.

Next.js의 캐싱 과정을 자체적으로 Request Memoization, Data Cahce, Full Route Cache, Router Cache 4가지의 방법을 가진다.

Router Cache

  • 클라이언트 측에서 작동하는 인메모리(in-memory) 캐시이다.
  • 사용자가 이미 방문한 페이지의 RSC 페이로드를 저장한다.
  • 세션 동안만 유지되며, 새로고침, revalidateCache등의 방법으로 무력화 가능하다.

Full Route Cache

  • 서버에서 동작하는 지속적인 캐시이다.
  • 정적으로 생성된 라우트의 RSC 페이로드와 HTML을 저장한다.
  • 빌드 시 생성되며, 배포 간 유지된다.
  • SSG와 ISR에 사용된다.

Request Memoization

  • 서버 측에서 동작하는 인메모리 캐시이다.
  • 같은 API 요청에 대해서 중복된 데이터 요청을 방지하기 위해, fetch 요청의 결과를 메모이제이션 하여 동일한 URL과 옵션으로 여러 번 호출되는 경우 한 번만 실행된다.
  • 요청이 완료되면(해당 요청을 사용하는 컴포넌트가 화면에 렌더링되면) 메모리에서 자동으로 삭제된다.

Data Cache

  • 서버 측에서 동작하는 지속적인 캐시이다.
  • fetch 요청의 반환값을 JSON 형태로 저장한다.
  • revalidate 옵션을 사용해 캐시 유효기간을 지정할 수 있다.

이러한 캐싱의 문제점은 최신 데이터를 서버에서 보내줘도 클라이언트가 캐싱되어 있는 데이터를 먼저 보여주기 때문에, 실시간 데이터 흐름이 강조되는 서비스에서는 문제가 발생할 수도 있다는 것이다. 따라서 캐싱 무력화 방법을 사용해 적절하게 적용하는 것이 중요하다고 한다.

2주차의 KPT 회고

Keep

꾸준히 운동하기

정말 극적인 효과를 보고 있는 것은 아니지만, 꾸준하게 운동을 가고 루틴에 맞춰 살다 보니 몸 상태가 점점 예전으로 돌아가고 있는 게 조금이나마 느껴지고 있다 👍🏻

정리 + 복습하기

수업시간에 배운 내용을 기록하면서 듣다 보니 잠깐 놓치거나 이해가 되지 않는 부분이 있어도 집에 가서 다시 확인할 수 있었다 🧑🏻‍💻

Problem

근데 이제 정리'만' 했던...

정리하는 것 까지는 좋지만, 돌아와서 나만의 언어로 다시 가공하지 않는다면 그냥 따라쓰기에 불과하다고 느껴졌다. 최근 글 하나를 읽게 됐는데, 제텔카스텐(Zetelkasten) 메모 방식을 소개하는 글이다. 이 방법이 도움이 될 것 같다.

Try

상향식 정리하기

그래서 지금은 제텔카스텐 메모를 꾸준히 시도하고 있다. 물론 제대로 활용하기까지는 시간이 걸리겠지만, 제텔카스텐의 핵심 개념인 '하나의 메모에는 한 가지 생각만 정리한다'를 지키려고 노력하고 있다.

Outro: 프로젝트, 잘할 수 있겠지?

이렇게 2주간의 사전직무교육이 모두 마무리됐다. 길다면 길고, 짧다면 짧은 기간이었지만, 그 기간동안 배워가는 것 만큼은 정말 많았던 것 같다. 사실 아직도 소화가 덜 된 기분이긴 한데, 프로젝트를 진행하면서 개념을 본격적으로 활용해볼 예정이다.

프로젝트를 시작하기 전 팀이 정해지고 이런저런 계획을 세우다 보니 조금은 불안하기도 하다. 오랜만의 팀장 경험이고, 스스로의 실력에 대한 확신도 100%는 아닌 것 같다. 하지만 늘 그래왔듯 잘 해낼 수 있을 거라고 믿고 이렇게 회고를 마무리해본다. 다음 주에는 프로젝트 회고와 함께 돌아와야지 😁


본 후기는 본 후기는 [유데미x스나이퍼팩토리] 프로젝트 캠프 : Next.js 3기 과정(B-log) 리뷰로 작성 되었습니다.

#유데미 #udemy #웅진씽크빅 #스나이퍼팩토리 #인사이드아웃 #미래내일일경험 #프로젝트캠프 #부트캠프 #React #리액트프로젝트 #프론트엔드개발자양성과정 #개발자교육과정

profile
개발과 친해지고 싶은 개발자

0개의 댓글