프론트엔드 개발 생태계는 그 어느 분야보다 빠르게, 그리고 파괴적으로 변화해왔습니다. 과거의 정적인 문서 수준이었던 HTML/JS 시절에서, 현대 클라이언트의 단일 페이지 애플리케이션(SPA)까지 어떻게 발전해왔는지 살펴보겠습니다.

약 20년간의 프론트엔드 역사를 통해, 각 시대의 기술이 왜 등장했고 어떤 문제를 해결했는지를 이해하면 현재의 React와 Next.js 생태계를 훨씬 깊이 있게 파악할 수 있습니다.

1. Vanilla JavaScript의 시대 — 브라우저 전쟁 (1990년대~2000년대 초)

초창기 웹은 HTML/CSS로 만들어진 정적 문서에 불과했습니다. Netscape Navigator와 Internet Explorer가 치열하게 경쟁하던 "브라우저 전쟁" 시절, 두 회사는 각자만의 JavaScript 엔진과 DOM API를 만들었습니다. 같은 코드가 IE에서는 동작하고 Netscape에서는 동작하지 않는 혼돈의 시대였습니다.

// 브라우저 전쟁 시대의 크로스 브라우저 코드
// IE와 Netscape에서 다른 방식으로 이벤트를 등록해야 했음
function addEvent(element, event, fn) {
  if (element.addEventListener) {
    // 표준 (Firefox, Chrome)
    element.addEventListener(event, fn, false);
  } else if (element.attachEvent) {
    // IE 전용
    element.attachEvent('on' + event, fn);
  }
}

// XMLHttpRequest도 브라우저마다 달랐음
function createXHR() {
  if (window.XMLHttpRequest) {
    return new XMLHttpRequest();           // 표준
  } else {
    return new ActiveXObject("Microsoft.XMLHTTP");  // IE
  }
}

2. jQuery — 브라우저 호환성을 통일하다 (2006년)

John Resig이 개발한 jQuery는 "Write less, do more"를 슬로건으로 이 혼돈의 브라우저 호환성 문제를 한 번에 해결했습니다. 어떤 브라우저에서나 동일하게 작동하는 DOM 조작, Ajax, 이벤트 처리 API를 제공하며 웹 생태계를 평정했습니다. 2010년대 초까지 거의 모든 웹사이트에서 사용되었을 만큼 폭발적인 성장을 이루었습니다.

// jQuery 예시: 브라우저 호환성 걱정 없이 DOM 조작
$(document).ready(function() {
  // 요소 선택 및 스타일 변경
  $('h1').css('color', 'blue');

  // 클릭 이벤트 등록
  $('button#submit').on('click', function() {
    var name = $('input#name').val();
    $('div#result').text('Hello, ' + name + '!');
  });

  // Ajax 요청 (크로스 브라우저 XHR 추상화)
  $.ajax({
    url: '/api/users',
    method: 'GET',
    success: function(data) {
      $.each(data, function(i, user) {
        $('ul#user-list').append('
  • ' + user.name + '
  • '); }); } }); });

    그러나 프로젝트가 커지면서 스파게티 코드가 문제가 됐습니다. jQuery는 단순한 DOM 조작 라이브러리이기 때문에, 애플리케이션의 상태(State)를 체계적으로 관리하는 방법을 제공하지 않았습니다. `$('div').text(data)` 형태의 명령형 DOM 조작이 수백, 수천 줄로 늘어나다 보면 어디서 어떤 데이터가 변경되었는지 추적하기가 거의 불가능해집니다.

    3. AngularJS — MVC가 프론트엔드로 (2010년)

    웹 애플리케이션의 덩치가 커지자, 구글에서 공개한 AngularJS는 백엔드의 전유물이었던 MVC(Model-View-Controller) 아키텍처를 클라이언트에 도입했습니다. HTML에 `ng-` 지시자(Directive)를 사용해 양방향 데이터 바인딩을 구현하는 방식이 핵심이었습니다.

    <!-- AngularJS 양방향 데이터 바인딩 예시 -->
    <div ng-app="myApp" ng-controller="UserCtrl">
      <input ng-model="username" placeholder="이름 입력">
      <p>안녕하세요, {{ username }}님!</p>
      <ul>
        <li ng-repeat="user in users">{{ user.name }} — {{ user.email }}</li>
      </ul>
    </div>
    
    <script>
    angular.module('myApp', [])
      .controller('UserCtrl', function($scope, $http) {
        $scope.username = '';
        $scope.users = [];
        $http.get('/api/users').then(function(res) {
          $scope.users = res.data;
        });
      });
    </script>
    

    구조화된 개발이 가능해졌지만, AngularJS의 양방향 바인딩은 성능 문제를 야기했습니다. 상태가 변경될 때마다 모든 바인딩을 검사하는 "Dirty Checking" 방식 때문에, 컴포넌트가 많아질수록 렌더링 속도가 느려지는 구조적 한계가 있었습니다.

    4. React — 단방향 데이터 흐름과 가상 DOM (2013년)

    Facebook이 공개한 React는 프론트엔드의 사고 패러다임을 완전히 뒤집었습니다. MVC 패턴을 거부하고 "상태(State)가 변하면 뷰(View)를 통째로 다시 만든다"는 선언적 렌더링 개념을 가져왔습니다. 잦은 렌더링을 극복하기 위해 메모리 속 Virtual DOM을 사용하고, 실제 DOM과 비교(Diffing)하여 최소한의 변경만 적용합니다.

    // React 함수형 컴포넌트 + Hooks 예시
    import { useState, useEffect } from 'react';
    
    function UserList() {
      const [users, setUsers] = useState([]);
      const [loading, setLoading] = useState(true);
      const [search, setSearch] = useState('');
    
      useEffect(() => {
        // 컴포넌트 마운트 시 데이터 로드
        fetch('/api/users')
          .then(res => res.json())
          .then(data => {
            setUsers(data);
            setLoading(false);
          });
      }, []);  // 빈 배열 = 최초 1회만 실행
    
      // 검색어로 필터링 (파생 상태, 별도 state 불필요)
      const filtered = users.filter(u =>
        u.name.toLowerCase().includes(search.toLowerCase())
      );
    
      if (loading) return <p>불러오는 중...</p>;
    
      return (
        <div>
          <input
            value={search}
            onChange={e => setSearch(e.target.value)}
            placeholder="검색어 입력..."
          />
          <ul>
            {filtered.map(user => (
              <li key={user.id}>{user.name} — {user.email}</li>
            ))}
          </ul>
        </div>
      );
    }
    

    2019년 React 16.8에서 도입된 Hooks(`useState`, `useEffect`, `useMemo` 등)는 클래스 컴포넌트의 복잡한 생명주기 메서드를 함수형으로 단순화하며 현대 React 개발의 표준이 되었습니다.

    5. Vue.js — 학습 곡선을 낮추다 (2014년)

    Google 엔지니어 출신의 Evan You가 AngularJS의 장점(양방향 바인딩)과 React의 장점(컴포넌트 기반)을 결합하여 만든 것이 Vue.js입니다. 단계적 도입이 가능하고 문서가 뛰어나 특히 소규모 팀이나 처음 프론트엔드를 배우는 개발자들에게 인기가 높습니다.

    <!-- Vue 3 Composition API 예시 -->
    <template>
      <input v-model="search" placeholder="검색..." />
      <ul>
        <li v-for="user in filteredUsers" :key="user.id">
          {{ user.name }}
        </li>
      </ul>
    </template>
    
    <script setup>
    import { ref, computed, onMounted } from 'vue';
    const users  = ref([]);
    const search = ref('');
    
    const filteredUsers = computed(() =>
      users.value.filter(u => u.name.includes(search.value))
    );
    
    onMounted(async () => {
      const res = await fetch('/api/users');
      users.value = await res.json();
    });
    </script>
    

    6. Next.js — SSR과 RSC의 시대 (현재)

    CSR(Client Side Rendering) 방식의 React SPA는 초기 번들 로딩 전까지 화면이 하얗게 비는 문제(SEO 불이익, LCP 저하)가 있었습니다. Vercel의 Next.js는 이를 해결하기 위해 SSR(서버사이드 렌더링)과 SSG(정적 사이트 생성)를 React에 통합했습니다.

    // Next.js App Router — React Server Component (RSC)
    // 이 컴포넌트는 서버에서만 실행됨 (클라이언트 번들에 포함 안 됨)
    async function UserPage({ params }) {
      // 서버에서 직접 DB 조회 (API 레이어 불필요)
      const user = await db.users.findById(params.id);
    
      return (
        <article>
          <h1>{user.name}</h1>
          <p>{user.bio}</p>
          {/* 클라이언트 상호작용이 필요한 부분만 Client Component로 분리 */}
          <FollowButton userId={user.id} />
        </article>
      );
    }
    
    // "use client" 지시자 → 이 컴포넌트만 클라이언트 번들에 포함
    "use client";
    function FollowButton({ userId }) {
      const [following, setFollowing] = useState(false);
      return (
        <button onClick={() => setFollowing(!following)}>
          {following ? '팔로잉' : '팔로우'}
        </button>
      );
    }
    

    7. Svelte/SolidJS — 컴파일러 기반의 혁신 (2019년~)

    Virtual DOM마저도 오버헤드라고 주장하는 새로운 패러다임이 등장했습니다. Svelte는 빌드 타임에 컴포넌트를 순수한 JavaScript로 컴파일하여 런타임에 프레임워크가 전혀 없는 코드를 생성합니다. 번들 크기가 극도로 작고 실행 속도가 빠릅니다.

    <!-- Svelte 컴포넌트: HTML/CSS/JS가 하나의 파일에 -->
    <script>
      let count = 0;
      // 반응형 선언: $: 뒤에 오는 코드는 의존성이 바뀔 때 자동 재실행
      $: doubled = count * 2;
    </script>
    
    <button on:click={() => count++}>
      클릭 수: {count}
    </button>
    <p>두 배: {doubled}</p>
    
    <style>
      button { background: #4589ff; color: white; }
    </style>
    

    프레임워크 선택 가이드 (2026년 기준)

    • React + Next.js: 대규모 팀, SEO가 중요한 서비스, 생태계 우선 → 현재 취업 시장에서 가장 높은 수요
    • Vue 3 + Nuxt: 중소 규모 팀, 점진적 도입, 아시아 스타트업에서 선호
    • Svelte + SvelteKit: 최고 성능, 작은 번들, 사이드 프로젝트나 정적 사이트
    • Angular (v17+): 엔터프라이즈 대규모 팀, 강력한 TypeScript 통합이 필요한 경우
    • SolidJS: 성능을 극한까지 추구, React와 유사한 문법으로 학습 전환 용이

    이러한 진화들은 개발 문서를 효율적으로 관리함과 동시에 프론트엔드 성능의 지표인 "로딩 속도와 렌더링 효율"을 극대화 하는 방향으로 꾸준히 발달해오고 있습니다. 결국 어떤 프레임워크를 선택하든, 그 이면의 컴포넌트 설계, 상태 관리, 렌더링 최적화 원칙을 이해하는 것이 장기적으로 더 중요합니다.