안녕하세요, 우리동네코딩 스튜디오에 오신 것을 환영합니다!
우리동네코딩 스튜디오는 카네기 멜론, 워싱턴, 토론토, 워터루 등 북미의 주요 대학에서 컴퓨터공학을 전공하고, Google, Microsoft, Meta 등 글로벌 IT 기업에서 실무 경험을 쌓은 개발자들이 함께 만든 교육 그룹입니다.
처음에는 미국과 캐나다의 컴퓨터공학 전공자들끼리 함께 공부하며 성장하고자 만든 스터디 모임에서 시작되었습니다. 각기 다른 대학, 다른 시간대에 있었지만 함께 문제를 해결하고 서로에게 배운 그 시간은 매우 특별했고, 자연스럽게 이런 생각이 들었습니다.
“우리가 공부하던 이 방식, 그대로 다른 사람에게도 전하면 어떨까?”
그 물음이 바로 우리동네코딩 스튜디오의 출발점이었습니다.
현재는 약 30명의 현직 개발자와 컴퓨터공학 전공 대학생들이 각자의 전문 분야를 맡아, 입문부터 실전까지 아우르는 커리큘럼을 직접 설계하고 강의합니다. 단순한 지식 전달을 넘어, 진짜 개발자의 시선으로 배우고 함께 성장할 수 있는 환경을 제공합니다.
“진짜 개발자는, 진짜 개발자에게 배워야 합니다.”
저희는 웹 개발의 전 과정을 처음부터 끝까지 체계적으로 다루되, 이론에 머무르지 않고 실습과 실전 중심의 피드백을 통해 실력을 키워드립니다.
수강생 한 사람, 한 사람의 성장을 함께 고민하고 이끌어가는 것이 우리의 철학입니다.
🎯 우리의 철학은 분명합니다.
"진정한 배움은 실천에서 오고, 성장은 함께할 때 완성된다."
개발을 처음 시작하는 입문자부터, 실무 능력을 키우고 싶은 취업 준비생, 진로를 탐색 중인 청소년까지.
우리동네코딩 스튜디오는 모두의 출발점이자, 함께 걷는 든든한 동반자가 되고자 합니다.
이제, 혼자 고민하지 마세요.
우리동네코딩 스튜디오가 여러분의 성장을 함께하겠습니다.
Welcome to Neighborhood Coding Studio!
Neighborhood Coding Studio was founded by a team of developers who studied computer science at top North American universities such as Carnegie Mellon, the University of Washington, the University of Toronto, and the University of Waterloo, and went on to gain hands-on experience at global tech companies like Google, Microsoft, and Meta.
It all began as a study group formed by computer science students across the U.S. and Canada, created to grow together by sharing knowledge, solving problems, and learning from one another.
Though we were attending different schools in different time zones, the experience was so meaningful that it led us to one simple thought:
“What if we shared this way of learning with others?”
That thought became the foundation of Neighborhood Coding Studio.
Today, we are a team of around 30 active developers and computer science students, each taking responsibility for their area of expertise—designing and delivering a curriculum that spans from foundational knowledge to real-world development.
We’re not just here to teach—we’re here to help you see through the lens of real developers and grow together.
“To become a real developer, you must learn from real developers.”
Our courses take you through the entire web development journey—from start to finish—focused on hands-on practice, real-world projects, and practical feedback.
We care deeply about each learner’s growth and are committed to supporting your path every step of the way.
🎯 Our philosophy is simple but powerful:
"True learning comes from doing, and true growth happens together."
Whether you're just getting started, preparing for your first job, or exploring your future in tech,
Neighborhood Coding Studio is here to be your launchpad—and your trusted companion on the journey.
You don’t have to do it alone.
Let Neighborhood Coding Studio walk with you toward your future in development.
講義
受講レビュー
- 「AIポチりの時代」殻を破りシステムを支配する:Node.jsの境界を崩すUNIX編
- 「AIクリックの時代」避けられないのなら、立ち向かえ:システムを守る暗号学とセキュリティ
- 「AIポチポチの時代」原理で突破するNode.jsとCS Part1 - V8とコア解体新書
- 「AIクリックの時代」避けられないのなら、立ち向かえ:システムを守る暗号学とセキュリティ
- 「AIポチりの時代」殻を破りシステムを支配する:Node.jsの境界を崩すUNIX編
投稿
Q&A
Composition단계에서 `Layer`는 누가 만드는지
안녕하세요 krak님! 이 부분은 과거에도 비슷한 질문을 주신 분이 있어, 당시 브라우저의 실제 렌더링 구조와 실무에서 어디까지 알아두면 좋은지까지 상세하게 정리해 둔 자료가 있습니다. 해당 내용을 바탕으로 이번 질문에 맞게 조금 더 쉽게 풀어서 설명드리겠습니다.질문하신 내용을 먼저 짧게 답하면, Composition 단계에서 여러 Layer가 갑자기 만들어지는 것은 아닙니다. 그리고 렌더 트리 여러 개가 각각 Layer가 되는 것도 아닙니다.현재 Chromium 계열 브라우저를 기준으로 조금 더 정확하게 보면, Layout 이후 Pre-Paint와 Paint를 거치면서 브라우저가 화면을 그리기 위한 정보를 만들고, 그 결과를 Paint Chunk라는 단위로 정리합니다. 이후 이 Paint Chunk들을 바탕으로 어떤 내용을 별도의 composited layer로 관리할지를 결정하는 layerization 과정이 진행됩니다. 그다음 Rasterization을 거쳐 실제로 화면에 그릴 수 있는 픽셀 데이터가 준비되고, 마지막으로 여러 결과를 합성해서 화면에 출력합니다. Chromium의 현재 구현 문서에서도 Paint 과정에서 만들어진 Paint Chunk들을 PaintArtifactCompositor가 처리하여 compositor가 사용할 cc::Layer들을 만드는 구조로 설명하고 있습니다.쉽게 전체 흐름부터 잡고 시작하면 다음과 같습니다.HTML + CSS ↓ DOM / Style ↓ Layout ↓ Pre-Paint ↓ Paint ↓ Display Items ↓ Paint Chunks ↓ Layerization ↓ Composited Layers ↓ Rasterization ↓ Composition ↓ 화면 출력강의에서는 보통 이 중간 과정들을 모두 설명하면 처음 배우실 때 오히려 전체 흐름을 놓치기 쉬워서 다음처럼 많이 단순화합니다.Layout ↓ Paint ↓ Composite그래서 질문하신 것처럼 “Paint까지 Layer 이야기가 없다가 왜 Composite에서 갑자기 Layer가 여러 개 나오지?”라는 의문이 자연스럽게 생길 수 있습니다.실제로는 그 사이에 몇 가지 중요한 과정이 숨어 있습니다.1. Layout에서는 Layer를 만드는 것이 아니라 위치와 크기를 계산합니다우선 이런 HTML이 있다고 해보겠습니다. Header Hello Content Modal 브라우저가 HTML과 CSS를 해석하고 Layout을 수행하면 각 요소가 화면의 어디에 위치하고, 얼마나 큰지를 계산합니다.개념적으로 보면 다음과 같습니다.body ├─ header ├─ main │ ├─ h1 │ └─ p └─ modal여기서 브라우저가 계산하는 것은 대략 이런 정보입니다.header → x: 0 → y: 0 → width: 1200px → height: 80px main → x: 0 → y: 80px → width: 1200px → height: 1000px modal → x: 400px → y: 200px → width: 400px → height: 300px물론 실제 내부 구조는 훨씬 복잡하지만, Layout을 이해할 때는 “각 요소가 어디에 있고 얼마나 큰지를 결정한다”고 생각하시면 충분합니다.여기서 첫 번째로 꼭 구분해야 할 것이 있습니다.DOM 요소 하나 = Layer 하나가 아닙니다.header가 있으니까 Header Layer가 생기고, main이 있으니까 Main Layer가 생기고, modal이 있으니까 Modal Layer가 생기는 식으로 동작하는 것이 아닙니다.DOM 요소가 수백 개여도 compositor layer는 훨씬 적을 수 있고, 반대로 특정 상황에서는 일부 콘텐츠가 별도의 layer로 분리될 수도 있습니다.실무에서는 이 차이를 알아두는 것이 중요합니다. 예를 들어 React로 카드 1,000개를 렌더링했다고 해서 “브라우저에 Layer가 1,000개 생겼겠구나”라고 생각하면 안 됩니다. DOM 개수와 compositor layer 개수는 일대일 관계가 아닙니다.2. Layout이 끝났다고 바로 Paint하는 것도 아닙니다Layout으로 위치와 크기를 계산했다고 해도 실제 화면을 그리기 위해서는 추가 정보가 필요합니다.예를 들어 modal에 다음 CSS가 있다고 해보겠습니다..modal { position: fixed; opacity: 0.9; transform: translateX(20px); overflow: hidden; }Layout만 알고 있다고 해서 이 요소를 어떻게 그릴지 완전히 결정할 수 있는 것은 아닙니다.브라우저 입장에서는 다음과 같은 정보도 필요합니다.이 요소는 transform되어 있는가? 어떤 영역에서 잘려야 하는가? opacity가 적용되는가? 다른 요소보다 앞에 그려야 하는가? 어떤 좌표계를 기준으로 그릴 것인가?이와 관련된 준비 작업이 Layout과 Paint 사이의 Pre-Paint 단계에서 이루어집니다.현재 Chromium에서는 Pre-Paint 과정에서 paint invalidation을 처리하고 transform, clip, effect 등의 상태를 표현하기 위한 paint property tree를 준비합니다.처음 공부하실 때는 내부 자료구조 이름까지 외우실 필요는 없습니다.다만Layout → 위치와 크기 결정 Pre-Paint → 실제 Paint를 하기 위해 필요한 상태 정리정도로만 구분해두시면 이후 내용이 훨씬 이해하기 쉬워집니다.실무 예시를 하나 들어보겠습니다.사용자가 버튼을 눌러서 어떤 요소의 width를 변경했다고 해보겠습니다.box.style.width = "500px";width가 달라지면 주변 요소의 위치에도 영향을 줄 수 있으므로 Layout을 다시 계산해야 할 수 있습니다.반대로 단순히 어떤 요소의 색상만 바꾼다면box.style.backgroundColor = "red";위치와 크기가 바뀐 것이 아니기 때문에 Layout 자체는 필요하지 않을 가능성이 높고 Paint 쪽 변경이 중심이 됩니다.이런 식으로 “내가 변경한 CSS 속성이 렌더링 파이프라인의 어디까지 영향을 주는가?”를 생각하는 것이 실무 성능 분석에서 상당히 중요합니다.3. Paint는 실제 픽셀을 바로 칠하는 단계라고 생각하면 조금 부정확합니다Paint라는 단어 때문에 가장 많이 생기는 오해 중 하나입니다.처음 배우면 보통 다음처럼 생각하기 쉽습니다.Paint = 모니터에 픽셀을 직접 칠한다하지만 현대 Chromium의 렌더링 구조를 이해할 때는 Paint를무엇을 어떤 순서로 어떻게 그릴 것인지 기록하는 과정이라고 생각하는 편이 좋습니다.예를 들어 앞의 HTML이 있다면 개념적으로 다음과 같은 그리기 작업들이 필요할 수 있습니다.header의 배경을 그린다 header의 글자를 그린다 main의 배경을 그린다 h1의 글자를 그린다 p의 글자를 그린다 modal의 배경을 그린다 modal의 글자를 그린다Chromium에서는 이런 그리기 작업을 표현하는 단위 중 하나가 Display Item입니다.아주 단순화하면 다음과 같습니다.Display Item 1 → Header 배경 Display Item 2 → Header 텍스트 Display Item 3 → Main 배경 Display Item 4 → h1 텍스트 Display Item 5 → p 텍스트 Display Item 6 → Modal 배경 Display Item 7 → Modal 텍스트현재 Chromium의 Paint 구현도 Layout 결과를 순회하면서 display item list를 만들고 이를 이후 compositor에서 사용할 수 있는 형태로 변환합니다.이 부분은 실무에서도 알아두면 좋습니다.예를 들어 버튼의 색상 하나만 바꿨다고 해보겠습니다.button:hover { background-color: tomato; }버튼의 크기나 위치는 그대로입니다.따라서 일반적으로 Layout을 다시 할 이유는 없지만, 버튼의 모양이 바뀌었기 때문에 Paint 결과는 변경될 수 있습니다.즉,width 변경 → Layout까지 영향을 줄 수 있음 background-color 변경 → 주로 Paint와 관련 transform 변경 → 조건이 맞으면 compositor에서 처리 가능처럼 어떤 CSS 속성을 변경했느냐에 따라 필요한 작업이 달라질 수 있습니다.4. Display Item들이 Paint Chunk로 묶입니다그렇다면 Display Item 하나가 Layer 하나일까요?이것도 아닙니다.Display Item 하나 = Layer 하나라고 생각하시면 안 됩니다.Display Item은 너무 세부적인 단위이기 때문입니다.브라우저는 동일한 paint property 상태를 공유하는 인접한 Display Item들을 Paint Chunk라는 단위로 묶습니다.예를 들어 개념적으로 다음과 같이 될 수 있습니다.Paint Chunk A ├─ Header 배경 └─ Header 텍스트 Paint Chunk B ├─ Main 배경 ├─ h1 텍스트 └─ p 텍스트 Paint Chunk C ├─ Modal 배경 └─ Modal 텍스트현재 Chromium 문서에서도 같은 property tree state를 공유하는 인접한 Display Item들을 Paint Chunk로 그룹화한다고 설명합니다.여기서 또 중요한 오해가 하나 있습니다.Paint Chunk 하나 = Layer 하나도 아닙니다.Paint Chunk는 이후 layerization에서 판단에 사용되는 중요한 단위이지만, 여러 Paint Chunk가 하나의 compositor layer로 합쳐질 수도 있습니다.5. 이제 여기서 실제로 Layerization이 이루어집니다질문하신 “그 여러 Layer는 누가 만드는 건가요?”의 핵심이 바로 이 부분입니다.Paint 결과로 Paint Chunk들이 만들어지면 Chromium의 PaintArtifactCompositor가 그 결과를 받아 compositor가 사용할 layer 구조로 변환합니다.그리고 Chromium에서는 이 과정을 layerization이라고 부릅니다. 현재 구현에서는 Paint Chunk들을 바탕으로 우선 PendingLayer를 구성하고, 서로 합칠 수 있는 것들은 합친 뒤 최종 cc::Layer들을 만듭니다. 이 과정에서는 GPU 메모리 사용량과 콘텐츠 변경 시 발생하는 비용 등의 trade-off도 고려됩니다.앞의 예제로 돌아가보겠습니다.Paint 결과가 이렇게 만들어졌다고 가정해보겠습니다.Paint Chunk A → Header Paint Chunk B → Main Paint Chunk C → Modal브라우저가 Header와 Main은 하나로 관리해도 괜찮다고 판단하고 Modal은 독립적으로 관리할 이유가 있다고 판단했다면 개념적으로는 다음처럼 될 수 있습니다.Compositor Layer 1 ├─ Paint Chunk A └─ Paint Chunk B Compositor Layer 2 └─ Paint Chunk C즉 Paint Chunk가 세 개라고 Layer도 반드시 세 개가 되는 것이 아닙니다.브라우저가 필요에 따라 묶거나 나눕니다.전체 관계를 아주 간단하게 표현하면 이렇게 생각하시면 좋습니다.여러 DOM 요소 ↓ 여러 LayoutObject ↓ 여러 Display Item ↓ 여러 Paint Chunk ↓ Layerization ↓ 하나 또는 여러 Compositor Layer이것이 질문하신 “Layer가 갑자기 어디서 나오는 것인가?”에 대한 가장 중요한 답입니다.Composition이 시작된 순간 아무것도 없는 상태에서 갑자기 Layer가 생성되는 것이 아니라, 그 이전 Paint 결과를 가지고 layerization을 수행하면서 compositor가 사용할 Layer들이 결정됩니다.6. 그러면 왜 굳이 Layer를 여러 개로 나눌까요?여기서 “그냥 화면 전체를 하나의 큰 이미지로 만들면 안 되나요?”라는 의문이 생길 수 있습니다.가능은 하지만 모든 것을 하나로 처리하면 화면의 일부만 움직여도 더 많은 작업을 다시 해야 할 수 있습니다.예를 들어 페이지에 다음 두 영역이 있다고 해보겠습니다.───────────────────────────── 일반적인 본문 ───────────────────────────── Modal ─────────────────────────────Modal에 다음 애니메이션이 있다고 가정해보겠습니다..modal { transform: translateX(100px); }만약 Modal이 적절하게 독립적인 composited content로 관리되고 있다면, 이미 만들어진 Modal의 결과물을 다시 처음부터 그리지 않고 compositor에서 위치만 변경할 수 있는 경우가 있습니다.개념적으로는 이런 방식입니다.본문 Layer → 그대로 유지 Modal Layer → 기존 그림 그대로 유지 → 위치만 이동 마지막에 두 결과를 다시 합성Photoshop을 생각하시면 이해가 조금 쉽습니다.배경과 사람을 각각 다른 Layer로 만들어 놓았다면 사람의 위치를 옮길 때 배경 그림 전체를 다시 그릴 필요는 없습니다.브라우저의 compositor layer도 완전히 동일한 구조는 아니지만, 처음 개념을 잡기에는 비슷한 비유가 도움이 됩니다.7. 여기서 Rasterization이 등장합니다Paint에서 “무엇을 그릴 것인가”를 기록했다면 실제 픽셀로 만들어야 합니다.이 과정이 Rasterization입니다.아주 쉽게 표현하면 다음과 같습니다.Paint "여기에 사각형을 그려라" "여기에 Hello라는 글자를 그려라" "여기에 이미지를 그려라" ↓ Rasterization 실제로 화면에 표시할 수 있는 픽셀 데이터로 변환따라서 Paint와 Raster는 구분해서 알아두시는 것이 좋습니다.Paint → 무엇을 어떻게 그릴지 기록 Raster → 그 정보를 실제 픽셀 데이터로 변환그리고 여기서도 자주 생기는 오해가 있습니다.Layer 하나 = 거대한 Bitmap 이미지 하나라고 생각하면 정확하지 않습니다.실제로 큰 layer의 콘텐츠는 여러 tile로 나뉘어서 rasterize될 수 있습니다.개념적으로는 다음과 같습니다.하나의 큰 콘텐츠 ┌─────────────┬─────────────┐ │ Tile 1 │ Tile 2 │ ├─────────────┼─────────────┤ │ Tile 3 │ Tile 4 │ ├─────────────┼─────────────┤ │ Tile 5 │ Tile 6 │ └─────────────┴─────────────┘이런 구조 덕분에 매우 긴 페이지 전체를 항상 한꺼번에 거대한 이미지로 만드는 식으로 처리할 필요가 없습니다.8. 마지막 Composition에서는 무엇을 하나요?이제 드디어 질문에서 말씀하신 Composition 단계입니다.이 시점에서는 브라우저가 가지고 있는 여러 렌더링 결과를 조합해서 최종 화면을 만듭니다.개념적으로 보면 다음과 같습니다.Layer A → 일반 페이지 콘텐츠 Layer B → Modal Layer C → 별도의 효과가 적용된 콘텐츠 ↓ Composition ↓ 최종 화면예를 들어 Modal Layer가transform: translateX(100px); opacity: 0.8;를 가지고 있다면 compositor는 준비된 결과를 적절한 위치로 이동시키고 opacity를 반영해서 다른 결과와 합성할 수 있습니다.그래서 Layerization과 Composition은 서로 다른 개념입니다.Layerization은무엇을 어떤 Layer로 나눌 것인가?를 결정하는 과정이고,Rasterization은그 Layer가 그릴 내용을 실제 픽셀로 어떻게 만들 것인가?와 관련된 과정이며,Composition은준비된 여러 결과를 어떻게 조합해서 최종 화면을 만들 것인가?와 관련된 과정입니다.9. 그러면 “렌더 트리 여러 개 = Layer 여러 개”인가요?두 번째 질문에 대해서는 명확하게 아니라고 보시면 됩니다.Render Tree라는 개념과 compositor layer는 역할 자체가 다릅니다.입문 자료에서 이야기하는 Render Tree는 쉽게 말하면페이지에서 실제로 렌더링될 요소들이 어떤 구조를 가지고 있는가?와 관련된 개념입니다.현대 Chromium 내부에서는 이를 단순히 하나의 “Render Tree”라는 표현으로만 설명하기보다는 LayoutObject tree, fragment 구조, PaintLayer, paint property tree, PaintArtifact 등 여러 자료구조가 렌더링 단계별로 사용됩니다. 현재 Chromium의 core rendering 역시 DOM, Style, Layout, Paint를 주요 단계로 두고, core rendering의 결과인 PaintArtifact를 compositor의 layer 목록으로 변환하는 구조입니다.반면 Compositor Layer는최종 렌더링 결과를 어떤 단위로 독립적으로 관리하고 합성할 것인가?와 관련된 구조입니다.예를 들어 페이지 구조가 다음과 같다고 해보겠습니다.body ├─ header ├─ main │ ├─ article │ └─ aside └─ modalLayout 관점에서는 하나의 연결된 페이지 구조입니다.그런데 compositing 결과는 상황에 따라 다음처럼 될 수 있습니다.Layer A ├─ header └─ main Layer B └─ modal따라서Render Tree가 두 개이기 때문에 Layer도 두 개가 된 것이 아닙니다.하나의 문서를 렌더링한 결과 중 일부를 독립적으로 관리하는 것이 유리하다고 브라우저가 판단했기 때문에 여러 compositor layer로 나뉠 수 있는 것입니다.10. PaintLayer와 Compositor Layer도 같은 Layer가 아닙니다여기까지 공부하면 검색하다가 또 다른 Layer라는 단어들을 만나게 됩니다.대표적으로 다음과 같습니다.PaintLayer Compositor Layer cc::Layer GPU Layer Stacking Context이 부분에서 많이 헷갈립니다.특히 Chromium 내부에는 실제로 PaintLayer라는 클래스가 있습니다.그렇다고PaintLayer = Compositor Layer라고 이해하시면 안 됩니다.현재 Chromium 문서에서도 PaintLayer는 Blink의 오래된 내부 구현 세부사항이라고 설명하고 있으며, LayoutObject/PaintLayer 쪽의 Paint 결과를 이후 PaintArtifactCompositor가 처리해 별도의 cc::Layer 목록으로 만드는 구조를 명확히 구분합니다.프론트엔드 개발자 입장에서는 PaintLayer 내부 구현을 세부적으로 외우실 필요는 없습니다.대신브라우저 내부에서 Layer라는 단어가 나왔다고 해서 모두 compositor layer를 의미하는 것은 아니다.이것만 확실하게 기억하시면 됩니다.11. Stacking Context도 Compositor Layer와 다릅니다이 둘도 정말 많이 혼동합니다.예를 들어 다음 CSS가 있다고 해보겠습니다..modal { position: relative; z-index: 9999; }z-index와 stacking context는 주로무엇을 먼저 그릴 것인가? 어떤 요소가 어떤 요소 위에 나타날 것인가?와 관련된 개념입니다.반면 compositor layer는어떤 렌더링 결과를 독립적으로 rasterize하거나 composite할 것인가?와 관련됩니다.따라서Stacking Context ≠ Compositor Layer입니다.이 부분은 실무에서 특히 중요합니다.예를 들어z-index: 999999999;를 줬는데도 Modal이 다른 요소 밑에 있다면, 단순히 “GPU Layer가 이상하다”고 볼 문제가 아닙니다.부모 요소가 새로운 stacking context를 만들고 있는지부터 살펴봐야 하는 경우가 많습니다.반대로 애니메이션이 버벅거린다면 stacking context보다는 Layout, Paint, Rasterization, compositing 비용을 확인하는 것이 더 중요할 수 있습니다.12. 실무에서 가장 많이 연결되는 것이 transform과 opacity입니다Layer와 Composition을 배우는 가장 큰 이유 중 하나가 프론트엔드 성능과 연결되기 때문입니다.예를 들어 어떤 상자를 오른쪽으로 이동시키고 싶다고 해보겠습니다.다음처럼 구현할 수도 있습니다..box { position: relative; left: 300px; }left 값의 변경은 요소의 기하학적 위치에 영향을 주므로 상황에 따라 Layout이 다시 필요할 수 있습니다.그러면 이후 과정까지 영향을 받을 수 있습니다.Style ↓ Layout ↓ Pre-Paint ↓ Paint ↓ Raster ↓ Composite반면 다음처럼 만들 수도 있습니다..box { transform: translateX(300px); }해당 콘텐츠가 적절하게 composited되어 있고 필요한 조건이 충족된다면, 이미 rasterize되어 있는 콘텐츠를 다시 Layout하고 Paint하지 않고 compositor에서 transform 정보만 변경하여 새로운 위치에 합성할 수 있습니다.개념적으로는 다음과 같습니다.이미 만들어져 있는 Box의 결과 ↓ transform 정보 변경 ↓ Composition ↓ 새 위치에 표시이 때문에 실무에서 애니메이션을 만들 때 transform과 opacity가 자주 권장되는 것입니다.하지만 여기서도 하나는 꼭 조심하셔야 합니다.transform 사용 = 무조건 새로운 GPU Layer 생성 = 무조건 빠름이라는 의미는 아닙니다.실제 layerization은 브라우저가 여러 조건을 보고 결정합니다. Chromium의 현재 layerization 알고리즘 역시 각 Paint Chunk를 무조건 독립 Layer로 만드는 것이 아니라, 가능한 경우 PendingLayer들을 합치고 직접적인 compositing reason, overlap, sparsity 등의 조건을 고려해 분리 여부를 판단합니다.따라서 실무에서는 “transform은 무조건 빠르다”보다transform이나 opacity는 상황에 따라 Layout과 Paint를 건너뛰고 compositor 단계에서 처리할 가능성이 높기 때문에 애니메이션에 유리하다.정도로 이해하시는 것이 더 정확합니다.13. will-change도 Layer와 관련해서 꼭 알아둘 만합니다다음과 같은 코드를 본 적이 있으실 수 있습니다..card { will-change: transform; }이는 브라우저에게이 요소의 transform이 곧 변경될 가능성이 높습니다.라는 힌트를 주는 속성입니다.현재 Chromium 구현에서도 will-change: transform은 직접적인 compositing reason으로 사용될 수 있고, layerization 결과에 영향을 줄 수 있습니다.그렇다고 다음처럼 사용하는 것은 좋지 않습니다.* { will-change: transform; }또는 카드가 2,000개 있는데.card { will-change: transform; }을 전부 적용하는 것도 일반적으로 좋은 최적화 방식은 아닙니다.Layer와 관련된 리소스는 공짜가 아닙니다.별도의 compositing 처리가 늘어나면 메모리 사용량이나 관리 비용 또한 증가할 수 있습니다.그래서 실무에서는 먼저 성능을 측정하고, 실제 애니메이션이 자주 발생하며 효과가 있는 요소에 필요한 경우 제한적으로 적용하는 것이 좋습니다.14. 실무에서는 “몇 개의 Layer가 있는가?”보다 “어느 단계가 다시 실행되는가?”가 더 중요합니다실제로 프론트엔드 개발을 하다 보면 “이 요소가 Layer인가 아닌가?”만 외우는 것보다 다음 사고방식이 훨씬 도움이 됩니다.예를 들어 애니메이션이 끊긴다고 해보겠습니다.그때는 먼저 이 CSS 변경이 Layout을 일으키는지를 생각합니다.Layout이 발생한다면 얼마나 많은 요소가 영향을 받는지 확인합니다.그다음 Paint가 매 프레임 발생하고 있는지 확인합니다.Paint가 발생한다면 화면에서 어느 정도의 영역이 다시 그려지고 있는지 확인합니다.그리고 Rasterization 비용이 너무 크지는 않은지, compositor에서 처리 가능한 움직임인데 불필요하게 Paint가 반복되고 있지는 않은지도 살펴봅니다.즉 실무에서는 이런 식으로 생각하시면 됩니다.CSS / JS 변경 ↓ Layout까지 필요한가? ↓ Paint가 필요한가? ↓ Raster를 다시 해야 하는가? ↓ Composite만으로 처리 가능한가?예를 들어 마우스를 따라다니는 tooltip을 매 프레임 left, top으로 움직였는데 성능 문제가 있다면 transform 기반 이동을 검토해볼 수 있습니다.대형 사이드 메뉴가 열릴 때 매 프레임 width를 변경하고 있다면 Layout을 계속 발생시키고 있지는 않은지 확인할 수 있습니다.스크롤 시 특정 요소의 배경 효과 때문에 넓은 영역이 계속 repaint된다면 Paint 비용을 살펴볼 수 있습니다.이렇게 실제 성능 문제를 “Layout 문제인지, Paint 문제인지, compositing 문제인지”로 나누어 보는 것이 Layer 개수를 암기하는 것보다 훨씬 중요합니다.15. 오래된 자료와 최신 자료의 설명이 다른 이유도 알아두시면 좋습니다검색하다 보면 어떤 자료에서는Layout ↓ Layer 생성 ↓ Paint ↓ Composite라고 설명하고,다른 자료에서는Layout ↓ Paint ↓ Layerization ↓ Composite라고 설명하는 것을 볼 수 있습니다.이 부분에서 “둘 중 하나가 틀린 건가?”라고 생각하기 쉽습니다.그런데 Chrome의 렌더링 아키텍처가 실제로 변경되어 왔기 때문에 오래된 자료와 현재 구현을 그대로 비교하면 차이가 생길 수 있습니다.과거 Chromium의 Slimming Paint V1 계열에서는 layerization이 Pre-Paint와 Paint보다 앞에서 이루어지는 구조가 사용됐습니다. 이후 Slimming Paint V2와 Composite After Paint 계열로 발전하면서 Paint Chunk를 만든 이후 PaintArtifactCompositor가 layerization하는 구조로 변경되었습니다. 현재 Chromium의 Paint 문서는 Layout 결과를 Pre-Paint와 Paint를 거쳐 Paint Chunk로 만들고, 이를 PaintArtifactCompositor가 처리하여 cc::Layer 목록으로 변환하는 방식을 설명하고 있습니다. 따라서 현재 Chromium을 기준으로 공부한다면 다음 흐름을 기준으로 이해하는 것이 좋습니다.Layout ↓ Pre-Paint ↓ Paint ↓ Display Items ↓ Paint Chunks ↓ Layerization ↓ Compositor Layers ↓ Rasterization ↓ Composition강의에서 Layout → Paint → Composite라고 설명하는 것은 이 모든 내부 단계를 초보자에게 처음부터 보여주기보다는 핵심적인 큰 흐름만 추려서 표현한 것이라고 생각하시면 됩니다.마지막으로 정리해보겠습니다첫 번째 질문인 “Layer 여러 개는 어떤 단계에서 생성되는 건가요?”에 대해서는, 현재 Chromium 기준으로 Paint 과정에서 Display Item과 Paint Chunk 같은 그리기 정보가 만들어지고, 이후 layerization 과정에서 이 Paint Chunk들을 바탕으로 compositor가 사용할 여러 cc::Layer가 결정된다고 이해하시면 됩니다.따라서 Composition 단계에서 갑자기 Layer가 만들어지는 것은 아닙니다. Composition은 이미 준비된 렌더링 결과들을 최종 화면으로 조합하는 쪽에 더 가깝습니다.두 번째 질문인 “렌더 트리 여러 개가 Layer 여러 개인가요?”에 대해서는 아닙니다.Render/Layout 계층은 페이지의 구조와 위치, 크기 등을 계산하기 위한 구조이고, compositor layer는 렌더링 결과를 어떤 단위로 독립적으로 관리하고 rasterize하고 합성할지를 결정하기 위한 별도의 개념입니다.가장 중요한 흐름만 다시 보면 다음과 같습니다.Layout → 어디에 얼마나 크게 배치할 것인가? Pre-Paint → Paint에 필요한 transform, clip, effect 등의 상태 준비 Paint → 무엇을 어떤 순서로 그릴 것인가? Display Item / Paint Chunk → Paint 결과를 브라우저 내부에서 관리하기 좋은 단위로 정리 Layerization → 어떤 Paint 결과들을 같은 Layer로 묶고, 어떤 것은 별도의 Layer로 관리할 것인가? Rasterization → 그리기 정보를 실제 픽셀 데이터로 변환 Composition → 준비된 여러 결과를 위치, transform, opacity 등을 반영하여 하나의 최종 화면으로 합성그리고 이 부분을 공부하실 때는 다음 관계를 특히 구분해두시면 좋습니다.DOM Element = Layer가 아니고, Render Tree = Layer Tree도 아니며, PaintLayer = Compositor Layer도 아닙니다. Stacking Context = Compositor Layer도 아니고, Paint Chunk 하나 = Layer 하나도 아닙니다. 마찬가지로 transform을 사용한다고 항상 새로운 Layer가 만들어지는 것도 아니며, will-change를 많이 사용한다고 항상 성능이 좋아지는 것도 아닙니다.프론트엔드 실무에서는 Chromium 내부 클래스명까지 전부 외우실 필요는 없습니다. 오히려 “내가 변경한 코드가 Layout까지 다시 발생시키는지, Paint가 필요한지, 아니면 기존 결과를 활용해서 Composite만으로 처리할 수 있는지”를 구분할 수 있는 것이 훨씬 중요합니다.이 흐름을 기준으로 이해하시면 이후에 reflow, repaint, composite, transform, opacity, will-change, stacking context, z-index, GPU 가속 같은 개념을 공부하실 때도 각각이 어느 지점에서 연결되는지 훨씬 수월하게 이해하실 수 있습니다.질문 감사합니다.
- いいね数
- 0
- コメント数
- 1
- 閲覧数
- 23
Q&A
Strict Mode 예시 질문!
안녕하세요 dwc07109님. 질문하신 부분은 중요한 포인트입니다.강의 화면의 예시처럼let formData = {}; function SignUpForm() { formData.name = "홍길동"; console.log("폼 렌더링:", formData); return {formData.name}; }단순히 formData.name에 매번 동일한 "홍길동" 값을 할당하는 경우라면, Strict Mode에서 컴포넌트가 두 번 실행되더라도 최종 값은 계속 "홍길동"입니다. 그래서 이 코드는 “두 번 실행되면서 값이 반복적으로 변경되는 문제”를 직접 보여주는 예시로는 조금 약한 편입니다.다만 이 예시에서 설명하려는 핵심은 React의 렌더링 과정이 순수해야 한다는 점입니다. 다시 말해, 컴포넌트가 렌더링되는 도중에 컴포넌트 외부에 있는 값이나 객체를 직접 변경하는 코드는 피하는 것이 좋습니다.예를 들어 아래처럼 작성하면 Strict Mode가 왜 이런 코드를 드러내는지 훨씬 명확하게 확인할 수 있습니다.const users = []; function SignUpForm() { users.push("홍길동"); console.log(users); return 회원가입; }개발 환경에서 Strict Mode로 인해 렌더링 로직이 다시 실행되면 다음과 같이 값이 누적될 수 있습니다.["홍길동"] ["홍길동", "홍길동"]이 경우에는 같은 컴포넌트를 다시 실행했는데도 외부 배열의 상태가 달라졌습니다. 즉, 렌더링 과정 자체에서 사이드 이펙트가 발생하고 있는 것입니다.여기서 한 가지 구분해두시면 좋습니다. Strict Mode가 “전역 변수를 사용했으니 잘못됐다”고 직접 감지해서 오류를 내는 것은 아닙니다. 개발 환경에서 일부 로직을 의도적으로 다시 실행해보면서, 순수하지 않은 렌더링 코드가 있다면 그 문제가 더 쉽게 드러나도록 도와주는 역할에 가깝습니다.반대로 아래처럼 컴포넌트 내부에서 매번 새로운 객체를 생성하는 경우에는 괜찮습니다.function SignUpForm() { const formData = { name: "홍길동", }; return {formData.name}; }컴포넌트가 여러 번 실행되더라도 각 실행마다 새로운 객체가 만들어지기 때문에 외부 상태를 변경하지 않습니다.실무에서는 “렌더링 중에는 계산만 하고, 외부 상태는 변경하지 않는다”는 기준을 잡아두시면 도움이 많이 됩니다. 서버 요청, 로컬 스토리지 저장, 전역 상태 변경, DOM 직접 조작처럼 외부에 영향을 주는 작업은 렌더링 과정에서 바로 수행하기보다 useEffect, 이벤트 핸들러, 상태 관리 로직 등 목적에 맞는 위치에서 처리하는 편이 안전합니다.또 Strict Mode를 사용할 때 “왜 API가 두 번 호출되지?”, “왜 배열에 값이 두 번 들어가지?” 같은 상황을 만나면 Strict Mode 자체를 원인으로 보기보다, 먼저 기존 코드 안에 렌더링 중 발생하는 사이드 이펙트가 있는지 확인해보시는 게 좋습니다.정리하면, 질문하신 것처럼 현재 "홍길동" 할당 예시는 Strict Mode의 문제를 보여주기에는 다소 약하고, push, ++, 누적 변경처럼 두 번째 실행에서 실제 결과가 달라지는 코드를 사용하는 편이 더 적절합니다.좋은 질문 남겨주셔서 감사합니다.
- いいね数
- 0
- コメント数
- 2
- 閲覧数
- 47
Q&A
오픈톡 인원이 다 차서 들어가지를 못 하네요...
안녕하세요, 티라임 님.문의해 주신 내용을 확인하여 즉시 인원을 증원했습니다. 감사합니다!!https://open.kakao.com/o/gC10Fnoh
- いいね数
- 0
- コメント数
- 1
- 閲覧数
- 52
Q&A
Context API vs Zustand 그리고 Auth
안녕하세요 Coi 님, 이번 Part 3 미션을 수강하시면서 Context API부터 전역 상태 관리, 그리고 인증(Auth)과 라우팅 보호까지 프론트엔드 아키텍처의 가장 깊은 곳까지 치열하게 고민하신 흔적이 묻어나는 질문입니다. 실무에서 누구나 한 번쯤 거대한 벽을 느끼는 주제들을 이렇게 논리적으로 정리해 주셔서 진심으로 감사합니다. 남겨주신 질문들이 서로 긴밀하게 연결되어 있으므로, 흐름에 따라 하나씩 자연스럽게 상세히 짚어보겠습니다.먼저 Context API와 Zustand의 선택 기준에 대한 Coi님의 이해는 완벽하게 맞습니다. 상태의 변경 빈도, 구독하는 컴포넌트의 범위, 리렌더링의 영향 등은 실무에서 두 기술을 나누는 가장 핵심적인 기준입니다. 이를 현실의 회사에 비유해 볼까요? Context API는 회사 전체에 울려 퍼지는 사내 방송 시스템과 같습니다. 다국어 설정이나 다크 모드처럼 '오늘부터 사내 공용어는 영어를 사용합니다', 혹은 '전 층 소등합니다'와 같이 가끔 발생하지만 모두가 알아야 하는 정보에 아주 적합하죠. 하지만 방송으로 '김 대리님, 버튼 색상 좀 바꿔주세요'라고 수시로 떠든다면, 모든 직원이 하던 일을 멈추고 방송을 들어야 하는 비효율, 즉 불필요한 리렌더링이 발생합니다.반면 Zustand는 특정 팀원들끼리만 소통하는 사내 메신저 단톡방과 같습니다. 상태가 수시로 변하더라도 해당 채팅방에 있는 사람들, 즉 구독한 컴포넌트에게만 알림이 가기 때문에 성능적으로 매우 유리합니다. 여기에 실무적인 기준을 한 가지 더 추가하자면, '일반 유틸리티 함수나 Axios 인터셉터 내부와 같은 React 컴포넌트 생명주기 외부에서도 해당 상태에 접근해야 하는가'를 고려해야 합니다. Context API는 반드시 React 트리 내부에서만 동작하지만, Zustand는 React 밖에서도 상태를 읽고 쓸 수 있다는 강력한 무기를 가지고 있습니다.그렇다면 애플리케이션의 척추와도 같은 Auth 상태는 어디에서 관리하는 것이 좋을까요? 결론부터 말씀드리면, 로그인한 유저 정보의 본질적인 '진실의 원천'은 클라이언트가 아니라 서버에 있습니다. 따라서 서버 상태를 동기화해주는 TanStack Query의 캐시를 그대로 진실의 원천으로 사용하는 것이 가장 현대적이고 권장되는 패턴입니다. 서버에서 데이터를 가져온 뒤 이를 Zustand나 Context에 다시 담아두는 방식은 피하는 것이 좋습니다. 현실에 비유하자면, 서버라는 은행에서 내 통장 잔고를 조회해 왔는데, 그걸 내 개인 수첩인 Zustand에 한 번 더 펜으로 적어두는 것과 같습니다. 시간이 지나면 수첩의 기록과 실제 은행 잔고가 달라지는 데이터 동기화 문제가 필연적으로 발생하기 마련입니다.물론 실무에서의 예외적인 케이스도 존재합니다. 만약 3번 질문에서 언급하신 ‘클라이언트에서 JWT 관리’ 방식을 채택하여, 보안상 Access Token을 로컬 스토리지가 아닌 메모리에 저장하기로 했다면 이야기가 다릅니다. 이때는 Axios 인터셉터 등 React 외부에서 최신 토큰을 꺼내 쓰기 위해 Zustand를 '토큰 저장소'로 활용하는 것이 매우 대중적이고 훌륭한 실무 패턴입니다. 또한, NextAuth.js(Auth.js) 같은 검증된 표준 라이브러리들은 내부적으로 라는 Context API를 사용해 전역에 인증 상태를 뿌려줍니다. 이렇게 라이브러리 차원에서 제공하는 Context를 사용하는 것은 자연스러운 패턴이니 안심하셔도 됩니다. 핵심은 '개발자가 직접 서버 데이터를 전역 상태에 수동으로 중복 복사하는 것을 지양하자'는 의미입니다.이러한 원칙은 React와 Next.js, 그리고 JWT와 세션 등 환경에 따라 조금씩 형태를 달리하지만 본질은 같습니다. 클라이언트에서 JWT를 관리하든 서버 세션 기반이든, 가장 중요한 것은 프론트엔드가 가진 유저 정보는 언제나 서버의 상태를 잠깐 빌려온 캐시라는 점입니다. 사실 이 내용은 향후 서버 개발을 하시면서 Redis나 Session 등 백엔드의 인프라를 다루시게 될 때 더욱 체감이 되실 텐데, 지금처럼 프론트엔드 관점에서만 보기보다는 이처럼 하나의 서비스가 굴러가는 풀스택 관점에서 큰 그림을 보는 것이 향후 개발을 해나가심에 있어 아주 큰 도움이 되실 것입니다. 예를 들어 React 환경에서 세션 기반 인증을 한다면, 클라이언트는 토큰의 존재조차 알 필요 없이 단순히 TanStack Query로 유저 정보를 요청하고 그 결괏값을 믿고 UI를 그리면 됩니다. Next.js 환경이라면 이 과정을 브라우저가 아닌 서버 컴포넌트에서 먼저 수행하여 캐싱된 상태를 클라이언트로 내려주는 방식으로 발전하게 되죠. 어떤 환경이든 내 클라이언트 전역 상태 수첩에 억지로 데이터를 복사하지 않고 캐시를 활용한다는 점이 핵심입니다.이어서 질문해주신 라우트 보호와 계층 분리에 대한 고민도 아주 예리하십니다. Coi님께서 말씀하신 대로 '로그인하지 않은 사용자에게 UI를 보여주지 않는 것'과, '실제 데이터 접근을 막는 보안'은 완벽하게 분리되어야 하는 별개의 문제입니다. 클라이언트에서의 라우트 보호는 현실의 클럽 입구에 쳐놓은 벨벳 밧줄과 같습니다. 정상적인 손님들에게 '여기는 VIP 전용입니다'라고 안내하여 헛걸음을 방지하는 UX적인 장치일 뿐입니다. 실제 철통같은 보안은 밧줄을 넘어갔을 때 마주치는 덩치 큰 경호원, 즉 서버의 인증 및 인가 API가 담당해야 합니다.따라서 React SPA 환경에서는 React Router v7의 loader 기능을 활용하는 것을 가장 권장합니다. 컴포넌트가 렌더링되기도 전에 TanStack Query 캐시나 서버 호출을 통해 인증 여부를 확인하고 진입을 차단하는 것이 가장 깔끔합니다. 반면 Next.js 환경이라면 이 경호원을 클럽 문 앞이 아니라 아예 동네 어귀에 배치할 수 있습니다. 바로 Middleware를 활용하는 방식입니다. 사용자가 프라이빗한 페이지를 요청하는 순간, React 컴포넌트가 실행되기도 전에 Next.js 미들웨어 엣지 단에서 쿠키를 검증하고 곧바로 로그인 페이지로 튕겨낼 수 있습니다.여기에 더해 Next.js App Router 환경이라면, TanStack Query를 거칠 필요도 없이 Server Component 단에서 직접 cookies()나 세션을 조회하여 곧바로 redirect 시킬 수도 있습니다. 서버 컴포넌트 자체가 든든한 경호원 역할을 겸할 수 있다는 점이 Next.js가 가진 가장 강력한 무기 중 하나입니다.실무에서 이 구조들이 어떻게 맞물려 돌아가는지 실제 코드 예시를 로직별로 상세하게 나누어서 보여드리겠습니다. 가장 먼저, Auth 상태의 진실의 원천을 TanStack Query로 관리하는 커스텀 훅을 작성합니다.JavaScriptexport function useAuthUser() { return useQuery({ queryKey: ['authUser'], queryFn: fetchCurrentUser, staleTime: 5 * 60 * 1000, }); }이렇게 만들어진 훅과 캐시를 바탕으로, React Router v7의 loader를 활용하여 UX 측면의 라우트 보호를 구현할 수 있습니다. 여기서 중요한 점은 Context나 Zustand를 보지 않고 서버 검증 결과를 신뢰한다는 것입니다.JavaScript// 주의: 컴포넌트 외부이므로 useQueryClient 훅을 사용할 수 없습니다. // App.js 등에서 생성한 전역 queryClient 인스턴스를 직접 import 해와야 합니다. export async function protectedLoader() { try { const user = await queryClient.fetchQuery({ queryKey: ['authUser'], queryFn: fetchCurrentUser, }); if (!user) return redirect('/login'); return user; } catch (error) { return redirect('/login'); } } 마지막으로 컴포넌트 내부에서는 Zustand와 억지로 동기화할 필요 없이 Query만 사용하여 아주 깔끔하게 화면을 그려낼 수 있습니다.export function Dashboard() { const { data: user } = useAuthUser(); return 환영합니다, {user.name}님!; }이러한 설계가 실제 실무 쇼핑몰 서비스에서 어떻게 거대한 풀스택 워크플로우로 굴러가는지, 프론트엔드와 백엔드의 상태 그리고 동기화 과정까지 포함하여 아주 상세하게 하나의 큰 그림으로 그려보겠습니다. 사용자가 장바구니에 담아둔 상품을 결제하기 위해 마이페이지에 진입하는 순간을 상상해 보시길 바랍니다. 가장 먼저 사용자가 페이지 이동 버튼을 누르면, 프론트엔드에서는 React Router의 loader나 Next.js의 Middleware(또는 Server Component)가 작동하여 브라우저에 세션 쿠키가 존재하는지 1차적으로 꼼꼼하게 확인합니다. 앞서 말씀드린 클럽 입구의 벨벳 밧줄 역할입니다. 쿠키가 없다면 화면을 렌더링조차 하지 않고 즉시 로그인 페이지로 돌려보냅니다.만약 쿠키가 존재해서 무사히 페이지 컴포넌트가 마운트되면, 이때 TanStack Query가 작동하여 백엔드 서버로 내 유저 정보와 장바구니 데이터를 달라는 API 요청을 보냅니다. 자, 이제 이 요청이 네트워크를 타고 백엔드 서버에 도착하는 순간부터 진짜 풀스택의 흐름이 시작됩니다. 클라이언트가 보낸 HTTP 요청은 가장 먼저 웹 서버나 로드밸런서를 거쳐 백엔드 API 서버에 도달하게 됩니다. 백엔드 서버는 요청 헤더에 담긴 HttpOnly 세션 쿠키를 추출해냅니다. 이 쿠키 안에는 사용자의 실제 개인정보가 아니라 그저 의미 없는 길고 복잡한 문자열인 세션 ID만이 들어있습니다. 서버는 이 세션 ID를 들고 가장 먼저 인메모리 데이터 저장소인 Redis로 달려갑니다. Redis는 하드 디스크가 아닌 메모리에서 동작하기 때문에 엄청나게 빠른 속도로 이 세션 ID가 유효한지, 만료되지는 않았는지, 그리고 이 세션의 주인이 어떤 유저 ID를 가지고 있는지 찾아냅니다. 만약 사용자가 다른 기기에서 로그아웃 버튼을 눌렀거나 세션 만료 시간이 지났다면 Redis에는 이 키가 없을 것이고, 서버는 즉시 401 권한 없음 에러를 프론트엔드로 던져 진짜 철통같은 보안 경호원 역할을 수행하게 됩니다.Redis에서 무사히 유저 ID를 확보했다면, 서버는 이제 이 유저의 실제 장바구니 데이터를 가져오기 위해 MySQL이나 PostgreSQL 같은 메인 관계형 데이터베이스에 접근합니다. 유저 테이블을 조회하여 최신 유저 프로필 정보를 가져오고, 장바구니 테이블과 상품 테이블을 조인하여 현재 담겨 있는 상품의 이름, 가격, 재고 상태 등을 하나의 깔끔한 JSON 데이터로 묶어냅니다. 이렇게 완벽하게 검증되고 가공된 '진실의 원천' 데이터가 다시 네트워크를 타고 프론트엔드로 응답됩니다.프론트엔드의 TanStack Query는 이 JSON 데이터를 받아 자신의 캐시 저장소에 예쁘게 저장하고 화면에 렌더링합니다. 이때 우리는 받아온 데이터를 Zustand나 Context API에 두 번 복사하지 않습니다. 서버의 상태를 그대로 거울처럼 비춰서 보여줄 뿐입니다. 그렇다면 Zustand는 언제 쓰일까요? 사용자가 장바구니 목록을 보다가 다크 모드 버튼을 누르거나, 모바일 화면에서 사이드바 메뉴를 열 때 사용됩니다. 이런 UI 상태는 서버와 전혀 상관이 없고 오직 클라이언트 브라우저 안에서만 유효하기 때문에 Zustand에 저장하고 빠르게 특정 컴포넌트들만 리렌더링 시키는 것입니다.만약 사용자가 상품의 수량을 플러스 버튼을 눌러 추가한다면 어떨까요? 프론트엔드는 즉시 백엔드로 수량 변경 POST 요청을 보내고, 백엔드는 다시 Redis에서 세션 권한을 확인한 뒤 메인 데이터베이스의 장바구니 테이블을 업데이트합니다. 서버가 성공 응답을 주면, 프론트엔드는 TanStack Query의 invalidateQueries를 호출하여 기존 캐시를 폐기하고, 앞서 말씀드린 서버로부터 장바구니 데이터를 다시 가져오는 그 정교한 과정을 새롭게 동기화해옵니다. 이처럼 프론트엔드에서 상태를 관리한다는 것은 단순히 클라이언트 라이브러리 하나를 고르는 것이 아니라, 거대한 서비스의 데이터가 서버의 메인 DB에서 출발해 빠른 Redis를 거쳐, API를 타고 클라이언트의 TanStack Query 캐시에 머물렀다가, 최종적으로 React 컴포넌트에 뿌려지는 이 거대한 물줄기의 흐름을 제어하는 아주 멋진 일입니다. 풀스택 관점에서 이 큰 그림을 머릿속에 담아두신다면, 앞으로 어떤 라이브러리가 새로 유행하더라도 흔들리지 않는 튼튼한 아키텍처 기준을 갖추시게 될 것입니다. 특히 AI 코딩 어시스턴트가 적극적으로 도입되며 개발의 경계가 빠르게 허물어지는 지금 같은 시점에는, 프론트엔드라는 영역에만 국한되지 않고 풀스택 관점에서 서비스 아키텍처 전체의 큰 그림을 설계하는 능력이 그 어느 때보다 중요해졌습니다. 이번 고민을 발판 삼아 숲 전체를 조망할 수 있는 훌륭한 엔지니어로 성장하시기를 진심으로 응원합니다.사실 이 아키텍처 설계라는 것이 단순히 폴더 구조나 라이브러리 하나만의 문제는 아닙니다. 이 구조 안에는 컴포넌트의 상태를 어떻게 관리할 것인지, props를 어떤 방식으로 내려줄 것인지, 함수와 훅의 재사용성은 어떻게 챙길 것인지 등 여러 복합적인 문제들이 아주 촘촘하게 얽혀 있기 때문에 단편적으로 무조건 이 방식이 정답이라고 말하기 참 힘든 부분이기도 합니다.그래서 권장해 드리고 싶은 방법은, 제가 조금 더 직접적이고 실무적인 도움이 될 수 있도록 현재 코딩하고 계신 프로젝트나 고민 중인 아키텍처의 형태를 대략적으로나마 말씀해 주시는 것입니다. 현재 팀의 상황이나 사용 중인 기술 스택, 고민되는 병목 지점 등을 공유해 주시면, 제가 그 맥락에 맞춰서 이런 방식의 설계가 조금 더 좋지 않을까 하고 시니어 관점에서 맞춤형 권장을 드릴 수 있을 것 같습니다.마지막으로, 현재 저희 우리동네코딩스튜디오 내부적으로 작은 이벤트를 진행하고 있습니다. 깊이 있는 고민으로 학습에 임하시는 Coi 님께 응원의 의미를 담아 작은 선물을 드리고자 하니, jeony0535@naver.com 으로 연락 한 통 주시면 우리동네코딩스튜디오 전 강의 쿠폰을 전달해 드리겠습니다. 앞으로도 학습하시면서 구조나 설계에 대한 실무적인 고민이 생기시면 언제든지 편하게 질문 남겨주세요.
- いいね数
- 1
- コメント数
- 2
- 閲覧数
- 79
Q&A
[reducer] 기능별 폴더 구조 설계/ 파일 분리로 유지 보수 가능한 아키텍처 구축
안녕하세요 Coi 님, 이번 미션을 진행하시면서 폴더 구조와 설계에 대해 치열하게 고민하시고 실무적인 관점이 담긴 참 좋은 질문을 남겨주셔서 진심으로 감사합니다.말씀해주신 reducers/cafeteria/rice처럼 기능이 커진다고 해서 무작정 뎁스를 파고드는 것에 대한 답변을 먼저 드리자면, 단순히 기능의 크기나 파일 개수만을 기준으로 기계적인 뎁스를 추가하는 것은 지양하는 것이 좋습니다. Coi님께서 짚어주신 대로 역할과 책임, 함께 변경되는 이유, 기능 간의 의존 관계를 기준으로 구조를 바라보셔야 합니다. 예를 들어 밥(rice)의 배식 로직이 변경될 때 반찬(side)의 로직도 항상 같이 확인하고 수정해야 할 만큼 강하게 결합되어 있다면, 굳이 뎁스를 깊게 파기보다는 같은 선상에 묶어두는 것이 맞습니다. 반대로 반찬 기능에만 특화된 복잡한 비즈니스 로직이 생겨나고 밥 메뉴와는 전혀 다른 이유와 주기로 코드가 변경되기 시작한다면, 그때 비로소 독립적인 바운더리로 세분화하여 분리하는 것이 좋습니다.실무에서 프로젝트 규모가 커졌을 때 가장 경계해야 할 안 좋은 예시는 기술적인 역할로만 폴더를 나누는 것입니다. 컴포넌트는 components 폴더에, 훅은 hooks 폴더에, 상태 관리는 reducers 폴더에 전부 몰아넣게 되면, 급식 메뉴와 관련된 기능 하나를 수정하기 위해 프로젝트 전역을 돌아다녀야 합니다. 그래서 최근 실무에서는 연관된 것들끼리 묶어두는 기능(도메인) 기반의 구조를 많이 지향합니다. 실제 실무에서 자주 구성하는 형태를 예시로 보여드리겠습니다.src/ ├── assets/ ├── constants/ ├── types/ ├── utils/ ├── hooks/ ├── components/ ├── layouts/ ├── pages/ └── features/ ├── cafeteria/ │ ├── components/ │ │ ├── CafeteriaMenu.js │ │ └── MenuCard.js │ ├── hooks/ │ │ └── useMenuFilter.js │ ├── store/ │ │ ├── riceReducer.js │ │ └── sideReducer.js │ └── api/ │ └── fetchWeeklyMenu.js └── payment/ ├── components/ └── api/ 위 구조의 목적을 자연스럽게 풀어보자면, 먼저 코드 외적인 이미지나 아이콘은 assets에 두고, 에러 메시지나 서버 URL 같이 변하지 않는 전역 값들은 constants에 모아둡니다. 타입스크립트를 사용한다면 전역 타입 객체는 types에 정의하고, 날짜 변환 함수처럼 React와 무관하게 언제든 재사용할 수 있는 순수 자바스크립트 함수들은 utils에 둡니다. 그리고 특정 비즈니스에 종속되지 않은 범용적인 커스텀 훅이나 어디서든 쓸 수 있는 공통 UI 버튼, 모달 같은 요소들은 각각 최상단의 hooks와 components에 배치합니다. 사용자가 접속하는 화면의 뼈대는 layouts에, 라우팅의 기준이 되는 최상위 뷰는 pages에 둡니다.가장 핵심이 되는 곳은 features 폴더입니다. 이곳에는 cafeteria나 payment처럼 특정 도메인별로 관련된 상태 관리 로직, UI, API 호출을 모두 모아둡니다. 이렇게 도메인별로 코드를 모아두었을 때 실무에서 어떻게 유지보수성이 극대화되는지 실제 파일 내부의 코드를 예시로 보여드리겠습니다.// src/features/cafeteria/components/CafeteriaMenu.js // 1. 전역에서 공통으로 쓰이는 요소는 외부(절대경로 등)에서 가져옵니다. import { Button } from '@/components/Button/Button'; import { formatDate } from '@/utils/formatDate'; // 2. 급식 도메인에 관련된 핵심 파일들은 바로 옆(상대 경로)에서 가져옵니다. import { MenuCard } from './MenuCard'; import { useMenuFilter } from '../hooks/useMenuFilter'; import { fetchWeeklyMenu } from '../api/fetchWeeklyMenu'; import { calculateCalories } from '../utils/calculateCalories'; export function CafeteriaMenu() { const { filteredMenu } = useMenuFilter(); return ( {/* 화면 렌더링 로직 */} ); }위 코드를 보시면 CafeteriaMenu라는 컴포넌트를 개발할 때, 전역적으로 쓰이는 공통 버튼이나 유틸 함수만 외부에서 가져오고, 급식과 관련된 비즈니스 로직(훅, API, 하위 UI)은 전부 자신의 폴더 내부에서 가져옵니다. 이렇게 하면 cafeteria 기능에 변경 사항이 생겼을 때 오직 해당 폴더 안에서만 작업을 끝낼 수 있습니다. 이 상태에서 파일이 많아진다면 폴더 뎁스를 더 파고 내려가기보다는, 위 예시의 store 폴더 안의 파일들처럼 연관된 로직들을 한 폴더 안에서 평면적으로 나누어 배치하는 것이 코드를 파악하는 데 훨씬 유리합니다.여기서 실무 폴더 구조를 나눌 때 제가 스스로에게 자주 던지는 질문이자, 꼭 드리고 싶은 경험적인 팁 중 하나는 바로 '삭제 테스트'입니다. 만약 내일 당장 기획이 변경되어 '급식(cafeteria)' 기능을 통째로 들어내야 한다면 어떨까요? 기능 기반으로 잘 뭉쳐진 구조라면 features/cafeteria 폴더 하나만 깔끔하게 삭제하면 끝납니다. 하지만 기술 기준으로 파편화되어 있다면 폴더마다 돌아다니며 관련 코드를 찾아 지워야 하고, 그 과정에서 실수로 다른 기능까지 망가뜨릴 위험이 큽니다. 즉, 기능을 추가할 때뿐만 아니라 '삭제하거나 수정할 때 얼마나 안전하고 빠른가'를 상상해 보는 것이 폴더 구조의 건강 상태를 체크하는 아주 훌륭한 기준이 됩니다.또 다른 실무적인 팁은 도메인 간의 '경계선'을 지키는 것입니다. 예를 들어 payment 결제 도메인에서 cafeteria 급식 데이터가 필요해질 때, 무작정 features/cafeteria/store/riceReducer.js 같은 깊은 내부 파일로 직접 경로를 뚫고 들어가 참조(import)하는 것은 피해야 합니다. 대신 각 기능 폴더의 최상단에 index.js를 두고 외부에서 사용할 컴포넌트나 상태만 명시적으로 내보내는 이른바 Public API 방식을 사용하거나, 두 도메인이 강하게 의존하는 공통 상태라면 차라리 더 상위의 전역 공간으로 끌어올리는(Lifting State Up) 결정을 내려야 합니다. 이렇게 폴더 간의 참조 규칙만 잘 세워두어도 기능이 커질 때 구조가 스파게티처럼 꼬이는 현상을 크게 방지할 수 있습니다.제가 실제 프로젝트를 진행할 때 구조를 잡아나가는 방식에 대해서도 말씀드리자면, 처음부터 완벽하고 일정한 구조나 엄격한 규칙을 정해두고 시작하지는 않습니다. 처음에는 폴더 구조를 최대한 얕고 단순하게 가져가며 관련된 코드들을 한곳에 뭉쳐둡니다. 그러다 프로젝트 규모가 커지면서 하나의 파일 안에서 코드를 찾기 위해 스크롤을 한참 내려야 하거나, 전혀 상관없어 보이는 다른 기능에 사이드 이펙트가 발생하는 등 기존 구조가 개발 생산성을 떨어뜨리기 시작하는 시점이 오면 그때 필요에 맞게 폴더를 분리하고 리팩토링합니다. 처음 정한 네이밍이나 기준에 억지로 맞추려다 뎁스만 깊어졌다는 Coi님의 경험처럼, 발생하지 않은 미래의 복잡도를 대비해 미리 거창한 구조를 짜두면 오히려 과도한 추상화의 늪에 빠지기 쉽기 때문입니다.사실 실무에서도 모든 프로젝트에 통용되는 완벽한 폴더 구조란 존재하지 않으며, 서비스의 규모와 팀의 상황에 맞는 최적의 형태를 하나씩 찾아가는 과정에 가깝습니다. 앞으로 React를 넘어 Next.js, Monorepo 등 다양한 기술과 환경으로 학습을 확장하시다 보면, Create React App 같이 프레임워크나 도구들이 자동으로 생성해 주는 권장 폴더 구조들을 접하시게 될 것입니다. 처음부터 완벽한 정답을 찾기보다는, 이러한 생태계의 표준화된 구조들을 하나씩 직접 사용해 보고 왜 파일들을 이렇게 배치했는지 분석해 보시면서 전체적인 큰 그림을 그리는 연습을 하시는 것이 큰 도움이 될 것입니다.구조를 설계하실 때 참고할 만한 개념으로는, 코드를 비즈니스 로직에 따라 나누는 도메인 주도 설계(DDD), 함께 변경되는 코드를 물리적으로 가장 가까운 곳에 배치하는 콜로케이션(Co-location), 그리고 최근 프론트엔드 아키텍처로 자주 논의되는 기능 분할 설계(FSD) 등을 가볍게 살펴보시는 것을 추천해 드립니다. 이 개념들은 뎁스를 몇 단계로 할지 정해주는 공식이 아니라, 어떤 코드를 묶고 어떤 코드를 분리할지 스스로 판단할 수 있는 튼튼한 기준점이 되어줄 것입니다.이러한 구조 설계 감각을 개인적으로 확실하게 연습해 보실 수 있는 팁도 하나 덧붙이겠습니다. 가장 추천하는 방법은 '과거의 내 코드 리팩토링하기'입니다. 예전에 기술 기준으로(components, hooks, reducers 등) 잘게 쪼개두었던 작은 토이 프로젝트나 과제 코드를 하나 꺼내서, 오늘 말씀드린 기능(features) 기반 구조로 직접 옮겨 담아보는 것입니다. 흩어져 있던 특정 기능의 UI와 상태, 훅들을 하나의 도메인 폴더로 묶어보고, 앞서 말씀드린 '해당 폴더 통째로 삭제해 보기' 테스트를 직접 시뮬레이션해 보세요. 백지상태에서 완벽한 구조를 짜려고 끙끙대는 것보다, 이미 동작하는 코드를 도메인 기준으로 재배치하며 발생하는 에러들을 하나씩 연결해 나가는 과정에서 아키텍처를 보는 안목이 비약적으로 성장하실 것입니다.추가적으로 한 가지 더 덧붙이자면, 사실 이 설계라는 것이 단순히 폴더 구조 하나만의 문제는 아닙니다. 컴포넌트의 상태(state)를 어떻게 관리할 것인지, props를 어떤 방식으로 내려줄 것인지, 그리고 함수들의 재사용성은 어떻게 챙길 것인지 등 여러 복합적인 문제들이 얽혀 있기 때문에 단편적으로 "딱 이렇다"라고 정답을 말씀드리기 참 힘든 부분이기도 합니다. 그래서 권장해 드리고 싶은 방법은, 제가 조금 더 직접적인 도움이 될 수 있도록 현재 진행하고 계신 프로젝트나 고민 중인 코드의 형태를 한 번 말씀해 주시는 것입니다. 대략적인 구조나 상황을 공유해 주시면, 제가 그에 맞춰서 "이런 방식의 설계가 조금 더 좋지 않을까" 하고 맞춤형 권장을 드릴 수 있을 것 같습니다.마지막으로, 현재 저희 우리동네코딩스튜디오 내부적으로 작은 이벤트를 진행하고 있습니다. 깊이 있는 고민으로 학습에 임하시는 Coi 님께 응원의 의미를 담아 작은 선물을 드리고자 하니, jeony0535@naver.com 으로 연락 한 통 주시면 우리동네코딩스튜디오 전 강의 쿠폰을 전달해 드리겠습니다. 앞으로도 학습하시면서 구조나 설계에 대한 실무적인 고민이 생기시면 언제든지 편하게 질문 남겨주세요.
- いいね数
- 0
- コメント数
- 2
- 閲覧数
- 50
Q&A
순수 함수 / 순수 컴포넌트
안녕하세요 Coi 님, 프론트엔드 개발을 하시면서 순수 함수와 순수 컴포넌트 개념을 단순히 넘기지 않고 완벽하게 체화하고자 깊게 고민하시고 이렇게 질문을 남겨주셔서 진심으로 감사합니다.강의 예제에서 언급했던 add(2, 3)이 항상 5를 반환하여 순수하고, getTime()은 실행할 때마다 결과가 달라져서 순수하지 않다는 개념을 먼저 떠올려 보시면 좋습니다. 질문해주신 내용을 보면 state는 언제든 변할 수 있고 서버에서 받아오는 상품 목록 같은 props도 결국 계속 바뀔 텐데 어떻게 항상 동일한 UI를 반환한다고 하는 건지 혼란스러우신 점을 아주 정확하게 짚어주셨습니다. 처음에 이 개념을 접할 때 누구나 한 번쯤 겪게 되는 아주 자연스럽고 핵심적인 의문입니다.우리가 짚고 넘어가야 할 가장 중요한 오해는 항상 동일한 UI를 반환한다는 말이 애플리케이션이 실행되는 내내 데이터나 화면이 영원히 변하지 않는다는 뜻이 결코 아니라는 점입니다. 여기서 말하는 순수성의 핵심은 시간이 아니라 특정 시점에 주어지는 특정 입력값에 대한 이야기입니다. 즉, 컴포넌트라는 함수에 입력으로 들어온 props와 state 값이 정확히 똑같다면 오늘 화면을 그리든 내일 그리든, 혹은 백 번을 다시 그리든 그 결과물인 UI는 토시 하나 틀리지 않고 똑같아야 한다는 뜻입니다.이 부분을 현실의 자판기에 비유해서 설명해 보겠습니다. 완벽하게 순수한 자판기가 있다고 가정해 보겠습니다. 이 자판기에 500원이라는 동전(props)과 콜라 버튼을 누르는 행위(state)가 입력되면 이 기계는 항상 콜라(UI)를 내어줍니다. 만약 내일 이 자판기에 1000원을 넣고 사이다 버튼을 누른다면 당연히 사이다가 나오겠지만, 그렇다고 해서 이 자판기가 순수하지 않은 기계가 되는 것은 아닙니다. 단지 입력값이 달라졌으니 그에 맞게 결과가 달라진 것뿐입니다.반면 순수하지 않은 자판기는 500원과 콜라 버튼을 똑같이 입력했는데도, 자판기 내부 온도가 높다는 외부 요인 때문에 갑자기 미지근한 물을 내어주거나, 자판기 관리자가 몰래 세팅을 바꿔두어 랜덤한 음료수를 내어주는 식입니다. 똑같은 입력을 주었는데도 외부 상황이나 시간에 따라 결과가 요동치는 것이죠.React의 컴포넌트도 이 자판기와 완벽하게 똑같이 동작해야 합니다. 컴포넌트는 결국 자바스크립트 함수이고, 렌더링은 그 함수를 실행하는 행위입니다. React는 state가 변경되거나 서버에서 새로운 props를 받아오면 컴포넌트라는 함수를 다시 호출하여 새로운 재료들을 던져줍니다. 이때 컴포넌트는 오직 자신이 방금 건네받은 그 재료들만을 사용해서 화면을 그려내야 합니다. 함수 밖에 있는 전역 변수를 몰래 가져와서 화면을 그리거나, 건네받은 props 자체를 컴포넌트 안에서 마음대로 수정해버리면 동일한 재료를 줬을 때 동일한 결과가 나온다는 보장을 할 수 없게 됩니다.이를 코드로 직접 비교해보면 훨씬 직관적으로 다가오실 것입니다. 먼저 외부 변수에 의존해서 순수성을 잃어버린 컴포넌트를 살펴보겠습니다.let externalCount = 0; function ImpureComponent({ label }) { externalCount += 1; return {label}: {externalCount}; }위 코드의 ImpureComponent는 label이라는 똑같은 props를 받더라도, 화면을 그릴 때마다 externalCount라는 외부 변수를 읽어오고 심지어 그 값을 증가시킵니다. 따라서 label로 "상품"이라는 동일한 입력을 주어도 렌더링 될 때마다 "상품: 1", "상품: 2", "상품: 3"으로 결과가 계속 달라집니다. 실행 시점마다 달라지는 getTime()과 다를 바가 없기 때문에 순수하지 않은 것입니다. 반면에 우리가 실무에서 지향해야 하는 순수 컴포넌트는 외부 요인을 철저히 배제합니다.JavaScriptfunction PureComponent({ label, count }) { return {label}: {count}; }이 PureComponent는 오직 자신에게 주어진 label과 count라는 props만을 재료로 화면을 그립니다. 외부 세상에 어떤 변수가 있든 전혀 신경 쓰지 않으며, 오로지 넘겨받은 값이 같으면 언제나 같은 UI를 반환합니다. 서버에서 새로운 상품 목록을 받아와서 새로운 state가 형성되고 그 값이 props로 넘어온다면, 컴포넌트는 단지 그 새로운 입력에 맞는 새로운 화면을 정직하게 그려낼 뿐입니다. 이것이 바로 React가 렌더링 결과를 예측 가능하게 만들고, 안심하고 불필요한 렌더링을 최적화할 수 있는 튼튼한 기반이 됩니다.결론적으로 컴포넌트가 순수하다는 것은 데이터가 변하지 않는 정적인 상태라는 뜻이 아니라, 동일한 재료를 넣었을 때 불순물이 섞이거나 예측 불가능한 결과가 튀어나오지 않는다는 굳건한 약속입니다. 앞으로 실무에서 코드를 작성하실 때, 내가 만든 컴포넌트가 오직 넘겨받은 props와 자신이 가진 state만을 가지고 화면을 결정하고 있는지, 혹시 함수 밖에 있는 무언가를 읽거나 건드리고 있지는 않은지만 스스로 점검해 보신다면 순수 컴포넌트의 개념을 코드에 완벽하게 녹여내실 수 있을 것입니다.끝으로, 개념을 확실히 체화하고 넘어가시려는 Coi 님의 멋진 학습 태도를 응원하는 마음을 담아 작은 선물을 드리고자 합니다. 현재 저희 우리동네코딩스튜디오 내부적으로 이벤트를 진행하고 있는데요, jeony0535@naver.com 으로 연락 한 통 주시면 우리동네코딩스튜디오 전 강의 쿠폰을 전달해 드리겠습니다. 앞으로도 개발하시면서 궁금한 점이 생기시면 언제든 편하게 질문 남겨주세요.
- いいね数
- 0
- コメント数
- 1
- 閲覧数
- 53
Q&A
nestjs 강의는 없나요??
안녕하세요 채유현 님! 우리동네코딩 스튜디오 Mike 입니다!우선 저희 강의를 믿고 열정적으로 수강해 주셔서 진심으로 감사드립니다. 원리를 파헤치는 과정에서 즐거움을 느끼고 큰 도움을 받고 계신다는 말씀에 강사로서 더할 나위 없이 큰 보람과 뿌듯함을 느낍니다. 프론트엔드와 백엔드를 아우르는 풀스택 개발자이자, 깊이와 넓이를 모두 갖춘 T자형 인재를 목표로 나아가시는 유현 님의 멋진 열정을 마음 깊이 응원합니다.지금 Node.js 핵심 원리와 유닉스 강의를 듣고 계신 것은 정말 탁월한 선택입니다. 탄탄한 컴퓨터 과학 지식과 코어 동작 원리라는 든든한 밑바탕이 갖추어져 있어야만, 향후 다루게 되실 NestJS나 React 같은 강력한 프레임워크들을 단순한 도구 수준을 넘어 온전히 내 의도대로 자유자재로 다룰 수 있기 때문입니다. 당장 업무에 NestJS와 React를 도입하셔야 해서 마음이 많이 조급하시겠지만, 지금처럼 원리에 집중하며 기초를 다지는 태도가 결국에는 가장 빠르고 확실한 지름길이라고 확신합니다.앞으로의 공부 방향과 더불어 NestJS에 어떻게 접근해야 할지 고민이 많으셨을 텐데, 마침 유현 님의 성장 로드맵에 완벽하게 들어맞는 새로운 커리큘럼 소식이 있어 안내해 드립니다. 다가오는 이번 8월 중에 'AI 딸깍의 시대, 생존을 위해 반드시 알아야 하는 암호화' 시리즈가 먼저 공개될 예정입니다. 총 3개의 파트로 구성된 이 시리즈는 풀스택 개발자라면 반드시 갖추어야 할 보안과 인증의 모든 핵심을 바닥부터 끝까지 깊이 있게 파헤치는 과정입니다.먼저 파트 1에서는 소프트웨어 개발자로서 꼭 알아야 할 암호학 코어를 샅샅이 해체해 봅니다. 개발 과정에서 접하는 다양한 암호화 기술들을 수박 겉핥기식으로 넘기는 것이 아니라, 실제 원리와 동작 메커니즘을 확실하게 뜯어보게 됩니다.구체적으로는 대칭키 및 비대칭키 암호화의 기초부터 시작해 일회용 비밀번호(OTP)를 직접 만들어보고, AES의 원리와 운영 모드, SHA-256을 직접 구현해 보는 해싱, 메시지 인증 코드와 키 도출 함수, RSA 알고리즘, 전자 서명, 디지털 인증서 발급과 HTTPS 및 TLS 구축, 그리고 디피-헬만 키 교환과 타원 곡선 암호(ECC)에 이르기까지 보안 구현에 필수적인 핵심 키워드들을 매우 깊이 있게 다룹니다.이어지는 파트 2에서는 프론트엔드와 백엔드를 아우르는 최신 웹 인증 및 인가 시스템을 실무 환경에 구현하는 방법을 본격적으로 학습합니다. 자격 증명 기반 로그인과 구글 및 깃허브를 활용한 OAuth 소셜 로그인은 물론, 이중 인증(2FA), 역할 기반 접근 제어(RBAC), 이메일 인증 및 비밀번호 재설정 같은 필수 기능들을 직접 구현해 보실 수 있습니다.나아가 Next.js 미들웨어와 Next-Auth 세션 확장, 서버 액션 및 API 라우트 보안, 사용자 관리 대시보드 구축, 그리고 프로덕션 환경에 필수적인 에러 핸들링까지 실전 풀스택 인증 체계를 구축하는 데 필요한 모든 과정을 체계적으로 담았습니다.그리고 파트 3로 넘어가기에 앞서, 당장 실무에서 NestJS를 다루셔야 하는 유현 님과 같은 수강생분들을 위해 NestJS 전용 강의를 먼저 선보일 계획입니다. 실무 투입에 대한 조급한 마음을 깊이 이해하고 있기에, 가능한 한 빠르게 실무에 적용할 수 있으면서도 프레임워크의 아키텍처와 원리를 꿰뚫어 볼 수 있는 높은 완성도로 준비하고 있습니다.이 강의를 통해 컨트롤러, 프로바이더, 모듈 등 NestJS의 기본 뼈대와 구조를 단단히 잡아두시면 실무 개발을 진행하실 때 즉각적이면서도 실질적인 큰 도움을 받으실 수 있을 것입니다.이렇게 NestJS 강의로 기본기를 다지신 후에는, 인증의 최종장인 파트 3를 통해 대규모 서비스 환경에서 요구되는 고도화된 인증 방식을 NestJS 백엔드 환경에서 완벽하게 구현하는 방법을 배우게 됩니다.상태 유지(Stateful) 방식과 무상태(Stateless) 인증의 차이를 이해하는 것을 시작으로 세션, 쿠키, JWT 토큰 기반 인증을 꼼꼼하게 다루며, NestJS에 TypeORM과 Redis를 연동하는 실전 기술을 학습합니다. 더 나아가 OAuth 2.0 통합, Auth0를 활용한 마이크로서비스 인증, 싱글 사인온(SSO), SAML 및 OIDC 아키텍처 등 엔터프라이즈급 인증 시스템에 필요한 방대한 내용을 시원하게 섭렵하시게 됩니다.질문해 주신 학습 순서에 대해서는, 가급적 제가 출시하는 강의 순서대로 하나씩 차근차근 완강하시는 것을 가장 권장해 드립니다. 제 모든 강의는 지식을 가장 밑바닥부터 다지며 거대한 뼈대를 쌓아 올릴 수 있도록 유기적으로 설계되어 있기 때문입니다. 지금 수강 중이신 강의를 통해 서버와 운영체제의 기초를 튼튼하게 다지신 뒤 새로 출시되는 강의들을 순서대로 따라오신다면, 어느새 깊이 있는 역량을 갖춘 풀스택 개발자로 성장해 계실 것입니다. 당장 급한 실무 업무는 공식 문서의 튜토리얼을 가볍게 참고하며 우선 해결해 나가시고, 깊이 있는 기술적 뼈대는 제가 책임지고 든든하게 채워드리겠습니다. 아울러 강의를 수강하시면서 반드시 나만의 프로젝트를 하나 정해 직접 개발해 보시는 것을 적극 추천해 드립니다. 배운 지식을 직접 내 코드에 적용하고 부딪히며 풀어내는 과정이야말로 실력을 도약시키는 가장 확실한 밑거름이 되기 때문입니다. 유현 님이 훌륭한 T자형 인재로 성장해 나가시는 여정에 끝까지 함께하겠습니다. 언제나 응원하겠습니다. 소중한 질문 남겨주셔서 다시 한번 진심으로 감사드립니다. 파이팅입니다!
- いいね数
- 1
- コメント数
- 2
- 閲覧数
- 68
Q&A
ReactScriptExample 관련 질문
안녕하세요 한건우 님, 문의 질문 주셔서 감사합니다.크롬을 포함한 어떠한 최신 브라우저도 JSX를 자체적으로 지원하지는 않습니다. JSX는 공식적인 자바스크립트 표준 문법이 아니기 때문입니다. 그럼에도 불구하고 Node.js 서버 없이 로컬 환경의 크롬에서 화면이 성공적으로 렌더링될 수 있었던 이유는 작성하신 아래의 코드 내에 포함된 Babel 스크립트 덕분입니다. Hello React with Babel const element = Hello, React with Express and Babel!; const root = ReactDOM.createRoot(document.getElementById("root")); root.render(element); 위 코드를 살펴보시면 태그 내에서 React와 함께 Babel 스크립트를 불러오고 있으며, 하단의 태그에는 타입이 type="text/babel"로 명시되어 있는 것을 확인할 수 있습니다. 브라우저가 HTML 문서를 읽어 내려갈 때 이 Babel 스크립트가 실행되며, 선언된 블록 안의 JSX 코드를 브라우저가 이해할 수 있는 순수 자바스크립트 코드로 실시간 변환해 줍니다. 즉, 브라우저가 JSX를 직접 이해한 것이 아니라 브라우저 위에서 동작하는 Babel이 JSX를 번역하여 렌더링 엔진에 전달해 주었기 때문에 정상적으로 화면이 나타난 것입니다.다만 엔진 설계자의 관점에서 보았을 때, 이처럼 HTML 파일에 직접 Babel을 불러와 사용하는 런타임 컴파일 방식은 로컬에서의 간단한 테스트 목적에만 적합합니다. 사용자가 웹 페이지에 접속할 때마다 브라우저에서 무거운 컴파일 과정을 거치게 되면 렌더링 성능에 치명적인 영향을 주기 때문입니다. 따라서 실제 프로덕션 환경에서는 빌드 도구를 활용해 개발 단계에서 미리 컴파일을 완료하고, 최적화된 순수 자바스크립트 결과물만을 브라우저로 전달하는 아키텍처를 채택하게 됩니다.이번 답변이 렌더링의 본질을 이해하는 데 도움이 되셨기를 바라며, 앞으로도 강의를 들으시며 궁금한 점이 있으시다면 언제든 질문 남겨주시길 바랍니다.추가로, 현재 수강생분들을 대상으로 최대 할인 쿠폰을 전달드리고 있습니다. 쿠폰 지급이 필요하시다면 jeony0535@naver.com으로 이메일 한 통 부탁드립니다!
- いいね数
- 0
- コメント数
- 1
- 閲覧数
- 55
Q&A
81강 실습 자료 없음
안녕하세요, Yoon Robin님!문의해주신 '81강. [Pain] 대규모 삭제 작업 시 발생하는 수많은 요청을 하나로 묶기' 차시와 관련하여 답변드립니다.네, 말씀해 주신 내용이 맞습니다. 해당 강의는 별도의 실습 자료가 제공되지 않는 것으로 의도된 구성이 맞습니다.학습 진행에 혹시나 도움이 될까 하여 강의 대본 자료를 함께 첨부해 드립니다!학습에 참고해 주시기 바라며, 추가로 궁금하신 점이나 더 필요하신 사항이 있으시다면 언제든 편하게 문의해 주세요. 감사합니다!지난 78강에서 우리는 MSW를 통해 서버 없는 테스트 환경을 구축하고 비동기 로직을 완벽하게 검증하는 법을 배웠으며, 이를 통해 우리의 코드는 개별 기능 단위에서 아주 높은 신뢰성을 확보하게 되었습니다. 하지만 실제 업무 현장은 그리 호락호락하지 않은데, 단일 요청의 성공과 실패를 넘어 수많은 데이터를 한꺼번에 조작해야 하는 이른바 규모의 문제가 우리를 기다리고 있기 때문입니다. 예를 들어 관리자 페이지에서 수백 명의 회원을 한꺼번에 선택하여 삭제하는 대규모 작업을 수행해야 할 때, 우리가 이전에 배운 방식대로 각각의 유저에 대해 독립적인 삭제 요청을 보내도록 설계할 수 있습니다. 그러나 이러한 방식은 곧 우리 앱과 서버에 엄청난 시각적이고 기술적인 고통을 안겨주는 네트워크 폭풍으로 변하게 되며, 오늘은 이러한 비효율이 발생하는 근본적인 원인과 그 고통의 실체를 하나씩 파헤쳐 보는 시간을 갖겠습니다.가장 먼저 우리가 마주하게 될 첫 번째 고통의 단위는 브라우저와 서버 사이의 통신 효율성 문제입니다. 현대의 대부분 브라우저는 HTTP/1.1 규격을 기준으로 동일 도메인당 동시 연결 수를 보통 6개로 제한하고 있습니다. 만약 관리자가 100명의 유저를 한 번에 삭제하려 하고 우리가 이를 100개의 개별 요청으로 보낸다면, 브라우저는 물리적인 제약 때문에 한 번에 6개 정도의 요청만 동시에 보낼 수 있게 됩니다. 이로 인해 나머지 94개의 요청은 네트워크 탭에서 'Pending' 상태로 하염없이 앞선 요청이 끝나기만을 기다리는 현상이 발생합니다.이러한 현상을 우리는 흔히 워터폴(Waterfall) 현상이라고 부르며, 이는 단순히 전체 삭제 프로세스를 매우 느리게 만들 뿐만 아니라 브라우저의 가용 자원을 독점하여 다른 중요한 요청들까지 가로막는 심각한 병목 현상을 초래하게 됩니다. 수백 개의 요청을 개별적으로 보내는 비효율적인 삭제 로직을 코드로 살펴보면 다음과 같습니다./** * [Pain Scenario] 수백 개의 요청을 개별적으로 보내는 비효율적인 삭제 로직 예시입니다. * userIds 배열을 순회하며 하나씩 mutate를 호출하는 방식은 네트워크 폭풍의 주범이 됩니다. */ const handleDeleteUsers = (userIds: number[]) => { // 아이디 배열을 단순 반복문으로 돌리며 각각 요청을 트리거합니다. userIds.forEach(id => { /** * [고통의 시작] 이 코드는 아이디가 100개라면 100번의 HTTP 통신을 발생시킵니다. * 브라우저의 6개 동시 연결 제한 때문에 나머지 94개는 대기 상태에 빠지게 됩니다. */ deleteUserMutation.mutate(id); }); }; 이러한 상황에 대해 최신 HTTP/2 환경에서는 멀티플렉싱을 지원하니 여러 요청을 동시에 보내도 괜찮지 않느냐고 생각할 수 있지만, 이는 어디까지나 네트워크 레이어의 전송 효율이 좋아지는 것일 뿐 어플리케이션 레이어에서의 부하와 데이터베이스 트랜잭션 비용은 전혀 줄어들지 않는다는 점을 명심해야 합니다. 서버는 여전히 100개의 요청을 각각 독립적으로 수신해야 하고 100번의 인증 로직을 거쳐야 하며 100번의 로그를 일일이 남겨야 합니다. 특히 데이터베이스 관점에서 보면 이러한 방식은 커넥션 풀을 순식간에 고갈시키는 주범이 됩니다.하나의 트랜잭션으로 묶어 한 번에 처리할 수 있는 작업을 100번의 독립적인 작업으로 쪼개서 보내는 것은 마치 마트에서 물건 100개를 사면서 매번 물건 하나를 집을 때마다 계산대로 달려가 따로 결제하는 것과 마찬가지입니다. 이 과정에서 발생하는 불필요한 입출력 부하는 서버 전체를 마비시킬 수 있는 위험을 내포하고 있으며, 클라이언트의 무심한 반복문이 서버의 자원을 어떻게 갉아먹는지 보여주는 가상 코드는 다음과 같습니다./** * 서버 측의 고통을 시뮬레이션한 가상 코드입니다. */ app.delete('/api/users/:id', async (req, res) => { /** * 개별 요청마다 인증, 로깅, DB 커넥션 점유가 반복적으로 일어납니다. * 50번째 요청에서 네트워크 에러가 발생한다면 원자성은 이미 파괴된 상태가 됩니다. */ const result = await db.users.delete(req.params.id); res.json(result); }); 무분별한 요청 폭발은 사용자 경험에서도 치명적인 페인 포인트를 발생시키는데, 그중에서도 가장 골치 아픈 문제는 바로 '원자성(Atomicity)'의 결여로 인한 데이터 정합성의 붕괴입니다. 여기서 원자성이란 "전부 성공하거나, 혹은 전부 실패하거나(All-or-Nothing)"라는 원칙을 의미합니다. 100개의 개별 요청을 보내면 네트워크 상황이나 서버 상태에 따라 일부는 성공하고 일부는 실패하는 상황이 발생할 수 있는데, 전부 삭제되거나 아예 삭제되지 않아야 하는 이 원칙이 지켜지지 않아 어정쩡하게 절반만 삭제된 데이터는 복구조차 불가능한 재앙이 됩니다.또한 시각적으로도 리스트 항목들이 하나씩 툭툭 끊기며 사라지거나 화면이 덜컥거리고, 100개의 로딩 인디케이터가 화면을 뒤덮으며 동시에 돌아가는 기괴한 광경을 연출하게 됩니다. 사용자는 삭제가 완전히 끝난 것인지 왜 일부는 여전히 남아있는지 알 수 없는 혼란에 빠지게 되는데, 이는 시니어 개발자가 결코 용납해서는 안 되는 설계의 실패라고 할 수 있습니다.결국 우리가 직면한 이 거대한 고통의 핵심은 네트워크 통신의 높은 비용과 원자성의 결여에 있으며, 수백 개의 작은 요청을 하나의 큰 요청으로 묶어서 보내는 전략을 사용한다면 우리는 이 네트워크 폭풍을 평화로운 잔물결로 바꿀 수 있습니다. 오늘 우리는 대규모 데이터 조작 시 발생하는 수많은 요청이 왜 우리를 괴롭히는지 그 페인 포인트의 실체를 낱낱이 파헤쳐 보았으며, 이제 이 지독한 성능 문제를 해결하기 위해 우리만의 특수 요원을 투입할 차례입니다.다음 강의에서는 비효율적인 네트워크 폭풍을 단 한 번의 깔끔한 통신으로 해결하는 솔루션인 Query Batching 전략을 통한 서버 부하 최소화에 대해 알아보겠습니다. 수백 번의 계산대 방문을 단 한 번의 카트 결제로 바꿔버리는 시니어의 마법 같은 최적화 기법을 기대해 주시길 바라며 오늘도 정말 고생 많으셨습니다. 그럼 다음 강의에서 뵙겠습니다.
- いいね数
- 0
- コメント数
- 2
- 閲覧数
- 63
Q&A
62강 실습자료가 잘못되었습니다.
안녕하세요, Yoon Robin님! 우리동네코딩스튜디오입니다.먼저 62강 실습 자료의 내용이 잘못 반영되어 있어 강의 수강에 불편을 드린 점 진심으로 사과드립니다. 말씀해주신 내용을 확인한 뒤, 기존의 '[Pain] 수동으로 관리하던 전역 로딩/에러 상태가 로직을 오염시킬 때' 자료를 올바른 '[Solution] Stale-While-Revalidate: 사용자에게는 캐시를, 뒤에서는 갱신을' 자료로 즉시 교체 수정 완료하였습니다! 지금 바로 확인해 보실 수 있습니다.수강생분들께 더 좋은 품질의 강의를 제공하기 위해 노력하고 있으나, 이렇게 꼼꼼하게 확인해주시고 소중한 제보를 남겨주셔서 정말 큰 도움이 되었습니다.감사한 마음과 함께 죄송한 마음을 담아 작은 선물을 전달해 드리고자 합니다. 괜찮으시다면 jeony0535@naver.com으로 편하게 메일 한 번 남겨주실 수 있으실까요? 메일을 남겨주시면 우리동네코딩스튜디오 전 강의 대상 최대 할인 쿠폰을 전달해 드리겠습니다.앞으로도 더 양질의 콘텐츠와 쾌적한 수강 환경을 제공해 드릴 수 있도록 최선을 다하겠습니다.다시 한번 제보해 주셔서 정말 감사드리며, 수강 중 궁금하신 점이 있다면 언제든 편하게 질문 남겨주세요!감사합니다. 우리동네코딩스튜디오 드림
- いいね数
- 0
- コメント数
- 1
- 閲覧数
- 58




