Next.js - React Foundations
reference: https://nextjs.org/learn/react-foundations올해가 얼마 남지 않았습니다. 연말이기도 하고 면접도 다니다 보니 공부를 조금 소홀히 한 것 같습니다. 물론 방향성에 대한 고민도 있었지만, 그렇다고 설렁설렁할 수는
0. Intro 🎯
reference: https://nextjs.org/learn/react-foundations
올해가 얼마 남지 않았습니다. 연말이기도 하고 면접도 다니다 보니 공부를 조금 소홀히 한 것 같습니다. 물론 방향성에 대한 고민도 있었지만, 그렇다고 설렁설렁할 수는 없죠.
남은 기간 동안 무엇을 공부하면 좋을까 고민을 해봤는데요, 늘 미루어왔던 Next.js를 공부할까 합니다.
당장 새로운 프로젝트를 개발하진 않고, 공식 문서를 저만의 언어로 정리하고자 합니다. 제가 느끼기에 핵심이라고 생각되는 부분을 중점으로 정리할 것이기 때문에, 세부적인 내용에 갈증을 느끼신다면 공식 문서를 참고하시길 바랍니다.
새로운 프로젝트는 새로운 해에 시작하겠습니다. 아마도 Next.js로 풀스택 개인 기술 블로그를 개발할 것 같습니다.
React Foundations 파트를 통해 React의 핵심이 무엇이었는지 복습하고, Next.js와는 어떻게 연결되는지 학습하고자 합니다. 이후에는 Pages Router, App Router, SEO 순으로 정리할 생각입니다. 시작해 보시죠.
1. About React and Next.js 🎯
1-1. Building blocks of a web application ✅
현대적인 웹 애플리케이션을 구축할 때에는 어떤 요소들을 고려해야 할까요?
User Interface: 사용자가 애플리케이션과 상호작용하는 방식에 대한 고려Routing: 사용자가 서로 다른 페이지를 이동하는 방식에 대한 고려Data Fetching: 데이터를 가져오는 방식에 대한 고려Rendering: 정적 또는 동적 콘텐츠를 렌더링하는 방식에 대한 고려Integrations: 서드파티 서비스를 연결하는 방식에 대한 고려Infrastructure: 코드의 배포, 저장, 실행에 대한 고려Performance: 애플리케이션 최적화에 대한 고려Scalability: 팀, 데이터, 트래픽 확장 시 적응에 대한 고려Developer Experience: 애플리케이션 개발 팀의 경험에 대한 고려
고려할 요소가 무쟈게 많죠? 모던 웹 개발자가 되고 싶다? 이 정도는 기본으로 해야 합니다. 행복해 죽겠습니다.
위와 같은 요소들에 대해 솔루션을 직접 구축할지, 패키지나 라이브러리 그리고 프레임워크를 사용할지 매 순간 근거를 갖고 결정해야 합니다.
1-2. What is React? ✅
React는 앞서 언급한 요소 중 User Interface를 구축하기 위해 사용하는 JavaScript 라이브러리입니다. UI 구축에 필요한 API는 제공하지만, 이러한 기능을 애플리케이션 어느 부분에서 어떻게 활용할지에 대한 판단을 개발자에게 유보하기 때문에 React를 프레임워크가 아닌 라이브러리로 규정하곤 합니다.

모든 선택에는 트레이드오프가 존재하죠. React는 UI를 제외한 나머지 애플리케이션 구성 요소들에 대한 선택권을 개발자에게 위임했기에 자유도가 높다는 장점이 있습니다. 이러한 자유도를 기반으로 많은 서드파티 도구와 패키지들이 개발될 수 있었죠. 하지만 개발자들은 수많은 애플리케이션 구성 요소들에 대응하기 위해 일정한 근거를 토대로 도구를 선정하고, 이러한 도구들을 별도로 학습하기 위한 시간을 할애해야만 합니다. 이게 보통 일이 아닙니다. 진짜로.
1-3. What is Next.js? ✅
Next.js는 풀스택 웹 애플리케이션을 구축하기 위해 필요한 구성 요소들을 제공하는 React 프레임워크입니다.
개발자는 여전히 React를 사용하여 UI를 구성합니다. 다만 Next.js는 React 애플리케이션을 실제 서비스로 운영하기 위해 반복적으로 고민하게 되는 문제들(라우팅, 데이터 페칭, 렌더링 전략, 빌드와 배포 등)에 대한 해답을 프레임워크로서 제공합니다.

React는 UI에 집중한 라이브러리이기 때문에 나머지 요소에 대한 선택은 전적으로 개발자의 몫이었습니다. 어떤 라우터를 쓸지, 서버 사이드 렌더링은 어떻게 할지, SEO는 어떻게 챙길지, 빌드는 어디서 어떻게 할지까지 말이죠. Next.js는 이러한 결정을 프레임워크 레벨에서 미리 정리해두고, 개발자가 애플리케이션의 본질적인 로직과 사용자 경험에 더 집중할 수 있도록 돕는 역할을 합니다.
2. Rendering UI 🎯
2-1. What is Rendering? ✅
버튼이나 모달과 같은 UI를 렌더링 한다는 것은 어떤 의미일까요?

사용자가 웹 페이지에 방문하면, 서버는 크롬과 같은 브라우저에 위와 같은 형태의 HTML 파일을 반환합니다. 이후 브라우저는 HTML을 읽고 Document Object Model(이하 DOM)을 구축합니다.
2-2. What is DOM? ✅
DOM은 HTML 요소들을 객체로 표현한 것입니다. 부모와 자식 관계를 가진 트리 구조를 형성하게 되죠.

개발자는 이러한 DOM을 메서드와 JavaScript를 통해 조작할 수 있게 됩니다. 조작에는 이벤트 감지, UI 특정 요소의 선택, 추가, 수정, 삭제 등의 작업이 포함됩니다. DOM 조작을 통해 특정 요소를 타겟팅하는 것을 넘어 해당 요소의 스타일과 내용 또한 변경할 수 있겠죠.
3. Updating UI with JavaScript 🎯
3-1. HTML vs DOM ✅
HTML과 DOM의 핵심적인 차이는 무엇일까요?
HTML은 애플리케이션의 초기 구조를 정의하는 정적인 문서이고, DOM은 브라우저가 HTML을 해석한 뒤 만들어 낸 객체 모델입니다. JavaScript는 HTML을 직접 수정하지 않고 DOM을 변경하며, 개발자 도구에서 보이는 결과는 HTML 소스 코드와 달라질 수 있다는 점이 핵심입니다.
아래와 같은 index.html 파일이 있습니다.
<html>
<body>
<div></div>
</body>
</html>"Develop. Preview. Ship."이라는 문구가 나오는 제목을 추가해 보겠습니다.
<html>
<body>
<div id="app"></div>
<script type="text/javascript">
// Select the div element with 'app' id
const app = document.getElementById('app');
// Create a new H1 element
const header = document.createElement('h1');
// Create a new text node for the H1 element
const text = 'Develop. Preview. Ship.';
const headerContent = document.createTextNode(text);
// Append the text to the H1 element
header.appendChild(headerContent);
// Place the H1 element inside the div
app.appendChild(header);
</script>
</body>
</html>우리가 제목을 추가했는데 DOM만 수정되고 HTML은 수정되지 않았습니다.
HTML은 정적인 원본 데이터입니다. 서버에서 내려주는 초기 문서이고, 브라우저는 HTML을 읽고 해석한 뒤 DOM으로 변환하죠. 반면 DOM은 브라우저 내부의 실행 모델입니다. JavaScript가 접근하는 대상은 HTML이 아니라 DOM이고, DOM은 수시로 변경되는 현재 상태라고 볼 수 있습니다.
만약 JavaScript가 HTML 파일을 직접 수정하면 어떻게 될까요? h1 요소 하나만 추가되었을 뿐인데 브라우저가 HTML 파일을 다시 파싱하고, 다시 DOM을 생성해야 하는 비효율이 발생할 것입니다.

그래서 브라우저는 HTML을 변경 가능한 대상으로 두지 않고, DOM이라는 중간 표현 계층을 두었습니다. JavaScript는 HTML이 아닌 DOM을 수정하고, 브라우저는 변경된 DOM을 기준으로 필요한 부분만 다시 렌더링 합니다.
다만 여전히 한계는 존재합니다. DOM은 여전히 브라우저 내부의 실제 객체 구조이기 때문에, 잦은 DOM 조작은 성능 저하로 이어질 수 있고, UI 상태가 복잡해질수록 “어떤 변경이 어떤 DOM 조작으로 이어졌는지”를 추적하기 어려워집니다.
이러한 문제를 해결하기 위해 등장한 개념이 Virtual DOM입니다. Virtual DOM은 실제 DOM을 직접 조작하기 전에, 변경 사항을 메모리 상에서 한 번 더 계산하고 비교하는 중간 단계입니다. 이를 통해 불필요한 DOM 조작을 줄이고, UI 변경을 보다 예측 가능하게 관리할 수 있게 됩니다.
이제부터는 DOM을 직접 조작하는 방식이 아닌, UI를 선언적으로 표현하는 방식에 대해 살펴보겠습니다.
3-2. Imperative vs Declarative Programming ✅
Imperative Programming은 명령형 프로그래밍입니다. 컴퓨터에게 명령할 때 어떻게(how) 문제를 해결할지를 전달합니다. 원하는 결과를 얻기 위한 구체적인 절차와 명령을 명시적으로 작성하게 됩니다. 우리가 이미 봤던 아래의 코드는 Imperative Programming 방식으로 작성된 코드라고 할 수 있습니다.
<script type="text/javascript">
const app = document.getElementById('app');
const header = document.createElement('h1');
const text = 'Develop. Preview. Ship.';
const headerContent = document.createTextNode(text);
header.appendChild(headerContent);
app.appendChild(header);
</script>Declarative Programming은 선언형 프로그래밍입니다. 컴퓨터에게 명령할 때 무엇(what)을 원하는지만 결과 중심으로 기술합니다. appendChild나 createTextNode와 같은 how에 해당하는 DOM 조작 명령이 들어가지 않습니다. React는 Declarative Programming 방식을 따르는 라이브러리입니다.
<h1>Develop. Preview. Ship.</h1>4. React core concepts 🎯
React의 핵심 개념이 무엇이냐고 물어보면 자다가도 "Components, Props, States입니다."라고 말할 수 있어야 합니다. 모던 웹 개발자라면요.
4-1. Components ✅
컴포넌트는 재사용을 위한 개념입니다. 컴포넌트는 레고 블록으로 비유할 수 있습니다. 개별 레고 블록으로 결합을 통해 더 큰 구조물을 쉽게 만들 수 있겠죠. 레고 블록이 없다면 매번 레고 블록에 해당하는 코드를 작성해야 하기 때문에 매우 비효율적이겠죠.

소프트웨어는 결합도가 낮고 응집도가 높을 때 품질이 좋다고 할 수 있습니다. 독립적이면서도 다른 요소들과의 의존관계는 낮은 상태를 의미합니다. 결국 컴포넌트는 Modularity에 대한 이야기입니다. 애플리케이션 규모가 커지더라도 코드를 더 쉽게 유지 및 관리할 수 있도록 하는 개념입니다.
컴포넌트를 개발하는 과정은 따로 작성하지 않겠습니다.
4-2. Props ✅
Props는 컴포넌트와 컴포넌트를 연결하는 파이프입니다. 부모 컴포넌트에서 발생하거나 결정된 값은 Props라는 파이프를 통해 자식 컴포넌트로 흘러갈 수 있습니다. 자식 컴포넌트는 그 흐름을 되돌리거나 막을 수 없습니다. 즉 단방향입니다.

컴포넌트를 레고 블록이라고 했죠? 버튼을 하나 만들어서 홈페이지에도 적용하고 마이페이지에서도 재활용할 생각입니다. 그런데 버튼에 variant와 disabled라는 파이프를 꽂아놓고, 원하는 속성을 흘려보내고 싶습니다. 위 내용을 코드로 구현하면 다음과 같습니다.
function Button({ variant, disabled }) {
return (
<button
disabled={disabled}
style={{
padding: "12px 32px",
borderRadius: "8px",
border: variant === "secondary" ? "1px solid #555" : "none",
background:
disabled
? "#333"
: variant === "primary"
? "#0B3C7A"
: "#1c1c1c",
color: disabled ? "#777" : "#fff",
cursor: disabled ? "not-allowed" : "pointer",
}}
>
Button
</button>
);
}
export default function App() {
return (
<div style={{ display: "flex", gap: "24px" }}>
<Button variant="primary" />
<Button variant="secondary" />
<Button disabled />
</div>
);
}4-3. States ✅
React의 Props가 파이프라면, State는 컴포넌트 내부에 고인 저수지입니다.
State는 상태인데요, 사용자가 폼에 입력하는 내용이나 좋아요 클릭 수 등 사용자의 대부분의 UI 상 의사 결정이 State에 반영됩니다. 토글 버튼을 생각해 볼까요?

토글 버튼을 자주 보셨을 겁니다. State 관점에서 보면 토글 버튼은 boolean 타입의 값을 상태로 갖고, true 또는 false 일 수 있습니다. 이제 코드를 보시죠.
import { useState } from "react";
function Toggle() {
const [on, setOn] = useState(false);
return (
<button onClick={() => setOn(!on)}>
{on ? "ON" : "OFF"}
</button>
);
}
export default Toggle;사용자가 클릭하면 false는 true로, true는 false로 변경됨을 알 수 있습니다. on이 저수지이고 setOn은 저수지 물을 업데이트하는 함수입니다. setOn에 콜라를 전달하면 on은 콜라를 저장하고 있는 저수지가 되겠네요.
아래는 ToggleButton 컴포넌트에 Props라는 파이프로 on에 담긴 콜라를 내려보내는 간단한 코드입니다.
import { useState } from "react";
function ToggleButton({ on, onToggle }) {
return (
<button onClick={onToggle}>
{on ? "ON" : "OFF"}
</button>
);
}
export default function App() {
const [on, setOn] = useState(false);
return (
<ToggleButton
on={on}
onToggle={() => setOn(!on)}
/>
);
}5. From React to Next.js 🎯
Next.js를 이해하기 위해서는 서버 컴포넌트와 클라이언트 컴포넌트의 동작 방식에 대해 간단히 익혀야 합니다. Environments와 Network Boundary를 이해하는 것이 핵심입니다.
5-1. Server and Client Environments ✅
일반적으로 환경이라고 하면 Client와 Server로 구분됩니다.
Client는 사용자 기기에 있는 브라우저를 의미합니다. 서버에 애플리케이션 구성에 필요한 코드를 요청하는 역할을 하죠. 이후 서버로부터 받은 응답을 사용자가 상호작용할 수 있는 인터페이스로 변환합니다.
Server는 애플리케이션 구성에 필요한 코드를 저장하고 있는 데이터 센터의 컴퓨터를 의미합니다. Client로부터 요청을 받아 로직을 처리한 뒤 적절한 응답을 다시 반환합니다.

Client와 Server는 고유한 기능과 제약 사항이 있습니다. 가령, Rendering과 Data Fetching을 Server가 전담하게 하면, 브라우저는 그만큼 무거운 코드를 다운로드하고 실행할 필요가 없어집니다. 결과적으로 사용자는 웹사이트가 훨씬 더 빠르다고 느끼게 됩니다.
하지만 Server는 사용자의 화면을 직접 만질 수 없습니다. 버튼을 눌렀을 때 메뉴가 나타나거나, 입력창에 글자를 쓰는 것 같은 Interaction을 만들려면 결국 Client가 직접 자기 앞에 있는 화면(DOM)을 수정해야 합니다.
이렇듯 Server와 Client는 각기 다른 실행 특성을 지니며, 수행하려는 작업의 성격에 따라 더 효율적으로 처리할 수 있는 최적의 환경이 존재합니다.
5-2. Network Boundary ✅
Network Boundary는 서로 다른 환경을 가르는 개념적인 선입니다.
React에서는 컴포넌트 트리의 어느 지점에 Network Boundary를 세울지 직접 선택할 수 있습니다. 예를 들어, 사용자의 게시물 데이터를 가져와 화면에 그리는 작업은 서버에서 처리하고, 각 게시물에 있는 상호작용 요소인 '좋아요' 버튼은 클라이언트에서 렌더링하도록 구성할 수 있겠죠.
여러 페이지에서 공통으로 사용하는 내비게이션 컴포넌트는 서버에서 렌더링 하되, 현재 클릭된 링크를 강조하는 active state를 보여줘야 한다면 그 링크 목록 부분만 클라이언트에서 렌더링하도록 설정할 수 있습니다.

이미지에 나타난 Layout, Nav, Page, Posts는 서버 컴포넌트입니다. 반면 파란색 테두리로 감싸진 Links와 LikeButton은 클라이언트 컴포넌트입니다. React는 빌드 시점에 위 이미지와 같은 트리를 보고 서버용 설계도와 클라이언트용 설계도 두 가지로 구분합니다.
서버가 작업을 마치고 클라이언트로 정보를 보낼 때, HTML만 보내는 것이 아니라 RSC(React Server Component) Payload라는 명세도 함께 보냅니다. RSC Payload에는 서버 컴포넌트의 결과물과 클라이언트 컴포넌트의 자리가 전달됩니다.
브라우저가 RSC Payload를 받으면 서버 컴포넌트를 먼저 화면에 그립니다. 다음으로 클라이언트 컴포넌트 자리에 클라이언트 컴포넌트를 그립니다. 최종적으로는 서버에서 가져온 데이터와 클라이언트 컴포넌트가 만나 하나의 완성된 화면이 구성됩니다.
6. Outro 🎯
연말은, 앞으로 달라지겠노라고 결심하는 시간보다는 지금까지 달라진 나를 발견하는 시간이 되어야 한다고 생각합니다. 그래야 그 앞에 놓인 긴 시간을 충실히 살아낼 수 있겠죠. 올해가 13일 남았습니다. 끝까지 꾹꾹 눌러 살 생각입니다. 우리네 인생 파이팅입니다.
More to read
Amazon VPC Architecture 이해하기
새로운 프로젝트를 기획하며, 개발에서 무엇을 가장 먼저 고민해야 하는지 다시 돌아보게 되었습니다.한때는 프론트엔드가 모든 설계의 출발점이라고 믿었습니다. 유저가 무엇을 보고, 어떤 흐름에서 머무르고 이탈하는지에 대한 이해 없이 서비스를 만든다는 건 불가능하다고 생각했기
'원사이트'프론트엔드 관점으로 알고리즘 이해하기
오랜만에 방법론에 관한 글을 쓰게 되었습니다. 최근 상황은 이렇습니다. SSAFY에서는 하루에 엄청난 양의 알고리즘 문제들을 과제로 수행하게 됩니다. 그 과정에서, '구현력'이 매우 떨어진다는 생각이 들었습니다. 완전히 어려운 문제라면 '아쉬움'이라는 감정조차 느끼지
SubnetVPC 설계의 시작: IP와 Subnet
반복되는 루틴 속에서 얻은 안정감을 발판 삼아, 이제는 기술적 스펙트럼을 넓히기 위한 개인 프로젝트에 착수하고자 합니다.이번 프로젝트의 목표는 단순한 포트폴리오 구축을 넘어, 실제 서비스 수준의 블로그 시스템 구현과 다국어 처리 적용 등 실무에 가까운 역량을 한 단계