GROWTH LOG STORIES

함께 만든 성장을,
기록으로 연결합니다.

AI와 함께 배우고, 직접 만들고, 서로 나눈
그로스로그 멤버들의 생생한 성장 기록을 만나보세요.

성장 기록
5기+이어진 커뮤니티
HEALTHIO
PROJECT

사진 한 장으로 시작하는 건강 루틴, Health IO

대표 이야기 읽기

ALL STORIES

성장의 순간을
한곳에서 만나보세요.

프로젝트의 시작부터 모임에서 나눈 인사이트까지,
그로스로그가 함께 만든 기록을 모았습니다.
개의 성장 기록

성장일지

[GL 2기] 브라우저가 화면을 그리는 순서, 그리고 빌드 결과물이 100KB 줄어든 이유 (성장일지 10회차 큐레이션) 🌱

GROWTH LOG🌱 2026. 8. 7. 04:33

안녕하세요! 그로스로그 입니다!😊 🌱

주소창에 URL을 넣고 화면이 그려지기까지의 전 과정, 그리고 옵션 하나를 끄고 켜서 빌드 결과물을 직접 비교해본 기록. 2기 10회차입니다.

성장일지는 멤버들이 2주에 한 번씩 자신의 성장을 기록하는 활동이에요. 매 회차 운영진이 모든 글을 읽고, 그중 특히 마음에 남은 글을 함께 골라 큐레이션합니다.

 

01

🖥️ 브라우저 렌더링 과정 톺아보기

이종경 님

이종경 님 원문에 실린 이미지
이런 내용이에요
주소창에 URL을 입력한 순간부터 화면에 픽셀이 찍히기까지의 전 과정을 순서대로 따라간 글입니다.
네트워크 요청(DNS 조회 → TCP 핸드셰이크 → TLS 협상 → 응답), 구문 분석(DOM·CSSOM 생성), 렌더 트리 합성, 레이아웃, 페인트까지요.
그리고 마지막에 리플로우와 리페인트를 다루며, 어떤 CSS 속성이 무엇을 다시 일으키는지까지 정리했습니다. 프론트엔드 면접 단골 주제이자 성능 최적화의 기초입니다.

"브라우저가 어떻게 동작하는가"는 아는 것 같으면서도 순서대로 설명하려면 막히는 주제입니다. 이 글은 그 순서를 잡아줍니다.

조금 더 들어가 보면

글의 첫 문장이 전체를 관통합니다. "대부분의 브라우저는 싱글 스레드입니다."

이 한 줄에서 뒤의 모든 게 파생됩니다. 화면을 그리는 일, 자바스크립트를 실행하는 일, 사용자 클릭에 반응하는 일 — 이 전부를 한 사람이 순서대로 처리합니다.

그래서 하나가 오래 걸리면 나머지가 전부 멈춥니다. 무거운 계산을 돌리는 동안 버튼이 안 눌리는 이유, 스크롤이 끊기는 이유가 전부 여기예요. 렌더링 시간이 중요한 건 예쁘게 그리기 위해서가 아니라, 그 시간 동안 브라우저가 다른 일을 못 하기 때문입니다.

🌐 화면이 나오기 전에 오가는 네 번의 왕복

글은 네트워크 단계를 4번의 왕복으로 정리합니다. 이 부분이 특히 유용해요.

1. DNS 조회example.com이 실제로 어느 IP인지 물어봅니다. 글이 짚은 디테일이 좋습니다. www.example.comcdn.example.com은 각각 따로 조회된다고요. 이미지를 CDN에 두면 도메인이 하나 더 늘고, 그만큼 조회가 한 번 더 생깁니다.

2. TCP 핸드셰이크 — 3-way handshake로 연결을 엽니다.

3. TLS 협상 — HTTPS라면 여기서 암호화 방식을 맞춥니다. 왕복이 한 번 더 늘죠.

4. 응답 — 이제야 GET 요청을 보내고 HTML을 받습니다.

HTML 첫 글자를 받기까지 이미 네 번을 오간 셈입니다. 서버가 아무리 빨라도 이 왕복 시간은 그대로 남아요. 물리적 거리가 성능에 영향을 주는 이유이고, CDN이 존재하는 이유이기도 합니다.

🌳 두 그루의 나무, 그리고 스크립트라는 브레이크

HTML을 받으면 파싱이 시작됩니다. 여기서 나무 두 그루가 자랍니다. HTML에서 DOM 트리, CSS에서 CSSOM 트리요.

글이 짚은 자원별로 다른 동작이 실무에서 가장 중요한 부분입니다.

· 이미지 같은 논 블로킹 자원을 만나면 → 요청만 걸어두고 파싱을 계속합니다.

· CSS 파일을 만나면 → HTML 파싱은 안 막지만, 자바스크립트 실행은 막습니다.

· asyncdefer가 없는 <script>를 만나면 → HTML 파싱 자체를 중단시킵니다.

세 번째가 병목의 주범입니다. 파서가 스크립트를 만나면 거기서 멈춰 서서, 다운로드하고 실행할 때까지 그 아래 HTML은 읽지도 않습니다. <head>에 스크립트를 여러 개 두면 화면이 늦게 뜨는 이유가 이거예요.

두 번째의 이유도 글에 나옵니다. 자바스크립트가 CSS 속성을 조작할 수 있기 때문입니다. 스타일이 확정되지 않은 상태에서 스크립트가 값을 읽으면 엉뚱한 답을 얻겠죠. 그래서 브라우저가 순서를 강제합니다.

그다음 DOM과 CSSOM이 합쳐져 렌더 트리가 되고, 레이아웃 단계에서 모든 상대 값(%, em, vw)이 절대 픽셀로 환산됩니다. 그리고 페인트에서 실제 픽셀이 찍혀요.

🔁 리플로우와 리페인트 — 여기가 실전입니다

이 글에서 가장 실용적인 부분입니다.

화면이 한 번 그려진 뒤에도 사용자는 계속 뭔가를 합니다. 요소가 추가되고, 스타일이 바뀌고, 애니메이션이 돌죠. 그때마다 브라우저는 다시 계산합니다.

· 리플로우(Reflow) — 위치나 크기가 바뀌어 레이아웃을 다시 계산하는 것

· 리페인트(Repaint) — 계산된 걸 화면에 다시 그리는

글이 짚은 핵심 관계가 이겁니다. 리플로우가 일어나면 리페인트는 반드시 따라옵니다. 그런데 리페인트만 일어나는 경우는 있습니다.

무슨 뜻일까요. 색만 바꾸면 배치는 그대로니까 다시 그리기만 하면 됩니다. 그런데 너비를 바꾸면 옆 요소, 아래 요소의 자리가 전부 달라집니다. 계산을 다시 하고, 그리기도 다시 해야 해요. 비용이 다릅니다.

글이 속성을 나눠 정리해둔 게 그래서 값집니다.

리플로우를 일으키는 속성position, width, height, margin, padding, border, font-size, font-weight, line-height, text-align, overflow

리페인트만 일으키는 속성background, color, text-decoration, border-style, border-radius

이 목록이 실무에서 바로 쓰입니다. 예를 들어 요소를 움직이는 애니메이션을 만들 때, left를 바꾸면 매 프레임마다 리플로우가 일어납니다. 60fps라면 초당 60번이죠. 반면 transform: translateX()를 쓰면 레이아웃 계산을 건너뜁니다. 같은 움직임인데 비용이 완전히 달라요.

"애니메이션에는 transformopacity를 쓰라"는 조언을 많이 들으셨을 텐데, 그 근거가 정확히 여기에 있습니다.

큐레이터 노트

흩어진 지식을 하나의 순서로 꿰었기 때문에 뽑았습니다.

DNS도 알고, DOM도 알고, 리플로우도 들어봤습니다. 그런데 이걸 주소창 입력부터 픽셀까지 한 줄로 이어서 설명할 수 있는 사람은 많지 않아요. 이 글은 그 줄을 만들었습니다. 순서가 잡히면 새로 배우는 것들이 어디에 놓일지도 저절로 정해집니다.

참고 문헌을 밝힌 점도 좋았습니다. MDN을 포함해 네 개의 출처를 적어두셨어요. 여러 자료를 읽고 자기 순서로 재구성한 글이라는 뜻입니다.

그리고 이 저자의 흐름을 보면 재미있습니다. 7회차에는 Bun으로 빌드 시간을 줄였고, 이번엔 브라우저가 화면을 그리는 원리를 봤어요. 이전 포스트는 Web Assembly, 다음 포스트는 Web Metrics더라고요. "웹이 빠르다는 게 무엇인가"를 여러 각도에서 파고들고 있습니다. 기록이 쌓여 방향이 되는 모습입니다.

원문 읽으러 가기  ↗velog.io/@jong-kyung/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-…
 

02

📦 Vite의 Minification

마지혁 님

이런 내용이에요
Vite를 공부하다가 Minification(코드 최소화)이라는 최적화 기법을 처음 알게 된 기록입니다.
여기서 그치지 않고 직접 옵션을 켜고 꺼서 빌드 결과물을 비교했어요. 크기가 얼마나 다른지, 코드가 어떻게 달라지는지를요.
결과가 인상적입니다. 코드가 거의 없는 보일러플레이트 상태에서도 약 100KB 차이가 났습니다.

짧은 글입니다. 그런데 이 글이 한 일이 좋아요. 배운 걸 그냥 두지 않고 확인해봤습니다.

조금 더 들어가 보면

Minification은 이름 그대로 코드를 작게 만드는 작업입니다. 글의 정의가 정확해요. 공백과 주석을 제거하고, 변수와 함수 이름을 줄여 파일 크기를 최소화하는 기법.

왜 이런 걸 할까요. 자바스크립트는 사용자의 브라우저로 전송되어야 하기 때문입니다.

서버에 있는 코드는 크기가 커도 상관없습니다. 그런데 프론트엔드 코드는 사용자가 페이지를 열 때마다 네트워크를 타고 내려가야 해요. 파일이 크면 그만큼 로딩이 늦어지고, 모바일 데이터를 쓰는 사용자라면 요금까지 씁니다.

그래서 사람이 읽기 좋은 코드와 기계가 실행할 코드를 분리합니다. 개발할 때는 userAccountBalance, 배포할 때는 a. 동작은 똑같고 크기만 줄어요.

🧭 이름을 줄이는 게 정말 그렇게 클까

글이 확인한 부분이 여기입니다. 실제로 빌드해서 비교해봤어요.

크기 차이 — 약 100KB. 그것도 코드가 거의 없는 보일러플레이트 프로젝트에서요.

이 숫자가 놀라운 이유가 있습니다. 내가 짠 코드가 없는데도 100KB가 차이 났다는 건, 줄어든 게 대부분 라이브러리 코드라는 뜻이거든요. React 같은 프레임워크가 이미 번들에 들어가 있으니까요.

실제 서비스에서는 라이브러리가 훨씬 많이 들어갑니다. 그러니 차이도 그만큼 커져요. Minification이 옵션이 아니라 기본값인 이유입니다.

코드 구성 차이도 직접 확인했습니다. 글에 나온 예가 명료해요. function Title()Uc()로 바뀌어 있었다는 것.

이름 하나가 5글자에서 2글자가 됐습니다. 별것 아닌 것 같지만, 그 함수가 코드 안에서 30번 호출된다면 줄어드는 건 30배입니다. 여기에 공백, 줄바꿈, 들여쓰기, 주석까지 전부 사라지면 그 100KB가 나옵니다.

🧭 그런데 알아둘 것 — 에러 메시지도 같이 줄어듭니다

Minification에는 대가가 있습니다. 이 글에서 한 발 더 나가보면 좋을 부분이에요.

운영 환경에서 에러가 나면 이렇게 보입니다.

TypeError: Cannot read properties of undefined (reading 'x')
    at Uc (index-a3f9c2.js:1:48210)

Uc가 뭔지, 1번 줄 48210번째 글자가 어디인지 사람은 알 수 없습니다. 압축의 대가로 디버깅 정보를 잃은 것이죠.

그래서 함께 쓰는 게 소스맵(source map)입니다. "압축된 Uc는 원래 Title이었고, 원본 파일 12번 줄이었다"를 적어둔 지도예요. Sentry 같은 오류 추적 도구에 이걸 올려두면, 압축된 코드에서 난 에러도 원본 위치로 되돌려 보여줍니다.

Vite에서는 build.sourcemap 옵션으로 켤 수 있어요. 압축은 하되 원본으로 되돌릴 열쇠는 따로 챙겨두는 것 — 실무에서 쓰는 방식입니다.

큐레이터 노트

"궁금해서 직접 해봤다"가 글이 되었기 때문에 뽑았습니다.

Minification을 설명하는 자료는 아주 많습니다. 그런데 대부분 개념만 적혀 있어요. 이 글은 "그래서 실제로 얼마나 차이 나는데?"라는 질문을 스스로 던지고, 옵션을 켜고 꺼서 확인했습니다.

100KB라는 숫자가 그렇게 나왔습니다. 남의 글에서 가져온 수치가 아니라 자기 프로젝트에서 잰 값이에요. 이런 숫자는 잘 잊히지 않습니다.

그리고 Title()Uc()가 된 걸 눈으로 확인한 것도 좋았습니다. "변수 이름을 줄인다"는 설명을 열 번 읽는 것보다, 실제 빌드 결과에서 그 한 줄을 찾아보는 게 훨씬 오래 남아요.

글이 짧다고 아쉬워하지 않으셔도 됩니다. 직접 실험하고 결과를 기록한 글은 길이와 상관없이 값집니다. 성장일지가 바라는 딱 그 형태예요.

원문 읽으러 가기  ↗velog.io/@foranything/Vite%EC%9D%98-Minification

💡 두 편을 겹쳐 읽으며

두 글은 웹 페이지의 서로 다른 구간을 봅니다. 한 편은 브라우저가 받은 뒤 무슨 일을 하는지, 다른 한 편은 브라우저에 도착하기 전 무엇이 줄어드는지를요.

이어 붙이면 하나의 흐름이 됩니다.

사용자가 주소를 입력하면 → 네 번의 왕복 끝에 HTML이 옵니다 → 파싱하다가 <script>를 만나면 멈춥니다 → 그 스크립트를 다운로드하는 데 걸리는 시간이 파일 크기에 비례합니다 → Minification이 그 크기를 줄입니다 → 다시 파싱이 이어지고 화면이 그려집니다.

마지혁 님의 100KB는 이종경 님이 설명한 그 멈춤의 길이를 줄이는 일이었던 거예요. 따로 쓰인 두 글이 같은 파이프라인의 앞뒤를 맡고 있습니다.

그리고 두 글이 공유하는 태도가 하나 더 있습니다. 둘 다 "당연하게 쓰던 것"을 열어봤어요.

브라우저는 그냥 화면을 보여주는 물건이고, 빌드는 그냥 돌리면 되는 명령어입니다. 매일 쓰지만 안을 들여다볼 일은 없죠. 그런데 안을 한 번 본 사람은 그다음부터 다르게 씁니다. 스크립트를 어디에 둘지, 어떤 CSS 속성으로 애니메이션을 만들지, 빌드 옵션을 어떻게 둘지를 근거를 가지고 정하게 돼요.

이번 회차에서 가져갈 것

1. 브라우저는 한 번에 한 가지만 합니다. 메인 스레드는 하나예요. 무거운 자바스크립트가 도는 동안 화면은 멈춥니다. 성능 문제를 만나면 "무엇이 메인 스레드를 붙잡고 있는가"부터 보세요.

2. <script>는 파싱을 멈춥니다. asyncdefer 없이 <head>에 둔 스크립트는 그 아래 HTML을 읽지 못하게 합니다. 위치와 속성만 바꿔도 첫 화면이 빨라지는 경우가 많습니다.

3. 애니메이션은 리플로우를 피해서. left, width, margin을 바꾸면 매 프레임 레이아웃을 다시 계산합니다. transformopacity는 그 단계를 건너뜁니다. 같은 움직임, 다른 비용이에요.

4. 배운 건 한 번 재보기. "Minification은 파일을 줄인다"를 읽는 것과 직접 켜고 꺼서 100KB를 확인하는 것은 다릅니다. 옵션 하나를 바꿔보는 데 5분이면 충분해요.

5. 압축했다면 소스맵도 챙기기. 운영 환경 에러가 Uc (index.js:1:48210)로 보이면 손을 못 씁니다. build.sourcemap을 켜두고 오류 추적 도구에 올려두세요.


좋은 글 남겨주신 이종경 님, 마지혁 님께 감사드립니다! 😊

다음 큐레이션으로 다시 찾아오겠습니다! 🌱

- 에디터 · 성장일지 큐레이터 -


※ 본 큐레이션은 각 저자가 공개한 글을 소개하는 것이며, 모든 원문의 저작권은 저자에게 있습니다. 저자 본인의 요청이 있을 경우 즉시 수정 또는 삭제합니다.

WRITE YOUR GROWTH

오늘의 배움을
내일의 성장으로.

기록은 나를 돌아보게 하고, 공유는 우리를 연결합니다.
당신의 다음 성장 이야기도 그로스로그에서 시작해보세요.

커뮤니티 둘러보기