안녕하세요, 우리동네코딩 스튜디오에 오신 것을 환영합니다!
우리동네코딩 스튜디오는 카네기 멜론, 워싱턴, 토론토, 워터루 등 북미의 주요 대학에서 컴퓨터공학을 전공하고, 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.
Courses
Reviews
- Implementing Notion Directly with Vanilla JavaScript
rarla
·
From DOM to Pixels, Complete Mastery of Browser Rendering and CRP - [DOM Complete Mastery Part 3]From DOM to Pixels, Complete Mastery of Browser Rendering and CRP - [DOM Complete Mastery Part 3]- Learning Express.js Properly: Part 1, From Basics to Advanced [Basics]
- Implementing Notion Directly with Vanilla JavaScript
- Everything About Structure Navigation and Manipulation - [Complete DOM Mastery Part 1]
Posts
Q&A
만약 문서 수가 매우 많아진다면 성능 이슈는 없을까요?
안녕하세요 쌀밥님 😊질문 주셔서 감사합니다.이 내용에서 다루는 최적화 방법을 완전히 이해하려면 약간의 컴퓨터 과학적 배경 지식이 필요합니다.특히 다음 세 가지 개념을 알고 있으면 훨씬 쉽게 이해할 수 있습니다.첫째는 시간 복잡도(Time Complexity) 개념으로, 어떤 코드가 데이터의 양이 늘어날수록 얼마나 느려지는지를 나타내는 지표입니다. 예를 들어 배열을 처음부터 끝까지 모두 훑는 코드는 O(n), 중첩 반복문이 있는 코드는 O(n²)처럼 표현합니다.둘째는 자료구조(Data Structure) 중 배열(Array)과 맵(Map, 해시맵 HashMap)입니다. 배열은 순서대로 저장하는 구조이지만 검색할 때는 처음부터 끝까지 찾아야 하고, 맵은 키(key)를 이용해 값을 거의 즉시 찾을 수 있는 구조입니다.셋째는 인덱스(Index) 개념으로, 데이터베이스나 검색 시스템에서 “필요한 데이터를 빠르게 찾기 위해 미리 만들어두는 참조용 지도”를 말합니다.이 세 가지 개념이 이 글의 모든 최적화 논리의 토대가 됩니다.지금의 구조는 state.docs 배열 전체를 매번 find()나 filter()로 훑어보는 방식이라, 문서가 수백 개나 수천 개로 늘어나면 자연스럽게 성능 저하가 생깁니다. 초반에는 아무 문제가 없어 보여도, 트리 렌더링이 일어날 때 각 노드마다 childrenOf()가 반복적으로 호출되고, 거기에 문서 이동, 제목 수정, 즐겨찾기 토글 같은 이벤트가 동시에 일어나면 한 프레임 안에서 선형 탐색이 여러 번 겹치게 됩니다. 이렇게 되면 체감상 O(n²) 형태로 느려져서 브라우저가 잠시 멈추는 현상이 발생할 수 있습니다.이 문제를 근본적으로 해결하려면 세 가지가 함께 고려되어야 합니다. 첫째, 조회를 거의 즉시 끝낼 수 있게 만드는 인덱스 구조 설계. 둘째, DOM 갱신 자체를 최소화하는 렌더링 방식. 셋째, 저장과 렌더 호출 빈도를 조절해 메인 스레드가 갑자기 과부하되지 않도록 하는 것입니다.이 원리를 조금 더 쉽게 설명하면 “자주 일어나는 일은 최대한 빠르게, 드물게 일어나는 일은 조금 느려도 괜찮게 만든다”는 개념입니다. 문서 탐색은 매우 자주 일어나는 작업이므로 반드시 빠르게 만들어야 하고, 반면 문서를 새로 만들거나 이동하거나 삭제하는 일은 상대적으로 덜 자주 일어나기 때문에 그때만 약간의 추가 비용을 내도 전체 앱의 체감 속도는 훨씬 빨라집니다.이 구조를 쉽게 구현할 수 있는 도구가 바로 자바스크립트의 Map입니다.Map은 일반 객체(Object)와 비슷하게 키(key)와 값(value)을 짝지어 저장하는 자료 구조이지만, 몇 가지 중요한 차이가 있습니다.첫째, Map은 어떤 타입이든 키로 사용할 수 있습니다. 숫자, 문자열뿐 아니라 객체나 심볼도 가능합니다.둘째, get, set, has, delete, size 같은 전용 메서드를 통해 데이터를 매우 빠르게 읽고 쓸 수 있습니다.셋째, Map은 내부적으로 해시 구조를 사용하기 때문에 평균적으로 데이터를 찾거나 넣는 데 걸리는 시간이 거의 일정합니다. 즉, 데이터가 아무리 많아져도 조회 속도가 거의 변하지 않습니다.이걸 전화번호부로 비유해 보면 훨씬 직관적입니다.일반 배열을 탐색하는 것은 마치 “전화번호부를 처음부터 끝까지 한 줄씩 넘기며 이름을 찾는 방식”이라면, Map은 “이름을 입력하자마자 바로 번호가 뜨는 스마트폰 연락처 검색”에 가깝습니다.이처럼 Map을 이용하면 id로 문서를 즉시 찾거나 특정 부모 ID에 속한 자식 문서들을 한 번에 가져올 수 있습니다.이 원리를 현재 코드에 적용하려면 두 가지 관점에서 인덱스를 설계하면 됩니다.하나는 id → 문서 객체 형태의 인덱스이고,다른 하나는 parentId → [자식 문서들] 형태의 인덱스입니다.앱이 처음 실행될 때 한 번 전체 데이터를 훑어서 이 두 인덱스를 미리 만들어 두고, 이후에는 문서가 새로 만들어지거나 수정되거나 이동되거나 삭제될 때마다 인덱스를 즉시 갱신하도록 유지하면 됩니다.이렇게 하면 문서를 찾을 때 전체 배열을 훑을 필요가 없고, 필요한 정보만 즉시 꺼내올 수 있습니다. 또한 변경 시에도 그 부모에 해당하는 작은 그룹만 다시 정렬하면 되므로 전체를 다시 계산할 필요가 없습니다.정렬 기준은 기존과 동일하게 order를 우선으로 하고, 값이 같을 때만 title을 비교하는 것이 좋습니다. localeCompare는 CPU 비용이 상대적으로 크기 때문에 보조 비교로만 사용하는 것이 효율적입니다.아래 코드는 이런 인덱스를 실제로 구현하는 최소한의 예시입니다. 호출부는 그대로 두고, 내부 동작만 교체하는 방식이라 전체 코드 구조를 바꾸지 않아도 안전하게 적용할 수 있습니다.// 인덱스 const index = { byId: new Map(), byParent: new Map() }; const byOrder = (a,b)=> (a.order - b.order) || a.title.localeCompare(b.title); function ensureBucket(pid){ const key = pid ?? null; if (!index.byParent.has(key)) index.byParent.set(key, []); return index.byParent.get(key); } function buildIndexes(){ index.byId.clear(); index.byParent.clear(); for (const d of state.docs){ index.byId.set(d.id, d); ensureBucket(d.parentId).push(d); } for (const arr of index.byParent.values()) arr.sort(byOrder); } // 조회 헬퍼 내부 구현만 교체 function findDoc(id){ return index.byId.get(id) || null; } function childrenOf(pid){ const bucket = index.byParent.get(pid ?? null) || []; return bucket.slice(); // 외부 변형 방지 } 여기서 나오는 “버킷(bucket)”이라는 단어는, 같은 부모를 가진 문서들을 하나의 묶음 단위로 저장해 둔 배열을 뜻합니다.즉, index.byParent라는 Map 안에 부모 ID → [해당 부모의 자식 문서 목록]이 저장되는데, 이 자식 문서 배열 하나하나가 바로 “버킷”입니다.쉽게 말해, “한 부모 밑에 있는 아이들 폴더를 한 바구니(bucket)에 담아두고, 부모별로 바구니를 따로 관리하는 구조”라고 생각하면 됩니다.이 덕분에 특정 부모 밑의 자식 문서들만 빠르게 찾거나 정렬할 수 있습니다.여기서 중요한 것은 “항상 최신 상태를 유지하는 것”입니다.Map 구조는 빠르지만, 한 곳이라도 갱신을 놓치면 실제 데이터(state.docs)와 인덱스(Map) 사이에 불일치가 생깁니다.따라서 createDoc, updateDoc, moveDoc, archiveDoc, restoreDoc, removeDoc 등 모든 변경 함수가 반드시 인덱스를 함께 갱신해야 합니다.이걸 보장하기 위해 기존 함수를 얇게 감싸서 “상태 변경이 일어날 때마다 인덱스도 함께 갱신되도록” 만드는 것이 안전한 방식입니다.그 원리는 간단합니다.문서를 새로 만들면 byId에 등록하고, 해당 부모의 버킷(즉, 자식 문서 배열)에 새 문서를 정렬된 위치에 끼워 넣습니다.제목이나 순서를 바꾸면 같은 부모 버킷 안에서 한 번 빼고 정렬 기준에 맞게 다시 넣습니다.문서를 다른 부모로 옮길 때는 이전 부모의 버킷에서 제거하고 새 부모의 버킷에 추가합니다.아카이브는 자신과 자식들을 인덱스에서 제거하고, 복원은 반대로 다시 삽입합니다.특히 이동이나 수정 같은 경우는 “이전 부모”와 “새 부모”를 구분해서 둘 다 업데이트해야 버킷이 꼬이지 않습니다.// createDoc 래핑 원리: 상태 반영 → byId 등록 → 부모 버킷에 정렬 삽입 const _createDoc = createDoc; createDoc = function(payload){ const id = _createDoc(payload); const d = state.docs.find(x => x.id === id); index.byId.set(id, d); // 정렬 비용 최소화를 위해 '이진 삽입' 같은 방식으로 들어갈 위치만 찾아 넣는 것이 유리 const arr = ensureBucket(d.parentId); let lo=0, hi=arr.length; while(lo>1; (byOrder(arr[mid], d)렌더링과 관련된 병목은 조회와는 별도의 문제입니다.예를 들어 childrenOf()를 한 함수 안에서 여러 번 부르면 매번 같은 데이터를 다시 가져오게 되므로 불필요한 연산이 쌓이게 됩니다.이럴 때는 한 번만 호출해 변수에 저장해 두고 재사용하는 것이 훨씬 효율적입니다.또한 변경이 일어난 부모 섹션만 부분적으로 다시 그리도록 분기하면 체감 속도가 눈에 띄게 좋아집니다.만약 한 화면에 수천 개의 문서를 펼쳐서 보여줘야 하는 상황이라면, 화면에 실제로 보이는 부분만 렌더링하는 “가상 스크롤(virtual scroll)” 방식을 사용하는 것도 좋은 선택입니다.그리고 normalizeOrders(pid) 함수는 이미 byParent 버킷이 정렬되어 있으니, 굳이 다시 정렬을 반복하지 말고 그 순서대로 0부터 순서 번호를 다시 매기기만 하면 됩니다.제목 검색이나 정렬이 자주 발생한다면, titleLower 속성을 문서에 추가해두어 미리 소문자로 저장해두는 것도 좋습니다.이렇게 하면 매번 toLowerCase()를 호출하지 않아 CPU 사용량을 줄일 수 있습니다.// renderNode 안에서 중복 호출을 없애는 간단한 형태 const kids = childrenOf(doc.id); const hasChildren = kids.length > 0; // hasChildren 사용 및 kids 재사용 … 저장과 렌더 호출은 사용자의 조작이 빠르게 이어질 때 짧은 시간 안에 여러 번 발생할 수 있습니다.이럴 때 localStorage 저장이 너무 자주 일어나면 메인 스레드를 잠시 멈추게 만들어 브라우저가 끊기는 느낌을 줄 수 있습니다.따라서 저장 함수를 바로 실행하지 않고, 약간의 지연을 둬서 일정 시간 동안 요청이 여러 번 들어오면 한 번만 실행되도록 하는 것이 좋습니다. 이를 “쓰로틀(throttle)”이라고 부릅니다.쓰로틀을 추가하면 기능은 그대로 유지하면서도 화면이 훨씬 부드러워집니다.let saveQueued = false; function saveThrottled(){ if (saveQueued) return; saveQueued = true; setTimeout(()=>{ save(); saveQueued = false; }, 250); } 이 구조를 적용할 때 반드시 지켜야 할 점이 있습니다.첫째, load()가 실행된 직후 단 한 번 buildIndexes()를 호출해 초기 인덱스를 반드시 만들어야 합니다.둘째, 문서를 생성하거나 수정하거나 이동하는 등 모든 변경 함수가 반드시 인덱스와 동기화되어야 합니다.셋째, findDoc()과 childrenOf()의 이름과 인자 구조는 그대로 유지해야 기존 코드들이 모두 정상적으로 동작합니다.이렇게 하면 조회는 항상 빠르게 유지되고, 렌더링은 필요한 부분만 다시 그리게 되며, 저장은 쓰로틀 덕분에 부드럽게 이루어집니다.이 방법은 문서가 수백 개 정도밖에 되지 않아도 트리 전체를 자주 렌더링하거나 문서 이동이 잦은 상황에서 큰 효과를 볼 수 있습니다.Map 인덱스를 적용했는데도 여전히 프레임 드랍이 느껴진다면, 먼저 childrenOf()의 중복 호출을 없애고, 전체를 새로 그리는 대신 필요한 부분만 다시 그리도록 바꾸는 것이 비용 대비 효과가 가장 큽니다.데이터가 수천 개로 늘어나거나 한 화면에서 수많은 노드를 동시에 펼쳐야 하는 경우에는 가상 스크롤을 함께 도입하는 것이 좋습니다.저장 쓰로틀은 어떤 규모에서도 유용하므로 기본적으로 활성화해 두는 것을 권장합니다.결국 핵심은 단순합니다.Map 인덱스 두 개(byId, byParent)를 만들어 문서를 빠르게 찾고, 중복 호출을 제거해 DOM 연산량을 줄이며, 저장과 렌더링을 쓰로틀로 제어해 메인 스레드가 버벅이지 않게 만드는 것입니다.이 세 단계를 함께 적용하면, 문서 수가 수천 개로 늘어나더라도 현재 코드 기반에서 부드럽고 안정적인 성능을 유지할 수 있습니다.또 한 가지 덧붙이자면, 실제로 데이터를 장기간 보관하거나 탭 간 동기화가 필요한 상황이라면, 메모리 기반 Map 인덱스만으로는 부족할 수 있습니다. 이런 경우에는 IndexedDB 같은 브라우저 내장 데이터베이스를 함께 활용해 인덱스를 디스크에 캐시 형태로 저장해 두면, 새로고침 후에도 빠른 초기 조회가 가능합니다. 이렇게 하면 메모리와 디스크 양쪽에서 균형 잡힌 구조로 더 안정적인 성능을 얻을 수 있습니다.감사합니다!
- 0
- 2
- 21
Q&A
CSS까지만 지연에 영향을 주는건가요?
안녕하세요, 정수지님 🙂DOM과 CSSOM이 결합되어 렌더 트리가 만들어진 다음에야 실제 화면이 그려진다는 원리를 정확히 이해하고 계시네요. 이 개념 위에 script 태그의 동작 방식을 더 깊이 이해하면, 브라우저가 어떻게 렌더링을 멈추거나 이어가며, async와 defer 속성이 실제로 CRP(Critical Rendering Path, 중요 렌더링 경로) 에 어떤 영향을 주는지를 완전히 체감하실 수 있습니다.브라우저는 HTML 문서를 위에서 아래로 읽어가며 DOM(Document Object Model) 을 생성하고, 동시에 외부 CSS 파일을 요청해 CSSOM(CSS Object Model) 을 만듭니다.이 두 구조가 모두 준비되어야 브라우저는 Render Tree를 만들 수 있습니다.Render Tree가 완성되면 그때서야 Layout(요소의 위치 계산)과 Paint(픽셀 그리기) 단계를 거쳐 화면을 표시하게 됩니다.이 일련의 과정 전체를 Critical Rendering Path라고 부릅니다.여기서 “HTML을 읽는다”는 것은 사람의 눈으로 읽는 것이 아니라,브라우저가 , 같은 태그를 하나씩 해석해서 트리 구조로 쌓는 과정을 의미합니다.그리고 CSS 파일은 이 트리 위에 “색, 크기, 위치” 정보를 입히는 설계도라고 생각하시면 됩니다.여기서 한 가지 중요한 사실이 있습니다.CSS 파일 또한 렌더링 경로를 차단(blocking) 할 수 있습니다.브라우저는 를 만나면 CSS 파일을 다운로드하면서, JS 실행을 잠시 멈춥니다. 왜냐하면 자바스크립트가 getComputedStyle()이나 DOM 변경을 통해 스타일에 영향을 줄 수 있기 때문입니다. 즉, CSS가 완전히 로드되고 CSSOM이 만들어질 때까지는 JS 실행이 연기될 수 있습니다.여기서 “차단(blocking)”이란, 브라우저가 다른 일을 잠시 멈추고“이거 먼저 끝내야지!” 하고 우선순위를 주는 상태를 말합니다.예를 들어, 요리를 하다 말고 갑자기 물을 끓여야 해서 모든 일을 멈추는 것과 비슷합니다.CSS 파일을 다 불러와야 브라우저가 다시 HTML을 계속 읽을 수 있기 때문입니다.(정확히는, CSS는 렌더링을 차단하고, 동기 실행을 지연시킵니다.HTML 파싱 자체를 즉시 멈추는 것은 아니지만,곧바로 이어지는 스크립트 때문에 실제로는 파싱이 잠시 멈춘 것처럼 보입니다.)이때 HTML 안에 태그가 등장하면,브라우저는 “여기서 JavaScript를 실행해야 한다”고 인식합니다.문제는 이 스크립트가 파서-블로킹(parser-blocking) 이라는 점입니다.즉, 브라우저는 스크립트를 다운로드하고 실행하는 동안 HTML 파싱을 멈춥니다.여기서 ‘파싱을 멈춘다’는 건, 브라우저가 HTML 문서를 더 이상 아래로 내려가지 않고“이 스크립트를 먼저 실행할게요” 하고 일시 정지하는 상태입니다.그래서 JS가 오래 걸리면, HTML의 나머지 부분(예: )이 늦게 그려지는 겁니다.예를 들어 다음과 같은 코드가 있다고 해보겠습니다. Hello, world! This text might not appear immediately. 이 경우 브라우저는 main.js를 다운로드하고 실행할 때까지 HTML 파싱을 중단합니다.main.js가 네트워크에서 내려받는 데 시간이 오래 걸린다면, 과 는 그동안 화면에 그려지지 않습니다.즉, 스크립트의 실행 순서뿐 아니라 CRP 자체가 지연되는 것이죠.“네트워크에서 내려받는다”는 것은, 브라우저가 인터넷을 통해 main.js 파일을 서버에서 요청해서 받아오는 과정입니다.만약 인터넷 속도가 느리면, 화면이 하얗게 멈춰 있는 시간이 길어질 수 있습니다.이 문제를 해결하기 위해 등장한 속성이 async와 defer입니다.async를 붙이면 브라우저는 HTML을 파싱하면서 스크립트를 병렬로 다운로드합니다.여기서 “병렬”이란 동시에 여러 일을 하는 것입니다.즉, HTML을 계속 읽으면서 백그라운드에서는 JS 파일도 내려받습니다.하지만 다운로드가 완료되는 순간, 파싱을 잠시 멈추고 스크립트를 즉시 실행합니다.즉, 다운로드는 비동기(=따로따로 동시에)지만 실행은 즉시 일어나므로,실행 시점마다 파서가 잠깐씩 중단될 수 있습니다.또한 여러 개의 async 스크립트가 있을 경우,도착한 순서대로 실행되므로 실행 순서가 보장되지 않습니다.이 말은, 먼저 내려받은 파일이 먼저 실행된다는 뜻으로,코드 사이에 의존 관계가 있으면 문제가 생길 수 있습니다.반면 defer는 다운로드는 비동기이지만 실행은 HTML 파싱이 끝난 직후에 이루어집니다.즉, 모든 DOM 파싱이 완료된 다음, 문서의 순서대로 스크립트가 실행됩니다.이렇게 되면 HTML 파싱을 막지 않기 때문에, 초기 렌더링 속도에 거의 영향을 주지 않습니다.쉽게 말해 async는 “먼저 도착하면 바로 실행”,defer는 “HTML 다 읽고 나서 한꺼번에 실행”이라고 기억하시면 됩니다.둘 다 HTML과 JS를 같이 다운로드하지만, 실행 시점이 다릅니다.현대 자바스크립트에서는 type="module" 속성을 사용할 수도 있습니다.모듈은 기본적으로 defer와 같은 동작 방식을 취하므로,파싱을 방해하지 않고 HTML이 모두 읽힌 뒤에 실행됩니다.단, 모듈 간의 의존 관계에 따라 여러 파일이 병렬로 다운로드되며,실행은 import 순서를 따릅니다.이 경우 defer 속성을 함께 붙이더라도 무시됩니다.(예외적으로, 모듈 스크립트에 async 속성을 함께 붙이면,다운로드가 끝나는 즉시 실행되어 defer와 달리 파싱 완료 전에 실행될 수도 있습니다.)추가로 import() 문법을 사용하면 완전히 비동기적 실행이 가능하며,이는 파서나 렌더링을 전혀 블로킹하지 않습니다.즉, HTML과 상관없이 나중에 “필요할 때만” JS를 불러오는 방식입니다.즉, async와 defer는 단순히 스크립트의 “실행 순서”만 바꾸는 속성이 아니라,HTML 파싱과 렌더링의 타이밍 자체를 바꾸는 성능 조절 도구입니다.실무에서 자주 쓰이는 시나리오를 하나 만들어 보겠습니다.예제 상황:전자상거래 페이지를 예로 들어보죠.사용자가 index.html을 처음 방문할 때, 메인 이미지가 빠르게 보여야 전환율이 높습니다.하지만 개발자가 다음과 같이 스크립트를 작성했다고 가정해봅시다. My Shop (사진) Buy Now 이 코드는 analytics.js와 main.js를 순차적으로 블로킹 방식으로 실행하므로,사용자가 페이지를 열면 메인 이미지가 한참 동안 보이지 않습니다.“순차적 실행”이란, 첫 번째 스크립트를 완전히 끝내야 두 번째 스크립트가 실행된다는 뜻입니다.즉, 한 번에 한 일만 하는 거죠.그래서 JS가 길거나 네트워크가 느리면, 이미지조차 늦게 보이게 됩니다.이를 수정하면 이렇게 됩니다. My Shop (사진) Buy Now 이제 두 스크립트가 CRP에 미치는 영향은 완전히 달라집니다.브라우저가 HTML을 읽으면서 동시에 CSS와 두 스크립트를 병렬로 다운로드합니다.(즉, 세 가지 일이 동시에 일어납니다: HTML 읽기, CSS 받기, JS 받기)analytics.js는 async이므로 도착 즉시 실행되지만,DOM에 의존하지 않는 분석 코드라 페이지 구조에는 영향을 주지 않습니다.main.js는 defer이므로, HTML 파싱이 모두 끝난 후에 실행됩니다.DOM이 완전히 준비된 뒤 실행되므로 안전하며, 첫 페인트에도 영향을 주지 않습니다.그 결과, 사용자는 메인 이미지와 버튼을 즉시 볼 수 있고,동시에 백그라운드에서는 스크립트 로딩이 병렬로 진행됩니다.이런 변화 하나로 첫 페인트(First Paint) 와 DOMContentLoaded 이벤트 시점이 훨씬 빨라집니다.참고로, defer 스크립트는 DOM 파싱이 끝난 직후 실행되고 나서DOMContentLoaded 이벤트가 발생합니다.반면 async 스크립트는 다운로드 완료 시점에 즉시 실행되므로,어떤 것은 DOMContentLoaded보다 먼저, 어떤 것은 나중에 실행될 수도 있습니다.따라서 DOMContentLoaded 전후 실행 타이밍이 중요한 로직이라면 defer가 훨씬 안정적입니다.다음은 이해를 돕기 위한 간단한 비교 요약입니다.기본 동기적 실행 방식으로, 파일을 바로 실행하며 순서를 보장하지만 HTML 파싱을 완전히 멈춥니다. 가장 느린 방식입니다.async비동기 다운로드 방식으로, HTML을 읽는 도중 JS를 동시에 내려받고, 다운로드가 끝나는 즉시 실행합니다. 실행 순서는 보장되지 않으며, HTML 파싱이 잠깐씩 멈출 수 있습니다. 광고나 통계 코드처럼 독립적인 코드에 적합합니다.defer비동기 다운로드 방식이지만, HTML이 전부 파싱된 이후에 문서 순서대로 실행됩니다. HTML 파싱을 멈추지 않기 때문에 렌더링 속도에 거의 영향을 주지 않습니다. 메인 코드에 적합합니다.type="module"모듈 시스템을 사용하는 최신 방식으로, 자동으로 defer처럼 동작하며 여러 파일을 동시에 다운로드합니다. import한 순서대로 실행됩니다. 단, async 속성을 함께 주면 도착 즉시 실행될 수도 있습니다.요약하자면, 기본 스크립트는 차단이 심하고 느리고,async는 빠르지만 순서가 불안정하며,defer와 type="module"은 안정적이면서 렌더링을 방해하지 않습니다.표에서 나왔던 “동기”는 한 번에 한 가지 일만 하는 것이고,“비동기”는 여러 가지 일을 동시에 처리하는 것을 의미합니다.“차단 없음”은 브라우저가 멈추지 않고 계속 HTML을 읽을 수 있다는 뜻입니다.정리하자면, async와 defer는 단순한 순서 옵션이 아니라,브라우저의 렌더링 경로를 최적화하는 속성입니다.async는 다른 코드와 의존성이 없는 외부 스크립트(예: 광고, 분석, 채팅 위젯 등)에 적합하고,defer나 type="module"은 메인 애플리케이션 코드에 적합합니다.이 원리를 이해하면,“어떤 스크립트를 언제 실행해야 가장 빠르게 사용자에게 첫 화면을 보여줄 수 있을까?”라는 질문에 직접 답을 낼 수 있게 됩니다.마지막으로, Chrome DevTools의 Network 탭에서실제 async/defer 스크립트의 다운로드 타이밍과 실행 시점을 관찰할 수 있습니다.async 스크립트는 ‘Received’ 시점에 즉시 실행되며,defer 스크립트는 DOMContentLoaded 직전에 실행되는 것을 확인할 수 있습니다.만약 정수지님께서 실제로 작성 중인 페이지의 구조를 보여주신다면,어디에 어떤 속성을 적용하면 최적의 성능이 나올지구체적인 리팩터링 예시까지 함께 도와드릴 수 있습니다.감사합니다😊😊
- 0
- 2
- 16
Q&A
json 대신 로그인, 회원가입 일때 db 연결 및 data 사용하려면 어떻게 하나요?
안녕하세요 eju님 🙂JSON 파일 대신 데이터베이스(DB)를 사용해 로그인과 회원가입 기능을 만들고 싶으시다면, 전체적인 그림을 이렇게 이해하시면 됩니다.우선 JSON 파일은 데이터를 “그때그때 파일로 직접 저장하는 방식”이에요. 간단해서 배우기에는 좋지만, 사용자가 많아지거나 여러 요청이 동시에 들어오면 금방 한계가 생깁니다. 반면 DB(데이터베이스)는 수많은 데이터를 빠르고 안정적으로 보관하고 꺼내 쓸 수 있는 전용 저장소예요. 쉽게 말해, “데이터를 안전하게 관리해주는 똑똑한 금고”라고 생각하시면 됩니다.Node.js와 Express 환경에서 자주 쓰는 DB에는 세 가지 대표적인 종류가 있습니다.먼저 MySQL과 PostgreSQL은 표(테이블) 형태로 데이터를 저장하는 관계형 데이터베이스입니다. 회원 정보, 결제 내역, 주문 기록처럼 구조가 정해진 데이터를 다룰 때 강력하고 안정적이에요.반면 MongoDB는 NoSQL 데이터베이스로, JSON처럼 생긴 문서(document) 단위로 데이터를 저장합니다. 구조가 자유롭고 변경이 잦은 서비스, 예를 들어 SNS, 게시판, 로그 기록 에서 특히 자주 사용됩니다. 실무에서는 여전히 PostgreSQL이나 MySQL이 가장 많이 쓰이고, 빠르게 기능을 만들어야 하는 스타트업이나 학습용 프로젝트에서는 MongoDB도 인기가 많습니다.Node.js + Express에서는 이런 DB와 연결하기 위해 중간 도구(라이브러리)를 사용합니다.PostgreSQL이나 MySQL은 Prisma나 Sequelize, MongoDB는 Mongoose를 가장 많이 써요.이 도구들이 “DB에 데이터 넣기 / 꺼내기”를 자바스크립트 코드 몇 줄로 간단하게 바꿔 줍니다.간단히 회원가입을 예로 들면, 사용자가 이름·이메일·비밀번호를 입력했을 때 서버는 다음 순서로 동작합니다.입력값이 비어 있지 않은지, 이메일 형식이 맞는지 검사합니다.비밀번호를 bcrypt 라이브러리로 암호화(해시)합니다. 이렇게 하면 실제 비밀번호가 노출되지 않고도 로그인 검증이 가능합니다.암호화된 비밀번호와 함께 DB에 안전하게 저장합니다.로그인 과정은 반대로,사용자가 입력한 이메일을 DB에서 찾아보고,저장된 암호화된 비밀번호와 입력된 비밀번호를 bcrypt.compare()로 비교합니다.두 값이 일치하면 로그인 성공으로 처리합니다.로그인이 성공하면 서버는 세션/쿠키 방식이나 JWT 토큰을 사용해 로그인 상태를 유지합니다. 세션은 “서버가 로그인표를 들고 있는 방식”이고, JWT는 “서버가 서명된 출입증을 만들어 클라이언트가 직접 들고 있는 방식”이에요.보안은 여기서 매우 중요합니다.1. 비밀번호는 절대로 원문 그대로 저장하지 않고 반드시 해시 형태로 저장해야 합니다.2. DB 주소와 계정 정보는 코드에 직접 쓰지 말고 .env 파일에 따로 숨겨야 합니다.3. 입력값을 꼼꼼히 검사해서 비정상적인 데이터(빈칸, 이상한 형식 등)는 바로 거절해야 합니다.이 세 가지만 지켜도, JSON 파일 대신 DB를 사용해도 훨씬 안전하고, 실제 서비스에 가까운 구조를 만들 수 있습니다.마지막으로, 실무에서는 PostgreSQL이나 MySQL이 표준으로 가장 널리 쓰이고,MongoDB는 빠르게 바뀌는 데이터나 JSON 구조의 서비스에 유리합니다.처음 공부하실 때는 MongoDB가 이해하기 쉬워서 입문용으로 좋고,나중에 회사나 프로젝트 단위로는 PostgreSQL을 연습해두시면 실무에 훨씬 도움이 됩니다.이 부분은 앞으로 제가 준비 중인 Node.js + Express 실전 강의에서 다룰 예정입니다.JSON 저장에서 DB 연결로 넘어가는 과정, Prisma·Mongoose 사용법, 비밀번호 암호화와 로그인 세션 관리까지 전부 하나씩 보여드릴 계획이에요. 현재 교재와 함께 실습 중심 강의로 준비 중이며, 가능한 한 빠른 시일 내에 출시하겠습니다.감사합니다.
- 0
- 1
- 11
Q&A
보안에 취약 한가요?
안녕하세요 eju님 🙂,어떤 상황에서의 보안을 말씀하시는지, 그리고 서버 보안, 데이터 전송 보안, 사용자 인증 보안 등 어떤 종류의 보안을 의미하시는지 구체적으로 알려주시면 보다 정확하게 안내드릴 수 있을 것 같습니다. 감사합니다.
- 0
- 1
- 16
Q&A
테스트시 포스트맨 외 테스트 할수 있는 방법이 있을까요?
안녕하세요 eju님 🙂Postman은 이미 전 세계 개발자들이 표준처럼 사용하는 매우 좋은 테스트 도구입니다. 직관적이고 안정적이며, 팀 단위로도 협업하기 쉬워서 지금도 실무에서 가장 많이 사용됩니다. 그래서 새로운 도구를 찾기보다는, Postman을 기본으로 꾸준히 쓰는 것 자체가 좋은 선택이에요. 다만 상황에 따라 “Postman이 없어도 테스트할 수 있는 방법”이 여러 가지 있으니, 그 배경을 함께 설명드릴게요.먼저, Postman과 가장 비슷한 대체 도구는 Insomnia입니다.Insomnia는 인터페이스가 조금 더 단순하고 가볍기 때문에, 처음 배우는 분들이 부담 없이 쓸 수 있습니다. REST API뿐 아니라 GraphQL이나 gRPC 같은 최신 기술도 지원하기 때문에, 나중에 프로젝트 규모가 커져도 그대로 사용 가능합니다. Postman이 조금 복잡하거나 무겁게 느껴질 때 좋은 선택입니다.다음은 브라우저나 터미널(명령줄)을 이용하는 방법입니다.간단한 테스트라면 브라우저 주소창에 API 주소를 직접 입력해도 됩니다. 예를 들어 http://localhost:3000/api/users를 입력하면 서버가 주는 응답을 바로 볼 수 있습니다. 조금 더 기술적으로는 curl이라는 명령어 도구를 사용할 수도 있습니다. 이건 대부분의 컴퓨터에 기본으로 들어 있어서, 별다른 설치 없이 터미널에 한 줄만 입력해도 서버의 응답을 확인할 수 있습니다. 예를 들어 “이 API가 실제로 잘 동작하는지”나 “서버가 응답을 보내는 데 걸리는 시간”을 간단히 점검할 때 좋습니다.좀 더 체계적으로, 코드로 테스트를 자동화하는 방식도 있습니다.Node.js와 Express.js 환경에서는 Jest, Mocha, Supertest 같은 도구를 많이 씁니다.이 방법은 사람이 직접 클릭하지 않고, “이 API를 호출했을 때 응답이 이런 모양이면 성공이다”를 미리 코드로 적어두는 방식이에요. 버튼 한 번으로 전체 API를 동시에 확인할 수 있어서, 개발이 어느 정도 익숙해졌을 때 도입하면 좋습니다. 예를 들어 로그인, 회원가입, 상품목록 API 같은 걸 한꺼번에 돌려서 “어디서 실패했는지”를 자동으로 알려줍니다. 기업 환경에서도 이런 자동화 테스트는 배포 전에 거의 필수로 사용됩니다.또 하나 기억하시면 좋은 건 서버 상태를 주기적으로 자동 검사해 주는 모니터링 도구입니다.Postman도 “Monitor” 기능을 자체적으로 제공합니다. 이걸 설정해 두면, 예를 들어 “10분마다 /api/health 엔드포인트를 검사해서 서버가 살아 있는지 확인” 같은 작업을 자동으로 해 줍니다.만약 서버가 멈추면 바로 이메일이나 슬랙으로 알려줘서 빠르게 대응할 수 있습니다.이외에도 Hoppscotch, Paw, K6, Newman 같은 모니터링·테스트 도구들도 있습니다.특히 Newman은 Postman에서 만든 테스트 스크립트를 그대로 가져와서 터미널에서 자동으로 돌릴 수 있게 해 주기 때문에, Postman을 그대로 확장해서 쓰는 느낌에 가깝습니다.정리하면, Postman은 지금도 충분히 훌륭한 도구이고 그대로 사용하셔도 전혀 문제 없습니다.다만 필요에 따라 다음과 같은 선택지를 알고 계시면 좋습니다.Insomnia — Postman보다 가볍고 단순한 대체 도구브라우저 주소창 또는 curl — 가장 빠르고 설치 없이 가능한 간단 테스트Jest, Supertest, Mocha — 코드로 자동 테스트를 돌릴 수 있는 실무형 방식K6, Hoppscotch, Newman, Postman Monitor — 서버를 주기적으로 자동 검사하는 방식결국 Postman은 기본이자 중심 도구입니다.그대로 사용하면서 위의 방법들을 “보조 수단”으로 조금씩 익히면, 테스트 효율이 훨씬 높아집니다.즉, “Postman을 버리는 게 아니라, Postman을 중심으로 확장한다”는 생각으로 접근하시면 가장 좋습니다.감사합니다.
- 1
- 1
- 13
Q&A
404, 500 에러 처리 외에 특정 개발 구문에서 에러 발생했을때 찾는 방법이 있을까요?
안녕하세요 eju님 🙂,404나 500 같은 에러는 “페이지를 찾을 수 없음”, “서버 내부 문제”처럼 이미 Express가 기본적으로 처리해 주는 에러입니다. 하지만 실제 개발 중에는 “특정 코드 줄에서 갑자기 터지는 에러”를 찾아야 하는 경우가 훨씬 많습니다. 이런 상황을 쉽게 찾는 실무용 방법을 아주 기초부터 설명드릴게요.가장 먼저 알아두실 건 “로그(log)”입니다. 로그는 프로그램이 실행되면서 남기는 일기라고 생각하시면 됩니다. Node.js와 Express에서는 보통 Winston이나 Pino 같은 도구를 써서 로그를 남깁니다.이걸 켜 두면, 에러가 났을 때 “어느 파일의 몇 번째 줄에서 어떤 요청이 들어왔을 때 터졌는지”를 바로 볼 수 있습니다. 예를 들어 “/login 페이지로 요청이 들어왔는데, username이 비어 있어서 오류 발생” 같은 식으로 남죠.이 로그는 나중에 콘솔이나 파일에서 그대로 읽어볼 수 있습니다.다음은 “디버깅(debugging)”이에요.디버깅은 코드의 실행을 잠깐 멈춰서, 변수 안에 무슨 값이 들어 있는지 직접 확인하는 작업이에요.Node.js는 --inspect 모드라는 기능을 제공합니다.이걸 켜면 크롬 브라우저나 VS Code에서 프로그램을 “일시정지”하고, 특정 코드 줄을 확인하거나 변수값을 바로 볼 수 있습니다. 예를 들어 “이 if문 안이 실행되는지?” 혹은 “여기서 받은 데이터가 비어 있는지?” 같은 걸 눈으로 확인할 수 있어요. 초보 단계에서는 console.log( )로 중간중간 출력해 보는 것도 디버깅의 한 형태이지만, 나중에는 이렇게 프로그램을 멈춰서 보는 방식이 훨씬 효율적입니다.세 번째는 “에러 추적 서비스”입니다.이건 조금 더 자동화된 방법인데, Sentry, Bugsnag, Rollbar 같은 서비스가 대표적이에요.이 도구들을 Express에 한 줄만 연결해 두면, 에러가 발생할 때마다 자동으로 서버에서 정보를 모아서 웹사이트로 보내줍니다. 그럼 나중에 브라우저에서 “언제, 어떤 요청에서, 무슨 이유로 에러가 났는지”를 한눈에 볼 수 있고, 에러가 반복되면 그 빈도나 경향까지 분석해 줍니다. 즉, 직접 콘솔을 보지 않아도 자동으로 알려주는 “에러 알림 시스템”이라고 생각하시면 됩니다.그리고 네 번째로는 “입력값 검증(validation)”입니다.사실 많은 에러는 서버 로직 자체보다, 사용자가 잘못된 값을 보내서 생깁니다.이걸 미리 잡아주는 방법이 “요청 검증 라이브러리”입니다.대표적으로 Joi, Celebrate, Zod 같은 도구가 있어요.예를 들어 “숫자여야 하는데 문자열이 들어오면 아예 실행하지 않고 400 오류로 깔끔하게 돌려주는” 식이죠. `이렇게 하면 “내 코드 내부 버그”와 “입력 잘못으로 인한 오류”를 구분하기 쉬워집니다.마지막으로 “에러 핸들러”라는 걸 꼭 하나 만들어 두시면 좋습니다.Express는 코드 어디에서든 에러가 생기면 마지막에 있는 에러 처리 구문으로 모아줍니다.그곳에서 에러 메시지와 스택(어느 파일 몇 줄에서 났는지)을 출력하거나, 사용자에게 보기 좋은 메시지를 보낼 수 있습니다. 이걸 써두면 갑작스러운 오류가 발생해도 프로그램 전체가 멈추지 않고, 원인을 안전하게 기록할 수 있습니다.정리하자면,로그 도구로 일기처럼 실행 상황을 남기고,디버깅 모드로 코드 중간을 직접 들여다보며,Sentry 같은 에러 추적 서비스로 자동 기록을 받고,입력 검증 라이브러리로 잘못된 요청을 미리 막고,에러 핸들러로 예외를 한곳에서 관리하면,“특정 코드에서 어떤 이유로 에러가 발생했는지”를 쉽게 찾아낼 수 있습니다.지금은 복잡해 보여도, 사실 각각의 도구는 “로그를 잘 남기고, 에러를 보기 쉽게 만들자”라는 같은 원리를 공유합니다. 처음에는 console.log로 시작하시고, 익숙해지면 Sentry나 Winston처럼 조금씩 자동화된 도구로 확장하시면 됩니다.감사합니다.
- 0
- 1
- 18
Q&A
ejs 와 어떤 개발언어로 조합해서 사용했을때 성능이 좋을까요?
안녕하세요 eju님 🙂,EJS는 Embedded JavaScript Templates의 약자로, HTML 안에 자바스크립트 코드를 끼워 넣어 동적으로 완성된 웹페이지를 만드는 템플릿 엔진입니다. 예를 들어 처럼 작성하면, EJS가 해당 구문 안의 자바스크립트 표현식을 평가해 실제 값으로 치환한 HTML을 만들어 줍니다. 이 과정은 브라우저가 아닌 서버에서 이루어지며, Node.js 환경에서 실행됩니다. Node.js는 브라우저 외부에서도 자바스크립트를 실행할 수 있도록 만든 런타임 환경으로, 구글 크롬의 V8 엔진을 그대로 사용합니다. 따라서 EJS가 생성한 자바스크립트 코드는 Node.js에서 별도의 변환 과정 없이 바로 실행됩니다.EJS는 단순히 HTML 문자열을 출력하는 도구가 아니라, 내부적으로 템플릿을 한 번 파싱하여 자바스크립트 함수 형태로 변환한 뒤 실행합니다. 즉, 사람이 작성한 .ejs 파일은 내부적으로 하나의 함수로 컴파일되어 실행되는 구조를 가지고 있습니다. 이 과정은 언어 번역을 위한 트랜스파일 과정이 아니라, HTML과 자바스크립트 표현식을 결합하기 위한 단순한 텍스트 변환 단계입니다. 그렇기 때문에 Babel 같은 별도의 번역 도구는 필요하지 않습니다. 즉, 자바스크립트로 작성된 템플릿이 자바스크립트로 그대로 실행된다는 점에서 매우 효율적입니다.Node.js와 EJS가 자연스럽게 함께 동작하는 이유는 JSON과 자바스크립트 객체의 완전한 호환성 덕분이기도 합니다. 서버에서 res.render('profile', { name: '홍길동' })처럼 단순히 객체를 전달하면, EJS 내부에서는 그 데이터를 변환 없이 바로 사용할 수 있습니다. JSON 문법이 자바스크립트 객체 리터럴과 동일하기 때문에 별도의 직렬화나 역직렬화 과정이 필요하지 않습니다. 이런 구조 덕분에 서버에서 생성된 데이터를 템플릿으로 전달할 때 중간 단계가 생략되어 코드가 단순하고 처리 속도도 빠릅니다.Node.js의 핵심 엔진인 V8은 JIT(Just-In-Time) 컴파일러를 내장하고 있습니다. 이는 자주 실행되는 코드를 기계어 수준으로 최적화하여 실행 속도를 높이는 기술로, EJS처럼 서버에서 반복적으로 실행되는 코드에서도 성능 이점을 제공합니다. 또한 Express.js는 EJS 템플릿 렌더링을 위한 캐시 기능을 지원합니다. 한 번 컴파일된 EJS 템플릿은 메모리에 저장되어 다음 요청 시 재사용되며, 프로덕션 모드에서는 기본적으로 이 캐시가 활성화됩니다. 즉, 서비스 환경에서는 한 번 파싱된 템플릿 함수가 그대로 재사용되어 렌더링 속도가 점점 더 빨라집니다.결과적으로 EJS와 Node.js의 조합은 같은 언어 위에서 움직이므로 불필요한 변환이 없고, JSON 데이터가 자연스럽게 연결되며, V8 엔진의 최적화와 Express의 캐시 기능 덕분에 빠르고 안정적으로 동작합니다. 이런 이유로 EJS는 Node.js 환경에서 가장 자연스럽고 효율적인 템플릿 엔진 중 하나로 평가됩니다. 다만 EJS는 Node.js 전용으로 만들어진 것은 아니며, 브라우저에서도 독립적으로 실행할 수 있는 범용 자바스크립트 템플릿 엔진이라는 점을 함께 기억하셔야 합니다.또한, 다른 언어들에서도 이와 유사한 방식의 템플릿 엔진이 존재합니다. 예를 들어 Python의 Jinja2, PHP의 Twig, Ruby의 ERB(Embedded Ruby), 그리고 Java의 JSP(JavaServer Pages) 등이 그 대표적인 예입니다. 이들은 모두 “코드와 마크업을 섞어 최종 HTML을 생성하는” 구조를 가지며, 각 언어 환경에서 EJS와 동일한 역할을 수행합니다. 즉, 자바스크립트 생태계에서는 EJS가 이 역할을 맡고, Python에서는 Jinja2가, Ruby에서는 ERB가, Java에서는 JSP가 같은 기능을 담당한다고 보실 수 있습니다. 이러한 템플릿 엔진들은 모두 언어별 런타임과 긴밀히 연결되어 작동하기 때문에, EJS가 Node.js와 자연스럽게 어우러지는 것처럼 다른 언어의 템플릿 엔진들도 각자의 환경에서 최적화된 형태로 실행됩니다.감사합니다.
- 0
- 1
- 13