비전공자도 개발지식은 언제나 중요합니다

타락한스벨트전도사·2025년 7월 11일
47
post-thumbnail

비전공자도 개발지식은 언제나 중요합니다.

1KB 줄인다고 뭐가 달라지나요?

저는 비전공자 출신 개발자입니다. 그동안 성능 최적화라고 하면 단순히 "코드를 더 효율적으로 작성하고, 번들 사이즈를 줄이면 되겠지"라고 생각했습니다. 코드 스플리팅을 하면 선형적으로 로딩 시간이 줄어들 거라고 막연히 믿고 있었죠.

그런데 최근 지인이 Qwik이라는 프레임워크를 소개해주면서, 제 생각이 완전히 바뀌었습니다. "Qwik이 왜 이렇게 빠른지 아냐?"라는 질문에서 시작된 대화는 TCP slow start라는 개념으로 이어졌고, 저에게는 완전히 새로운 세상이 열렸습니다.

🚗 고속도로에서 배우는 네트워크 원리

TCP slow start를 고속도로에 비유해보면 이렇습니다.

상상해보세요. 새로 개통된 고속도로가 있는데, 이 도로가 얼마나 많은 차량을 수용할 수 있는지 아무도 모릅니다. 도로의 폭도 다르고, 중간중간 있는 교차로나 톨게이트의 처리 능력도 제각각이죠.

그래서 교통 관제센터는 이렇게 결정합니다: "처음에는 10대의 차만 보내보자. 이 차들이 무사히 목적지에 도착하면, 그다음에는 20대를 보내고, 그다음에는 40대를 보내자."

이것이 바로 TCP slow start의 핵심 아이디어입니다.

💡 1KB가 만드는 차이

웹에서도 마찬가지입니다. 서버와 브라우저가 처음 만날 때, 네트워크 상황을 모르기 때문에 안전하게 시작합니다. 첫 번째 왕복에서는 약 14KB 정도의 데이터만 보낼 수 있죠.

만약 당신의 웹사이트가 15KB라면, 그 1KB 때문에 사용자는 추가 왕복을 기다려야 합니다. 위성 인터넷 환경에서는 612ms, 일반적인 모바일 환경에서도 수십에서 수백 밀리초의 추가 지연이 발생합니다.

사용자 경험 관점에서 보면, 14KB vs 15KB는 단순한 7% 차이가 아닙니다. 체감되는 로딩 속도에서는 몇 배의 차이를 만들어낼 수 있죠.

어떻게 하면 최적화 할 수 있을까

대부분의 프레임워크는 "어떻게 하면 번들 사이즈를 줄일까?"에 집중합니다. 하지만 일부 혁신적인 접근법들은 다른 관점에서 생각합니다:

"실제로 필요한 순간에만 로드하자"

이런 접근법은 코드를 최대한 세밀하게 분리해서, 사용자가 실제로 상호작용할 때만 해당 기능의 코드를 로드합니다. 초기 로딩 시에는 정말 필요한 최소한의 JavaScript만 보내는 거죠.

이 경험으로 깨달은 건, 컴퓨터 공학 기초가 단순한 이론이 아니라는 점입니다. TCP/IP를 이해하면 프레임워크 선택부터 성능 최적화까지 모든 게 달라집니다.

비전공자든 전공자든, 기술의 동작 원리를 깊이 아는 것이 더 나은 개발자가 되는 길이라고 생각해요. 이제 TCP slow start가 정확히 어떻게 작동하는지, 그리고 이것이 웹 성능에 어떤 영향을 미치는지 자세히 살펴보겠습니다.

TCP Slow Start의 비밀

앞서 고속도로 비유로 TCP slow start를 간단히 설명했지만, 실제로는 훨씬 더 정교한 메커니즘이 작동합니다. 이 원리를 제대로 이해하면 웹 성능 최적화에 대한 관점이 완전히 바뀝니다.

TCP Congestion Window의 동작 원리

TCP는 네트워크 혼잡을 방지하기 위해 Congestion Window(혼잡 윈도우)라는 개념을 사용합니다. 이 윈도우의 크기가 한 번에 보낼 수 있는 데이터의 양을 결정하죠.

Round Trip 1: [패킷 1개] → ACK 받음 → 윈도우 크기 2로 증가
Round Trip 2: [패킷 2개] → ACK 받음 → 윈도우 크기 4로 증가
Round Trip 3: [패킷 4개] → ACK 받음 → 윈도우 크기 8로 증가
Round Trip 4: [패킷 8개] → ACK 받음 → 윈도우 크기 16으로 증가

처음에는 1개의 패킷만 보내고, 성공적으로 전달되면 2개, 4개, 8개... 이런 식으로 지수적으로 증가합니다. 이를 Slow Start Phase라고 부릅니다.

왜 하필 14KB일까?

그런데 왜 하필 14KB일까요? 여기에는 명확한 기술적 근거가 있습니다.

초기 Congestion Window 크기

대부분의 현대 시스템에서 초기 혼잡 윈도우 크기는 10개 패킷으로 설정되어 있습니다. (RFC 6928에서 권장)

패킷당 실제 데이터 크기

이더넷 프레임 최대 크기: 1,500바이트 (MTU)
IP 헤더: 20바이트
TCP 헤더: 20바이트
실제 데이터 영역: 1,500 - 20 - 20 = 1,460바이트

계산 결과

첫 번째 라운드트립에서 전송 가능한 데이터:
10개 패킷 × 1,460바이트 = 14,600바이트 ≈ 14.6KB

즉, 첫 번째 네트워크 왕복에서 최대 14KB의 데이터만 전송 가능합니다.

실제 측정: 14KB vs 15KB의 체감 차이

이론적 계산을 넘어서, 실제로 어떤 차이가 발생하는지 살펴보겠습니다.

14KB 이하인 경우

시간 0ms:    클라이언트 → 서버 (요청)
시간 50ms:   서버 → 클라이언트 (14KB 데이터, 완료!)
총 소요시간: 50ms (1 Round Trip)

15KB인 경우

시간 0ms:    클라이언트 → 서버 (요청)
시간 50ms:   서버 → 클라이언트 (14KB 데이터)
시간 50ms:   클라이언트 → 서버 (ACK)
시간 100ms:  서버 → 클라이언트 (나머지 1KB, 완료!)
총 소요시간: 100ms (2 Round Trip)

단 1KB 차이로 로딩 시간이 2배가 됩니다!

네트워크 환경별 영향도

이 차이는 네트워크 지연(RTT, Round Trip Time)에 따라 더욱 극명해집니다:

일반적인 환경

  • 유선 브로드밴드: 10-30ms RTT → 10-30ms vs 20-60ms
  • 4G LTE: 30-70ms RTT → 30-70ms vs 60-140ms
  • 3G: 100-500ms RTT → 100-500ms vs 200-1000ms

극단적인 환경

  • 위성 인터넷: 600ms RTT → 600ms vs 1200ms
  • 국제 연결: 200-300ms RTT → 200-300ms vs 400-600ms

모바일 환경이나 네트워크 품질이 좋지 않은 환경에서는 1KB의 차이가 수백 밀리초에서 1초 이상의 차이를 만들어낼 수 있습니다.

인프라 엔지니어의 마음을 알겠어요

TCP slow start를 이해하고 나니, 성능 최적화에 대한 기존 생각들이 얼마나 단순했는지 깨닫게 됩니다.

기존 생각들의 오해

"1KB 줄인다고 뭐가 달라지겠어?"
→ 14KB와 15KB는 2배의 로딩 시간 차이를 만듭니다.

"번들 사이즈가 늘어나면 선형적으로 늘어나겠지"
→ 14KB 임계점을 넘는 순간 갑자기 팍 늘어납니다.

"gzip으로 압축하면 용량이 절반이 되니까 로딩도 절반!"
→ 네트워크 왕복 횟수가 같다면 체감 속도는 거의 비슷합니다.

새로운 사고방식

이제 성능 최적화는 단순한 "용량 줄이기"가 아닙니다:

  • "첫 로딩에 정말 필요한 것만 14KB 안에 담자"
  • "나머지는 사용자가 실제로 상호작용할 때 로드하자"
  • "네트워크 왕복 횟수를 최소화하는 것이 핵심"
기존 접근법: 50KB → 40KB (20% 개선이라고 생각)
실제 결과: 4번 왕복 → 3번 왕복 (체감상 큰 차이 없음)

새로운 접근법: 50KB → 13KB + 37KB 지연 로딩
실제 결과: 4번 왕복 → 1번 왕복 (체감상 4배 빨라짐)

단순히 "용량을 줄이는" 것이 아니라 "언제 무엇을 로드할지"를 전략적으로 결정하는 것이 진짜 성능 최적화입니다.

이런 관점을 가지고 기존 프레임워크들이 어떤 방식으로 이 문제에 접근해왔는지, 그리고 그 한계가 무엇인지 살펴보겠습니다.

Next.js는 어떻게 이 문제를 해결하고 있나

14KB 임계점을 알고 나니, 기존 프레임워크들이 얼마나 치열하게 이 문제와 싸워왔는지 보입니다. 특히 Next.js는 다양한 코드 스플릿 기법을 도입하며 끊임없이 진화해왔죠.

SPA의 등장, 그리고 새로운 문제들

React가 등장하면서 웹 개발 패러다임이 크게 바뀌었습니다. 전통적인 멀티 페이지 애플리케이션(MPA)에서는 페이지를 이동할 때마다 전체 HTML을 다시 받아와야 했죠. 하지만 SPA(Single Page Application)는 거의 빈 HTML을 받습니다.

<div id="root"></div>

이것만 받아오고, JavaScript로 화면을 동적으로 다시 만드는 거죠.

사용자 경험은 확실히 좋아졌습니다. 페이지 전환이 매끄럽고, 마치 네이티브 앱을 사용하는 것 같은 느낌이죠. 하지만 새로운 문제들이 생겼습니다.

첫 번째는 초기 로딩 시간입니다. 모든 JavaScript 코드를 다운로드하고 실행해야 첫 화면을 볼 수 있으니까요. 두 번째는 SEO 문제입니다. 검색 엔진 봇이 JavaScript를 실행하기 전의 빈 HTML만 보게 되어 콘텐츠를 제대로 인덱싱하지 못했습니다.

SSR의 등장과 해결책

이 문제들을 해결하기 위해 서버사이드 렌더링(SSR)이 등장했습니다. React에서는 renderToString이나 Next.js 같은 프레임워크를 통해 서버에서 미리 HTML을 생성해서 보내주는 방식이죠.

SSR을 사용하면 사용자는 훨씬 빠르게 첫 화면을 볼 수 있습니다. 검색 엔진도 완성된 HTML을 받아서 SEO 문제가 해결되고요. 하지만 여기서 또 다른 문제가 생깁니다.

하이드레이션, 그리고 새로운 네트워크 병목

서버에서 받은 HTML은 정적입니다. 클릭해도 반응하지 않고, 상태도 변하지 않죠. 이를 인터랙티브하게 만들어주는 과정이 바로 하이드레이션(Hydration)입니다.

// 서버에서는 정적 HTML 생성
<button>클릭하세요</button>

// 클라이언트에서 하이드레이션으로 이벤트 리스너 부착
<button onClick={handleClick}>클릭하세요</button>

여기서 네트워크 관점의 문제가 생깁니다. 하이드레이션을 위해서는 해당하는 모든 JavaScript를 초기에 다운로드해야 합니다.

첫 화면에 인터랙티브한 요소가 많을수록:

  • 초기에 보내야 하는 JavaScript 양이 증가
  • 14KB 임계점을 넘을 가능성이 높아짐
  • 추가 네트워크 왕복이 필요해짐

결국 하이드레이션 대상이 많다 = 초기 번들 사이즈가 크다는 뜻이고, 이는 곧 네트워크 성능 저하로 이어집니다.

Next.js의 자동 라우트 코드 스플릿

Next.js를 쓴다면 이미 코드 스플릿을 하고 있습니다. Next.js는 기본적으로 라우트별로 코드를 자동 분리해주거든요.

pages/
  index.js        → /_next/static/chunks/pages/index.js
  about.js        → /_next/static/chunks/pages/about.js
  contact.js      → /_next/static/chunks/pages/contact.js

/about 페이지에 접근할 때만 해당 페이지의 JavaScript가 로드됩니다. /contact 페이지를 방문하지 않는 사용자는 그 페이지의 코드를 다운로드할 필요가 없죠.

하지만 이것만으로는 충분하지 않습니다. 하나의 페이지 안에서도 수많은 컴포넌트와 라이브러리들이 한꺼번에 로드되면서 여전히 성능 병목이 발생할 수 있습니다.

dynamic()으로 컴포넌트 레벨 최적화

Next.js의 dynamic() 함수는 React의 lazy()Suspense를 합친 것이라고 보면 됩니다. 특정 컴포넌트를 필요할 때만 로드할 수 있게 해주죠.

import dynamic from 'next/dynamic'

// 차트 라이브러리는 용량이 크니까 나중에 로드
const Chart = dynamic(() => import('../components/Chart'))

// 모달은 사용자가 버튼을 클릭할 때만 필요
const Modal = dynamic(() => import('../components/Modal'))

function Dashboard() {
  const [showModal, setShowModal] = useState(false)
  
  return (
    <div>
      <h1>대시보드</h1>
      <Chart data={chartData} />
      {showModal && <Modal onClose={() => setShowModal(false)} />}
    </div>
  )
}

여기서 중요한 점은 초기 번들에 포함되는 순서입니다. 처음에는 dynamic으로 명시하지 않은 컴포넌트들의 JavaScript만 다운로드하고, dynamic으로 감싼 컴포넌트들의 JavaScript는 나중에 필요할 때 다운로드합니다. 초기 네트워크 비용을 줄이는 것이 핵심이죠.

SSR 제어로 더 세밀한 최적화

때로는 특정 컴포넌트를 아예 클라이언트에서만 렌더링하고 싶을 때가 있습니다. 브라우저 API를 사용하는 컴포넌트나, 서버에서 렌더링할 필요가 없는 경우죠.

const ClientOnlyComponent = dynamic(
  () => import('../components/InteractiveWidget'),
  {
    ssr: false,
    loading: () => <div>위젯을 불러오는 중...</div>
  }
)

// 차트 라이브러리는 캔버스니까 SSR에서 렌더링할 필요 없음
const ChartComponent = dynamic(
  () => import('../components/Chart'),
  { ssr: false }
)

ssr: false로 설정하면 서버사이드 렌더링에서는 아예 HTML 태그마저 생략해서 클라이언트에서 다시 그려옵니다. 그러면 초기에 보내야 하는 HTML과 JavaScript 양이 줄어들어 네트워크 병목도 줄고, 14KB 임계점을 넘지 않을 가능성이 높아집니다.

서버 컴포넌트의 등장

Next.js 13부터는 서버 컴포넌트라는 새로운 개념이 등장했습니다. 모든 컴포넌트는 기본적으로 서버에서만 실행되고, JavaScript 번들에 포함되지 않습니다. 더욱더 하이드레이션 과정을 줄여주는 거죠.

// app/page.tsx - 서버 컴포넌트 (기본값)
async function HomePage() {
  const data = await fetch('https://api.example.com/data')
  
  return (
    <div>
      <h1>홈페이지</h1>
      <UserProfile data={data} />
    </div>
  )
}

서버 컴포넌트는 서버에서 HTML 스니펫으로 렌더링되어 클라이언트에 전달되며, 클라이언트 JavaScript 번들에 포함되지 않습니다. React 렌더 트리는 이 위치를 "구멍(hole)"으로 남겨두었다가 해당 HTML 스니펫을 삽입하는 형태로 페이지를 완성합니다.

이는 초기 번들 사이즈를 크게 줄여주는 혁신적인 방법입니다.

서버 컴포넌트는 대신 이벤트 핸들러나 useState를 쓸 수 없습니다. 클라이언트 JavaScript 번들에 포함되지 않도록 명시하는 거기 때문에, Next.js에서는 서버 컴포넌트에서 useState를 쓰면 클라이언트 번들이 필요한 컴포넌트라고 판단해서 use client를 붙이라고 하죠. 안 그러면 빌드 실패합니다.

여전히 남는 근본적 한계

지금까지 살펴본 최적화 기법들은 분명 효과적입니다. 하지만 초기 번들 사이즈를 줄이는 데는 근본적인 한계가 있습니다.

초기 로드 시 필요한 JavaScript의 최소 임계점

JavaScript 다운로드는 비동기이지만, 하이드레이션 과정에서 서버사이드에서 했던 렌더링을 그대로 반복하면 메인 스레드를 잡아먹습니다. 특히 모바일에서는 더더욱 느리죠.

핵심 문제는 초기 번들에 포함되는 JavaScript의 양입니다:

  • dynamic으로 import하려면 해당 컴포넌트를 따로 파일로 빼야 함
  • useState 하나만 써도 전체 컴포넌트가 초기 번들에 포함됨
  • 하나의 컴포넌트 안에서는 모든 JavaScript가 함께 번들링
  • 파일 단위로만 분리 가능해서 세밀한 최적화가 어려움

개발자의 수동 최적화 부담

결국 개발자가 "이건 초기 번들에, 이건 나중에" 하나하나 수동으로 결정해야 합니다:

// 이렇게 써야 초기 번들에서 제외
const Modal = dynamic(() => import('./Modal'))

// 하지만 이렇게 쓰면 초기 번들에 포함
function Component() {
  const [state, setState] = useState(false)
  return <Modal show={state} />
}

서버 컴포넌트로 잘게 쪼개는 것도 어렵죠. 만들다 보면 useState나 이벤트 핸들러 하나씩은 들어간 컴포넌트가 나오거든요. 그렇다고 모든 컴포넌트를 use client로 명시해버리면... 초기 번들 사이즈 최적화의 의미가 없어집니다.

초기 번들 사이즈를 줄이는 것에는 구조적 한계

아무리 최적화해도 복잡한 애플리케이션에서는 초기에 필요한 JavaScript가 14KB를 넘기 쉽습니다:

  • React 런타임 자체의 네트워크 오버헤드
  • 라우터, 상태 관리 라이브러리들의 번들 사이즈
  • 첫 화면의 모든 인터랙티브 요소들의 JavaScript
  • 하이드레이션을 위한 컴포넌트 트리 정보

초기 번들 사이즈를 줄이는 것에는 구조적 한계가 있는 것이죠.

그렇다면 이 한계를 어떻게 극복할 수 있을까요? 여기서 완전히 다른 접근법이 등장합니다.

Qwik의 혁신: Zero Hydration과 재개가능성

Next.js의 한계를 보면서 드는 생각이 있습니다. 대부분의 컴포넌트는 인터랙션이 있지만, 동시에 대부분의 컴포넌트는 인터랙션이 필요 없습니다.

모든 페이지들의 요소요소들은 다 상호작용할 부분들이 많으면서도, 동시에 유저들은 모든 요소들을 한 페이지에서 눌러보지 않기 때문이죠. 사용자는 보통 페이지의 일부분만 실제로 상호작용합니다.

그렇다면 필요할 때만 그 부분의 JavaScript를 로드하면 되지 않을까요?

기존 접근법의 답답한 현실

Next.js에서 useState 하나 썼다고 전체 컴포넌트가 초기 번들에 포함된다거나, dynamic으로 import하려면 해당 컴포넌트를 따로 파일로 빼야 하는 불편함이 있습니다.

서버 컴포넌트로 잘게 쪼개는 것도 어렵죠. 만들다 보면 useState나 이벤트 핸들러 하나씩은 들어간 컴포넌트가 나오거든요. 그렇다고 모든 컴포넌트를 use client로 명시해버리면... 최적화의 의미가 없어집니다.

결국 개발자가 수동으로 "이건 서버에서, 이건 클라이언트에서" 하나하나 결정해야 하는 상황입니다.

Qwik이 등장! No Hydration과 자동 코드 스플릿

Qwik은 완전히 다른 접근을 합니다. 하이드레이션을 아예 하지 않습니다.

기존 프레임워크들은 서버에서 했던 렌더링을 클라이언트에서 한 번 더 반복하면서 하이드레이션을 수행합니다. 하지만 Qwik은 서버에서 실행을 일시정지하고, 클라이언트에서 실행을 재개합니다.

핵심은 HTML에 모든 정보가 이미 시리얼라이즈되어 있다는 점입니다.

<button on:click="./chunk-c.js#Counter_onClick[0,1]">클릭하세요</button>

이 버튼을 보세요. on:click 속성에 어떤 파일의 어떤 함수를 실행해야 하는지, 심지어 어떤 변수들을 복원해야 하는지([0,1]) 모든 정보가 들어있습니다.

Qwikloader: 1KB의 마법

Qwik이 클라이언트에 보내는 JavaScript는 Qwikloader라는 1KB짜리 스크립트 하나뿐입니다.

<html>
  <body q:base="/build/">
    <button on:click="./myHandler.js#clickHandler">push me</button>
    <script>
      /* Qwikloader */
    </script>
  </body>
</html>

Qwikloader의 역할은 단순합니다:

  1. 전역 이벤트 리스너 하나만 등록
  2. 사용자가 클릭하면 해당 요소에서 on:click 속성 찾기
  3. 속성값을 파싱해서 필요한 청크 파일 다운로드
  4. 해당 함수 실행

실제 동작 원리: 개발자 코드에서 최적화까지

개발자가 이렇게 코드를 작성한다면:

export const Counter = component$((props: { step: number }) => {
  const count = useSignal(0);
 
  return <button onClick$={() => (count.value += props.step || 1)}>{count.value}</button>;
});

Qwik의 Optimizer가 이를 자동으로 여러 청크로 분리합니다:

개발자 코드 (하나의 컴포넌트)
         │
         ▼
   Qwik Optimizer
         │
    ┌────┼────┐
    ▼    ▼    ▼
chunk-a chunk-b chunk-c
(mount) (render) (click)
// chunk-a.js - 컴포넌트 마운트
export const Counter_onMount = (props) => {
  const count = useSignal(0);
  return qrl('./chunk-b.js', 'Counter_onRender', [count, props]);
};

// chunk-b.js - 렌더링
const Counter_onRender = () => {
  const [count, props] = useLexicalScope();
  return (
    <button onClick$={qrl('./chunk-c.js', 'Counter_onClick', [count, props])}>{count.value}</button>
  );
};

// chunk-c.js - 클릭 핸들러
const Counter_onClick = () => {
  const [count, props] = useLexicalScope();
  return (count.value += props.step || 1);
};

결과적으로 생성되는 HTML:

<button q:obj="456, 123" on:click="./chunk-c.js#Counter_onClick[0,1]">0</button>

사용자 클릭의 순간

사용자가 버튼을 클릭하는 순간:

   사용자 클릭
       │
       ▼
 Qwikloader (1KB)
       │
       ▼
on:click 속성 파싱
"./chunk-c.js#Counter_onClick[0,1]"
       │
       ▼
chunk-c.js 다운로드
       │
       ▼
함수 실행 + 상태 복원
  count, props
  1. Qwikloader가 클릭 이벤트를 감지
  2. on:click="./chunk-c.js#Counter_onClick[0,1]" 파싱
  3. chunk-c.js 파일을 동적으로 로드
  4. Counter_onClick 함수 실행
  5. [0,1]로 필요한 변수들(count, props) 복원

대박인건, 컴포넌트 내에서도 핸들러별 청킹이 됩니다

기존 접근법의 한계였던 "하나의 컴포넌트 안에서는 모든 JavaScript가 함께 번들링"되는 문제를 Qwik은 근본적으로 해결합니다.

컴포넌트 내에서 이벤트 핸들러마저도 알아서 Optimizer가 다른 번들로 분리합니다. 개발자는 신경 쓸 필요 없이 평범하게 코드를 작성하면, 프레임워크가 알아서 최적의 코드 스플릿을 만들어줍니다.

사용자가 실제로 상호작용하는 그 순간에만 해당 JavaScript가 로드되니, 메인 스레드 블로킹도 없고 인터랙션 지연도 없습니다.

14KB 임계점과의 완벽한 조화

TCP slow start의 14KB 임계점을 생각해보면, Qwik의 접근법이 얼마나 이상적인지 알 수 있습니다:

기존 방식:
- React 런타임: ~45KB
- 컴포넌트들: ~100KB+
- 첫 로딩: 여러 번의 네트워크 왕복 필요

Qwik 방식:
- Qwikloader: 1KB
- HTML: ~10KB
- 첫 로딩: 14KB 이하로 단일 왕복 완료

사용자는 첫 화면을 즉시 볼 수 있고, 실제로 상호작용할 때만 필요한 JavaScript가 로드됩니다. TCP slow start의 특성을 완벽하게 활용하는 구조죠.

Zero Hydration의 의미

이것이 바로 Zero Hydration이자 재개가능성(Resumability)의 힘입니다. 서버에서 일시정지된 실행이 클라이언트에서 필요한 순간에 정확히 재개되는 것이죠.

기존 프레임워크들이 "어떻게 하면 하이드레이션을 더 효율적으로 할까?"를 고민했다면, Qwik은 "하이드레이션을 왜 해야 하지?"라는 질문부터 시작했습니다. 이런 근본적인 사고의 전환이 혁신을 만들어내는 것 같습니다.

앞으로 개발자들은 더욱 게을러져도 됩니다

Qwik을 보면서 정말 감탄했습니다. 하이드레이션이라는 근본적인 문제를 아예 다른 관점에서 해결해버린 접근법이 인상적이었어요.

┌─────────────────┐    ┌─────────────────┐
│  기존 방식      │    │  Qwik 방식     │
├─────────────────┤    ├─────────────────┤
│ 전체 JS: 500KB  │    │ Qwikloader: 1KB │
│ 하이드레이션 ✓  │    │ 하이드레이션 ✗  │
│ 초기 로딩 느림  │    │ 초기 로딩 빠름  │
└─────────────────┘    └─────────────────┘

기존 프레임워크들이 "어떻게 하면 하이드레이션을 더 효율적으로 할까?"를 고민했다면, Qwik은 "하이드레이션을 왜 해야 하지?"라는 질문부터 시작했습니다. 이런 근본적인 사고의 전환이 혁신을 만들어내는 것 같습니다.

프레임워크가 담당하는 최적화

Svelte가 "리렌더링을 왜 개발자가 신경 써야 해?"라고 하면서 등장했듯이, Qwik은 "코드 스플릿을 왜 개발자가 생각해야 해?"라고 묻고 있습니다.

어떻게 하면 서버 컴포넌트로 잘게 더 쪼개볼 수 있을지, "use client"를 어떻게 하면 덜 쓸 수 있을까, 어떻게 파일을 분리해야 할지, 어떤 컴포넌트를 dynamic으로 감쌀지 같은 복잡한 결정들을 개발자가 매번 고민해야 하는 것은 피곤한 일이죠.

프로젝트가 아무리 커져도 O(1)의 속도를 유지할 수 있도록 프레임워크가 담당하는 거죠. 개발자가 컴포넌트를 100개 만들든 1000개 만들든, 사용자가 실제로 상호작용하는 부분만 로드되니까 초기 성능은 일정하게 유지됩니다.

성능 최적화를 프레임워크가 담당하니까 개발자의 인지 부하와 초기 지식 습득 난이도가 줄어듭니다. 복잡한 최적화 지식들을 프레임워크에게 위임하면서 좀 더 제품에 집중할 수 있는 거겠죠.

기초 지식이 만드는 차이

하지만 이런 혁신적인 프레임워크들을 제대로 이해하고 활용하려면 결국 기초 지식이 중요합니다.

TCP slow start를 몰랐다면 Qwik의 1KB Qwikloader가 왜 혁신적인지 이해하기 어려웠을 거예요. 네트워크 지연과 패킷 단위 전송을 모르면 단순히 "용량이 작으니까 빠르겠지"라고만 생각했을 테죠.

14KB 임계점을 이해하고 나니:

  • 프레임워크 선택 기준이 바뀝니다
  • 성능 최적화 전략이 달라집니다
  • 사용자 경험 설계가 개선됩니다

비전공자든 전공자든, 기술의 동작 원리를 깊이 아는 것이 더 나은 개발자가 되는 길이라고 생각해요.

계속 발전하는 웹의 미래

SPA에서 시작해서 SSR, 하이드레이션, 코드 스플릿, 서버 컴포넌트, 그리고 이제는 재개가능성(resumability)까지. 각 단계마다 "개발자가 신경 써야 할 것들"을 하나씩 프레임워크가 가져가는 과정이었다고 생각합니다.

프레임워크들이 점점 더 똑똑해져서, 우리는 비즈니스 로직과 사용자 가치 창출에만 집중할 수 있게 되는 것 같아요. 앞으로도 이런 흐름은 계속될 것 같고, 저도 그런 변화를 따라가면서 더 나은 사용자 경험을 만드는 개발자가 되려고 노력하고 있습니다.

저도 배우고 있습니다

사실 커뮤니티에서 어느 한 분이 소개해줘서 Qwik이라는 것을 알게 되었습니다. 이런 글을 쓰면서도 저 역시 계속 배우고 있는 중이에요.

성능 최적화라는 게 결국 개발자의 인지 부하를 줄이면서도 사용자 경험을 개선하는 방향으로 발전하고 있다는 걸 느낍니다.

1KB 줄인다고 뭐가 달라지나요?

이제 이 질문에 자신 있게 대답할 수 있습니다. 때로는 그 1KB가 사용자 경험을 완전히 바꿀 수 있다고요. 그리고 그것을 이해하는 것이 더 나은 개발자가 되는 첫걸음이라고 생각합니다.

여러분은 최근에 어떤 기술적 깨달음이 있으셨나요?


📝 프론트엔드 개발자를 위한 이력서 & 면접 준비

이런 기술적 깨달음들을 면접에서 어떻게 어필할지 고민이신가요?

AI 이력서 분석에서 프론트엔드 개발자를 위한 이력서 작성 서비스를 만들고 있습니다. 모의면접 질문을 공유하고 대답하면서 실력을 쌓아가봐요!

🎯 오늘 글과 관련된 면접 질문들

이런 질문들이 나올 때 어떻게 대답하시겠어요?

성능 최적화 관련:

  • "웹 성능 최적화를 위해 어떤 방법들을 시도해보셨나요?"
  • "번들 사이즈를 줄이기 위해 사용한 기법들을 설명해주세요"
  • "코드 스플리팅을 어떻게 적용하셨고, 그 효과는 어땠나요?"

프레임워크 이해도:

  • "Next.js의 장단점과 사용 경험을 말씀해주세요"
  • "SSR과 CSR의 차이점과 각각의 장단점은?"
  • "최근 관심 있게 본 새로운 기술이나 프레임워크가 있나요?"

이런 질문들에 자신 있게 답할 수 있도록, 실전 면접 준비와 이력서 작성을 도와드리고 있습니다.

👉 지금 바로 시작하기

profile
기부하면 코드 드려요

3개의 댓글

comment-user-thumbnail
2025년 7월 17일

와.. 좋은 글 감사합니다. 👍
덕분에 1KB의 위대함을 알게 되었네요!

답글 달기
comment-user-thumbnail
2025년 7월 21일

고속도로가 있는데, 이 도로가 얼마나 많은 차량을 수용할 수 있는지 아무도 모릅니다. 도로의 폭도 다르고, 중간중간 있는 교차로나 톨게이트의 처리 ForemostPayOnline com

답글 달기
comment-user-thumbnail
2025년 7월 24일

https://fondecranvip.com/ soutient désormais la création indépendante en collaborant avec des artistes visuels internationaux. Une nouvelle collection "Artistes en lumière" vient d’être lancée, mettant à disposition des œuvres originales, exclusives et libres d’accès, dans le respect de la licence Creative Commons.

답글 달기