Next.js App Router를 공부하다 보면 가장 자주 접하게 되는 개념이 Server Component와 Client Component이다.

처음에는 단순히

  • Server Component = 서버에서 실행
  • Client Component = 브라우저에서 실행

정도로 이해했는데, 실제로 코드를 작성하다 보면 useState는 왜 사용할 수 없는지, "use client"는 어디에 붙여야 하는지, 모든 컴포넌트를 Client Component로 만들면 안 되는지 등이 헷갈렸다.

이번 글에서는 두 컴포넌트의 차이와 각각 언제 사용하는지 정리해보려고 한다.


1. Server Component란?

Next.js의 App Router에서는 컴포넌트가 기본적으로 Server Component로 동작한다.

따라서 별도의 설정 없이 아래처럼 컴포넌트를 작성하면 Server Component가 된다.

export default function Page() {
  return <h1>Hello Next.js</h1>;
}

Server Component는 서버 환경에서 렌더링되며, 브라우저에서 실행할 필요가 없는 JavaScript를 클라이언트로 보내지 않을 수 있다.

또한 서버에서 실행되기 때문에 데이터베이스나 서버 내부 리소스에 접근하는 로직을 작성할 수 있다.

예를 들어 다음처럼 서버에서 데이터를 조회할 수 있다.

export default async function Page() {
  const res = await fetch("https://api.example.com/posts");
  const posts = await res.json();

  return (
{posts.map((post) => (

{post.title}

))}

  );
}

별도의 useEffect 없이 컴포넌트 내부에서 async/await를 사용해 데이터를 가져올 수 있다.

Server Component의 장점

  • 서버에서 직접 데이터 조회 가능
  • 데이터베이스에 직접 접근 가능
  • API Key 등 민감한 정보를 클라이언트에 노출하지 않을 수 있음
  • 클라이언트로 전달되는 JavaScript 양을 줄일 수 있음
  • 데이터 조회 로직을 UI와 가까운 위치에서 작성할 수 있음

React 공식 문서에서도 Server Component는 빌드 시점이나 요청 시점 등 클라이언트 앱과 분리된 환경에서 실행될 수 있다고 설명하고 있다.


2. Server Component에서 할 수 없는 것

Server Component는 브라우저에 존재하는 컴포넌트가 아니기 때문에 사용자와 직접 상호작용하는 기능을 사용할 수 없다.

대표적으로 다음 기능을 사용할 수 없다.

useState
useEffect
onClick
onChange
window
localStorage

예를 들어 아래 코드는 App Router에서 오류가 발생한다.

import { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount(count + 1)}>
      {count}
    </button>
  );
}

useState는 브라우저에서 상태를 유지하면서 다시 렌더링해야 하기 때문이다.

Server Component는 렌더링이 끝난 뒤 브라우저에서 상태를 유지하는 컴포넌트가 아니기 때문에 이런 상호작용을 처리할 수 없다.

이럴 때 사용하는 것이 Client Component이다.


3. Client Component란?

Client Component는 브라우저에서 사용자와 상호작용해야 하는 부분에 사용하는 컴포넌트이다.

Client Component로 만들기 위해 파일 최상단에 "use client"를 선언한다.

"use client";

import { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount(count + 1)}>
      클릭 {count}
    </button>
  );
}

이제 해당 컴포넌트에서는 상태와 이벤트를 사용할 수 있다.

Client Component가 필요한 경우

다음과 같은 기능을 사용하는 경우 Client Component가 필요하다.

  • useState
  • useEffect
  • useReducer
  • 이벤트 핸들러
    • onClick
    • onChange
    • onSubmit
  • 브라우저 API
    • window
    • document
    • localStorage
  • 사용자 인터랙션
  • 일부 클라이언트 전용 훅

즉,

사용자가 클릭하거나 입력하고 그 결과에 따라 화면이 변해야 한다면 Client Component가 필요한 경우가 많다.


4. "use client"는 정확히 무엇일까?

처음에는 "use client"를

이 컴포넌트를 클라이언트에서 렌더링해라

정도로 생각했다.

하지만 조금 더 정확히 보면 "use client"는 Server Component와 Client Component 사이의 경계(boundary)를 정의하는 역할을 한다.

예를 들어 다음과 같은 구조가 있다고 해보자.

Page
 ├─ Header
 ├─ ProductList
 └─ LikeButton

Page, Header, ProductList는 사용자 상호작용이 필요하지 않다고 가정한다.

반면 LikeButton은 클릭할 때 상태가 바뀐다.

그렇다면 굳이 전체 페이지를 Client Component로 만들 필요가 없다.

// app/page.tsx
import LikeButton from "./LikeButton";

export default async function Page() {
  const products = await getProducts();

  return (
    <>
      <Header />

      <ProductList products={products} />

      <LikeButton />
    </>
  );
}
// LikeButton.tsx

"use client";

import { useState } from "react";

export default function LikeButton() {
  const [liked, setLiked] = useState(false);

  return (
    <button onClick={() => setLiked(!liked)}>
      {liked ? "좋아요 취소" : "좋아요"}
    </button>
  );
}

이 경우

Page          → Server Component
Header        → Server Component
ProductList   → Server Component
LikeButton    → Client Component

처럼 구성할 수 있다.

React 공식 문서에서는 "use client"가 선언된 모듈과 해당 모듈이 불러오는 의존성들이 클라이언트 영역에 포함된다고 설명한다.


5. 왜 모든 컴포넌트에 "use client"를 붙이면 안 될까?

처음에는

그냥 전부 Client Component로 만들면 편한 거 아닌가?

라는 생각이 들 수 있다.

실제로 그렇게 만들어도 동작하는 경우는 많다.

하지만 이렇게 하면 Next.js App Router가 Server Component를 제공하는 장점을 제대로 활용하기 어렵다.

예를 들어 페이지 전체에 "use client"를 선언하면 브라우저에서 처리해야 하는 JavaScript가 많아질 수 있다.

"use client";

export default function Page() {
  // ...
}

그리고 이 페이지에서 불러오는 컴포넌트들도 클라이언트 영역에 포함될 수 있다.

따라서 일반적으로는

가능하면 Server Component를 유지하고, 사용자 상호작용이 필요한 작은 부분만 Client Component로 분리하는 방식

이 좋다.


6. Server Component와 Client Component 비교

구분Server ComponentClient Component

기본 여부 App Router 기본값 "use client" 필요
실행 환경 서버 브라우저 + Next.js의 사전 렌더링 과정
useState 사용 불가 사용 가능
useEffect 사용 불가 사용 가능
이벤트 처리 불가 가능
DB 직접 접근 가능 불가
서버 비밀 정보 접근 가능 노출 위험
브라우저 API 사용 불가 사용 가능
JS 번들 영향 클라이언트 JS 감소에 유리 클라이언트 JS에 포함

여기서 주의해야 할 점은 Client Component라고 해서 처음부터 무조건 브라우저에서만 HTML이 만들어지는 것은 아니라는 것이다.

Next.js는 초기 페이지 요청 시 Client Component도 서버 측 렌더링 과정에 참여할 수 있다.

따라서

Client Component = CSR

이라고 단순하게 동일시하면 정확하지 않다.

Client Component라는 표현은 브라우저에서 실행되어야 하는 React 코드의 경계에 더 가깝다.


7. Server Component와 Client Component는 어떻게 나누면 될까?

컴포넌트를 나눌 때 다음과 같은 기준으로 생각하면 이해하기 쉽다.

Server Component가 적합한 경우

데이터 조회
DB 접근
정적인 UI
게시글 목록
상품 목록
서버 내부 정보 사용

예를 들어 상품 목록 자체는 Server Component로 만들 수 있다.

export default async function ProductList() {
  const products = await getProducts();

  return (
    <ul>
      {products.map((product) => (
        <li key={product.id}>
          {product.name}
        </li>
      ))}
    </ul>
  );
}

Client Component가 적합한 경우

버튼 클릭
폼 입력
모달
드롭다운
탭
검색 필터
실시간 상태 변경

예를 들어 검색 입력창은 사용자가 입력한 값을 상태로 관리해야 하므로 Client Component가 적합하다.

"use client";

import { useState } from "react";

export default function SearchInput() {
  const [keyword, setKeyword] = useState("");

  return (
    <input
      value={keyword}
      onChange={(e) => setKeyword(e.target.value)}
    />
  );
}

8. 실제 페이지에서는 둘을 함께 사용한다

Server Component와 Client Component 중 하나만 선택해야 하는 것은 아니다.

실제로는 한 페이지 안에서 두 방식을 조합해 사용하는 경우가 많다.

예를 들어 쇼핑몰 상품 페이지를 생각해보면

ProductPage
│
├─ ProductInfo
├─ ProductDescription
├─ ReviewList
│
├─ QuantitySelector
└─ AddToCartButton

다음과 같이 나눌 수 있다.

ProductPage        Server Component
ProductInfo        Server Component
ProductDescription Server Component
ReviewList         Server Component

QuantitySelector   Client Component
AddToCartButton    Client Component

상품 정보나 리뷰 목록은 서버에서 가져와 출력하면 된다.

반면 수량 선택과 장바구니 버튼은 사용자의 클릭이나 상태 변경이 필요하므로 Client Component로 만들 수 있다.

결국 핵심은

페이지 전체를 Server 또는 Client 중 하나로 선택하는 것이 아니라, 컴포넌트별 역할에 따라 서버와 클라이언트의 경계를 나누는 것이다.


9. "use server"를 붙이면 Server Component일까?

여기서 헷갈리기 쉬운 부분이 하나 있다.

"use server";

라는 문법도 존재하기 때문에

"use client" → Client Component
"use server" → Server Component

라고 생각하기 쉽다.

하지만 그렇지 않다.

Server Component에는 별도의 "use server" 선언이 필요하지 않다.

App Router에서는 기본적으로 Server Component이기 때문이다.

"use server"는 Server Function을 정의하기 위한 지시어이다.

따라서 다음처럼 기억하는 편이 좋다.

Server Component
→ 기본값

Client Component
→ "use client"

Server Function
→ "use server"

정리

Next.js App Router에서는 Server Component가 기본값이다.

Server Component는 서버에서 데이터 조회나 렌더링을 처리할 수 있고, 클라이언트에 전달하는 JavaScript를 줄이는 데 도움이 된다.

반대로 useState, useEffect, onClick처럼 브라우저에서 사용자와 상호작용해야 하는 기능은 Client Component에서 처리해야 한다.

따라서 모든 컴포넌트를 Client Component로 만드는 것보다

Server Component
       ↓
대부분의 UI와 데이터 처리

       +

Client Component
       ↓
상호작용이 필요한 부분

처럼 필요한 부분만 Client Component로 분리하는 것이 Next.js App Router를 사용하는 핵심이라고 이해했다.

처음에는 Server Component와 Client Component를 단순히 SSR과 CSR의 차이라고 생각했지만, 실제로는 컴포넌트 트리에서 서버와 클라이언트의 실행 경계를 어떻게 나눌 것인가에 대한 개념에 더 가깝다.


참고

  • Next.js 공식 문서 - Server and Client Components
  • React 공식 문서 - Server Components
  • React 공식 문서 - 'use client'

AWS Certified Cloud Practitioner 자격증 공부

 

AWS Certified Cloud Practitioner를 취득한다 해서 클라우드를 잘 다루는 것은 아니지만 cloud native에 대해 정리하는덴 도움이 될거 같아 올해 1월에 취득하였다

공부 기간은 2주 반정도 되었다

1주엔 AWS에서 올려준 한국어 동영상 강의를 들었다.

공부 계획을 세우기 위해 여러 블로그를 찾아보니 처음엔 동영상 강의를 보면서 전체적인 서비스를 파악하는 것이 좋다고 하여 매일 한 챕터씩 볼려고 노력했다

 

▶동영상 사이트

https://explore.skillbuilder.aws/learn/signin

 

Self-paced digital training on AWS - AWS Skill Builder

Your learning center to build in-demand cloud skills. Skill Builder provides 500+ free digital courses, 25+ learning plans, and 19 Ramp-Up Guides to help you expand your knowledge. Courses cover more than 30 AWS solutions for various skill levels. Skill Bu

explore.skillbuilder.aws

 

2주째부터 온라인에서 돌아다니는 400 몇개의 덤프를 풀었다.

이 하나의 파일을 가지고 오답노트를 정리하면서 한 3번 정도 계속 풀었다.

오답노트를 한글문서로 정리하고 pdf로 변환해서 시간 날 때마다 계속 봤다.

하지만 한 파일을 여러번 문제를 푸는것보단 많은 문제를 1, 2 번 풀어보는게 더 나은거 같다.

어쩌피 덤프 문제는 돌고 돌기 때문에 같은 문제가 여러번 나오기 때문이다.

물론 틀린 문제는 따로 정리는 해놓고 시험보기 한 2일 전에 틀린거 살펴보면 좋을 거 같다.

그리고 덤프 문제에 대한 정답은 공식적으로 나와있진 않아서 공식 홈페이지를 많이 들어가서 찾아 보았다.

학교 시험을 잘 보려면 교과서를 많이 봐야 하는거처럼 AWS 시험의 교과서는 공식 홈페이지이니까 공홈가서 영어로도 보고 한국어로 번역해서도 보고 여러번 보았다.

그러다가도 모르겠으면 chatGPT에게 물어보면서 공부했다. chatGPT도 틀리는 경우도 종종 있기 때문에 공홈에서 더블 체크!!

 

▶덤프 문제 모아져 있는 사이트

https://www.examtopics.com/exams/amazon/aws-certified-cloud-practitioner/

 

Free & Accurate Amazon AWS Certified Cloud Practitioner Practice Questions | ExamTopics

As we often say at ExamTopics, work smarter not harder. You are about to see a study guide that took hours of hard collection work, expert preparation, and constant feedback. That's why we know this exam prep will help you get that high-score on your journ

www.examtopics.com

 

▶덤프 문제 파일

AWS Cloud Practitioner - Practice Questions.pdf
2.76MB

 

 

▶위 덤프 문제 풀다가 정리한 오답노트

클라우드 오답 정리.pdf
0.15MB

 


AWS Certified Cloud Practitioner 후기

문제 풀 때 생각보다 내가 풀었던 덤프에 안나와서 걱정했지만 다행히도 합격했다!!

이 자격증을 따고 한 2달이 지났다.

이 자격증을 따기 위해 공부했던 내용들이 아예 헛되진 않은거 같다.

물론 클라우드 구축하는건 어렵지만 프로젝트 멘토님이나 강사님들께 설명을 들으면 자격증 공부했을 때 내용들이 새록 새록 생각나서 이해하는데 도움이 된다. 

지금은 최종 프로젝트 중이라 시간이 여유있지 않지만 여력이 된다면 aws solution architect professional자격증도 취득할 것이다.

sk 쉴더스 클라우드 보안 가이드를 기준으로 취약점을 진단 후 조치 내역 정리

 

1. 계정 

    - IAM 사용자 계정 관리 시 태그 사용

    - MFA

 

2. 권한

    - 인프라 담당자, 개발 담당자 계정에 설정 되어 있는 AdministratorAccess를 제거

    - 각 담당에 맞는 정책 생성 후 설정

 

3. 리소스 

    - 보안그룹 인/아웃 바운드 포트에 대한 설정을 서비스에 맞게 수정

      → 프론트엔드 보안 그룹 : 인바운드 규칙 내 ICMP, SSH 프로토콜 규칙을 삭제

      → 백엔드 보안 그룹 : 인바운드 규칙 내 TCP 3306 포트 규칙을 삭제

      → 베스천 호스트 보안 그룹 : 인바운드 규칙 내 TCP 3306 프로토콜 규칙을 삭제

    - 보안그룹 인/아웃 바운드 대상에 0.0.0.0/0 설정을 서비스에 맞게 수정

      → 프론트엔드 보안 그룹 : 인바운드 22 포트는 “Infra 담당자, 개발 담당자” IP만 허용 

      → 백엔드 보안 그룹 : 인바운드 22 포트는 “Infra  담당자, 개발  담당자” IP만 허용, 5000 포트는 프론트 사설IP만 허용

      → 베스천 호스트 보안 그룹 :  인바운드 22, 3389 포트는 “Infra 담당자, 개발  담당자” IP만 허용

프론트 엔드 보안 그룹
백엔드 보안 그룹
베스천 호스트 보안 그룹

 

4. 운영

    - WAF 설정

    - Route 53을 이용해 "취지직.com" 도메인 생성 

    - KMS 사용해서 EBS, 볼륨 암호화 설정, 로그 암호화

    - Cloud Watch, Cloud Trail 서비스 사용 

    - VPC 플로우 로그 설정

    - Lambda 사용해서 수집한 로그 S3에 백업 관리

최종 클라우드 아키텍처

 

'rookies > 최종 프로젝트' 카테고리의 다른 글

최종 프로젝트 - 서버 설정  (0) 2024.03.20
최종 프로젝트 - 인프라 구성  (0) 2024.03.20

1. 윈도우에 Front-End 올리기

    ① final4-public-frontend EC2에 RDP 클라이언트를 생성 

    ② 원격 데스크톱 이용해서 접속

    ③ Nodejs

    ④ Git에 작성된 취지직 쇼핑몰 프로젝트 및 패키지 설치

    ⑤ 해당 폴더에서 cmd 접속

         - npm install 

         - npx next bulid (소스 코드 빌드)

         - npx next start (소스코드 실행)

    ⑥ nginx.org/en/download.html에서 Nginx 설치

    ⑦ 설치된 파일에서 nginx.exe 클릭해서 nginx 실행

 

2. 우분투에 Back-End 올리기

    ① Bitvise에 client key manager 이용해서 키 import

    ② Bastion Host에 ssh 접속

    ③ Bastion Host로 SFTP 이용해서 .pem키 업로드 

    ④ 업로드된 키의 권한 600으로 수정

          - chmod 600 [key 이름].pem

    ⑤ ssh -i [key이름].pem ubuntu@[프라이빗 IP] 입력하여 Back-End 인스턴스로 접속

    ⑥ 파이썬 설치 (버전11) 

         - install software-properties-common : 써드파티 벤더 sw app을 관리하는데 도움이 되는 스크립트 제공

         - sudo add-apt-repository ppa:deadsnakes/ppa : python 최신버전 제공 repository 다운

         - sudo apt install python3.11

         - sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.11 1 : 심볼릭 링크 생성

         - sudo update-alternatives --config python3 : python3 명령어 입력시 파이썬 3.11버전으로 실행되도록 변경

         - sudo apt-get install python3-pip : pip 다운로드

    ⑦ Git 설치 및 clone

         - sudo apt-get install git-core

         - git clone https://github.com/CHWI-JI-JIG/Backend.git [소스코드가 있는 git의 url]

    ⑧ requirements 설치

         - cd Backend 해서 ls 해서 requirements.txt가 있는지 확인

         - 있다면 pip install -r requirements.txt (모듈이 없을 경우 pip install 이용해서 다운로드)

    ⑨ mysql_config.py 파일 생성 -> 자신에 맞게 아래 내용 작성

         - nano(vi) mysql_config.py

         - mysql_db = {
                "user": "",
                "password": "",
                 "host": "",
                "port": "3306",
                "database": "",
                 "charset": "",
            }

    ⑩ MySQL 설치 & 설정 

        - sudo apt install mysql-server : 설치 
        - sudo ufw allow mysql : 포트 설정 

        - sudo systemctl start mysql : mysql 실행

        - mysql -u root -p : mysql 접속

    ⑪ Flask 설치

        - sudo pip install flask

    ⑫ Flask 실행 

         - python3 manage.py --host 0.0.0.0 --port 5000

 

3. Front-End와 Back-End 연동 (reverse proxy 이용)

    ① 윈도우에서 nginx가 설치된 폴더에서 conf 파일 밑에 있는 nginx.conf 파일에 아래의 코드를 추가

# connect nginx - next.js | Flask
server {
listen 80;
server_name [frontend서버 퍼블릭IP];
location /api/ {
# API 호출시 Backend Flask 서버로 요청
proxy_pass http://[backend_private_ip]:[Flask port(5000)];
proxy_http_version 1.1;
#proxy_set_header Upgrade $http_upgrade;
#proxy_set_header Connection "upgrade";
#proxy_set_header Host $host;
#proxy_set_header X-Real-IP $remote_addr;
#proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#proxy_redirect off;
}
location / {
# Next.js Frontend 서버로 요청
proxy_pass http://localhost:3000; # next.js 서버 실행주소
proxy_http_version 1.1;
}
}

    ② 윈도우 서버 내 방화벽 인바운드 규칙 80번 port 허용

         - win > 고급 보안이 포함된 Windows Defender 검색 후 열기

         - 인바운드 규칙 클릭하면 옆에 작업칸에 새 규칙 선택

            → 규칙 종류: 포트
                 프로토콜 및 포트: TCP | 특정 로컬 포트 80
                 작업: 연결 허용
                 프로필: (기본) 도메인, 개인, 공용 전체 체크
                 이름: Connect 80 port

    ③ next.js 애플리케이션 코드 중 요청 url을 config > apiConfig에서 export const API_BASE_URL = 'http://[Frontend IP]'

    ④ Front-End의 IP로 접속하여 로그인 등의 API 요청 동작을 확인   

+ Recent posts