브라우저가 http://localhost:3000에서 리액트 애플리케이션의 개발 서버를 실행하는 과정

dev_dam·2024년 8월 31일
7
post-thumbnail

들어가며

보통 리액트 프로젝트를 생성할 때 create-react-app 명령어로 생성하여 프로젝트를 실행하고 개발하면서, 개발 서버가 어떻게 어떤 원리로 실행되는지에 대한 의문을 품지 않았습니다.
그래서 개발 서버가 어떻게 실행되는지에 대한 질문을 받았을 때, 대답을 하지 못하는 저의 모습을 보고 이번 기회에 살펴보기로 결심했고 관련된 내용을 정리하였습니다.


우리는 보통 리액트 프로젝트를 생성할 때 create-react-app 명령어로 생성합니다.
이렇게 create-react-app 명령어로 애플리케이션을 생성하면 개발 환경 설정, 빌드 도구, 기본 디렉터리 구조 등을 자동으로 설정해 줍니다.
이후 npm start 명령어를 실행하면, package.json 파일에 정의된 스크립트가 실행되어 브라우저에서 http://localhost:3000 을 입력하면 리액트 애플리케이션이 구동되는 것을 확인할 수 있습니다.

  • npm start 명령어를 입력했을 때

동영상이 업로드되지 않아 사진으로 대체하지만, 시작할 때 위의 문구가 빠르게 출력되었다가 사라지는 것을 확인할 수 있고, npm start 명령어를 입력하면 개발 서버가 시작됩니다

그렇다면 React 애플리케이션의 개발 서버는 어떻게 실행되는지 알아보겠습니다.

1. React Scripts 와 Webpack Dev Server

React가 이렇게 http://localhost:3000에서 애플리케이션을 실행할 수 있는 이유는 create-react-app이 제공하는 react-scriptsWebpack Dev Server 때문입니다.

create-react-app 명령어로 리액트 애플리케이션을 생성하면, 프로젝트는 react-scripts 패키지에 의존하며 이 패키지는 개발 환경 설정과 빌드 프로세스를 관리하며, react-scripts Webpack Dev Server 를 사용하여 개발자가 쉽게 개발 서버를 시작하고 변경사항을 빠르게 반영하여 정적 파일을 제공할 수 있도록 도와줍니다.

즉, npm start 명령어로 애플리케이션의 개발 서버를 실행하면, Webpack Dev Server가 소스 코드를 번들링하고, 이를 메모리에 저장하여 빠르게 제공하는 방식으로 동작합니다.

1-1. React Scripts

react-scripts create-react-app(CRA)의 대부분의 기능을 수행하는 패키지로 개발, 빌드, 테스트, 배포 등 다양한 작업을 관리합니다.
이 패키지는 프로젝트의 복잡한 설정과 구성을 자동으로 관리하여 개발자가 신경 쓰지 않고 코드 작성에만 집중할 수 있도록 도와줍니다.

주요 스크립트 기능

react-scripts는 package.json 파일안에 있으며, 여기에 여러 스크립트가 정의되어 있고, 추가할 수 있습니다.

  • start : npm start 의 명령어로 개발 서버를 실행
  • build : npm run build 명령어로 애플리케이션을 프로덕션용으로 빌드
  • test : npm test 명령어로 테스트를 실행
  • eject : eject 로 설정 파일을 숨겨 개발자가 설정에 신경 쓰지 않고 개발에 집중할 수 있도록 함. 프로젝트 폴더로 가져와서 직접 수정할수도 있음.

여기서 npm start를 실행하면 react-scripts start가 호출되어 webpack-dev-server를 사용하여 개발 서버를 실행하게 됩니다.

1-2. Webpack Dev Server

Webpack Dev Server는 Webpack으로 번들링된 파일을 제공하는 개발 서버로, 실시간으로 파일 변경을 감지하고, 변경된 파일을 브라우저에 반영하여 빠른 개발 사이클을 지원합니다. (webpack devServer 공홈)

주요 기능

  • 자동 새로고침(Live Reloading): 소스 코드가 변경되면 자동으로 브라우저를 새로고침
  • Hot Module Replacement (HMR): 전체 페이지를 새로고침하지 않고 변경된 모듈만 교체하여 빠르게 반영
  • 정적 파일 제공 : Webpack이 번들링한 파일을 메모리에 저장하고 이를 제공하여 빠른 응답 속도를 제공
  • 프로젝트의 특정 포트에서 실행 : 기본적으로 http://localhost:3000에서 실행

Webpack Dev Server 설정

Webpack Dev Server는 주로 Webpack 설정 파일인 webpack.config.js를 통해 설정됩니다. (create-react-app을 사용하면 이 설정이 react-scripts에 의해 자동으로 관리되기 때문에 직접 파일을 수정할 필요가 없습니다)

// webpack.config.js 예시
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
    publicPath: '/'
  },
  // devServer 옵션은 Webpack Dev Server 동작 방식을 정의
  devServer: {
    contentBase: path.join(__dirname, 'public'),
    compress: true,
    port: 3000,
    hot: true,
    historyApiFallback: true
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: 'babel-loader'
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  }
};

devServer 옵션은 Webpack Dev Server의 동작 방식을 정의합니다.

  • contentBase : 정적 파일의 위치를 정의
  • compress : 모든 항목에 대해 gzip 압축을 활성화
  • port : 개발 서버가 실행될 포트를 지정
  • hot : HMR(Hot Module Replacement)을 활성화
  • historyApiFallback : HTML5 History API 기반의 라우팅을 사용할 때, 404 응답을 index.html로 대체

즉, react-scripts start 스크립트를 실행하여 애플리케이션 실행에 필요한 환경 변수를 설정하고, Webpack Dev Server 시작하여 애플리케이션이 로컬에서 실행할 수 있도록 개발 서버를 시작하고, 소스 파일의 변경을 감시하고 변경될 때마다 자동으로 애플리케이션을 다시 빌드하고 기본 브라우저에서 http://localhost:3000을 자동으로 열어주는 역할을 합니다.

2. React 애플리케이션의 실행 과정

2-1. npm start로 리액트 애플리케이션을 실행

  1. npm start 명령어를 실행하면 Node.jspackage.json 파일에 정의된 scripts 섹션의 start 명령어를 실행합니다.
  2. react-scripts start 명령어는 내부적으로 Webpack Dev Server를 설정하고 실행합니다.
  3. Webpack Dev Server는 Webpack을 사용하여 프로젝트의 모든 JavaScript, CSS 파일 등을 번들링하고, 번들링된 파일을 메모리에 저장하며 http://localhost:3000에서 개발 서버를 시작합니다.
  4. 브라우저에서 http://localhost:3000을 입력하고 엔터를 입력합니다.
    • localhost는 특별한 도메인 이름으로 로컬 호스트를 가리키기 때문에 DNS 해석 과정 없이 바로 로컬 네트워크 인터페이스를 가리킨 후, 브라우저는 localhost를 IP 주소로 해석하고 포트 번호 3000번으로 연결을 시도합니다.
  5. 브라우저는 로컬 네트워크 인터페이스를 통해 3000번 포트에 TCP 연결을 시도하고 서버 응답을 대기합니다.
  6. Webpack Dev Server브라우저로부터 요청을 수신하여 브라우저가 요청한 경로에 해당하는 번들 파일을 메모리에서 검색합니다.
  7. Webpack Dev Server는 메모리에 저장된 번들 파일을 브라우저에 응답으로 전송합니다.
  8. 브라우저는 Webpack Dev Server로부터 응답 받은 index.html 파일을 처리합니다.
  9. index.html 파일에 포함된 <script> 태그를 통해 번들된 JavaScript 파일을 로드하고 React 애플리케이션이 실행되어 브라우저는 UI를 사용자에게 표시합니다.

2-2. npm start를 하지 않고 브라우저에서 http://localhost:3000을 입력하여 리액트 애플리케이션을 실행했을 때

  1. 브라우저에서 http://localhost:3000을 입력하고 엔터를 입력
    • localhost는 특별한 도메인 이름으로 로컬 호스트를 가리키기 때문에 DNS 해석 과정 없이 바로 로컬 네트워크 인터페이스를 가리킨 후, 브라우저는 localhost를 IP 주소로 해석하고 포트 번호 3000번으로 연결을 시도합니다.
  2. 브라우저는 해당 포트에서 서버의 응답을 대기하지만, Webpack Dev Server가 실행되지 않아서 응답할 서버가 없기 때문에 에러가 발생합니다.
  3. 브라우저는 연결 실패 메시지를 화면에 표시합니다.
    • Chrome 예시
      • "This site can’t be reached"
      • "localhost refused to connect."
    • Firefox 예시
      • "Unable to connect"
      • "Firefox can’t establish a connection to the server at localhost:3000."

3. Webpack Dev Server가 현재 실행중인 프로세스인지 확인하기

Mac

  • 활성 상태 보기 → 메모리 → node 검색
  • ps aux | grep node 명령어를 통해 실행 중인 프로세스 목록에서 webpack-dev-server 또는 node 를 필터링하여 확인할 수 있습니다.
    • ps aux | grep node 로 검색하면 너무 많은 프로세스가 나와서 lsof -i :3000 명령어로 포트 3000을 사용 중인 프로세스를 검색하여 확인할 수 있습니다.

4. React 애플리케이션의 build/index.html 을 더블 클릭했을 경우 일어나는 현상

만약 브라우저에서 http://localhost:3000을 입력하지 않고 React 애플리케이션의 build 파일인
index.html을 클릭 했을 때는 제대로 애플리케이션이 구동하는지 확인해보면 빈 화면만 출력되는 것
을 알 수 있습니다.

위의 이미지를 보면 javascript 파일을 제대로 불러오지 못하는 것을 확인할 수 있습니다.

4-1. 빈 화면만 나오는 이유

1. 상대 경로 문제

위의 이미지를 보면 index.htmlRequest URL 의 경로가 (file://)로 시작하는 것 알 수 있는데 React 애플리케이션은 빌드될 때, 자바스크립트와 CSS 파일 등은 상대 경로로 지정되기 때문에 브라우저에서 파일 시스템 경로(file://)로 직접 접근하면 이러한 경로를 제대로 해석하지 못합니다.

예를들어 <script defer="defer" src="/static/js/main.3dd63bcb.js"></script> 의 경우 서버 환경에서만 제대로 동작하고 로컬 파일 시스템에서 열 때는 이 경로가 올바르게 해석되지 않습니다.

2. Webpack Dev Server 기능의 부재

즉, npm start 명령어로 실행되는 Webpack Dev Server는 JS 파일을 번들링하고, 이를 클라이언트에게 제공하는 역할을 하기 때문에 이 서버가 없으면 브라우저는 필요한 자원을 로드하지 못합니다.

3. CORS 문제

브라우저의 보안 정책으로 인해, 파일 시스템에서 직접 열 경우 Cross-Origin Resource Sharing (CORS) 문제로 인해 외부 자원을 로드하지 못하기 때문에 자바스크립트 파일들이 제대로 로드되지 않을 수 있습니다.

즉 이러한 문제를 해결하기 위해 React 애플리케이션을 개발할때는 npm start 명령어를 사용하여 Webpack Dev Server를 통해 개발 서버를 실행하는것이 가장 좋습니다. 그 이유는 모든 필요한 자원을 제공하며, 로컬 파일 시스템의 문제를 피할 수 있기 때문입니다.

5. Vite를 사용하여 React 프로젝트를 생성했을 때

Vite도 npm run dev 명령어를 사용하여 개발 서버를 시작할 수 있습니다.

5-1. Vite의 주요 구성 요소

  • 개발 서버: 빠른 HMR(Hot Module Replacement)을 제공하여 코드 변경 시 즉시 반영됩니다.
  • 번들러: 프로덕션 빌드 시 Rollup을 사용하여 애플리케이션을 번들링합니다.
  • ES Module: 개발 중에는 ES Module을 직접 브라우저에서 사용하여 빠른 시작을 지원합니다.

5-2. Vite 개발 서버 동작 원리

  1. npm run dev 명령어를 실행하면, Vite는 개발 서버를 시작

  2. Vite는 개발 환경에서 번들링을 하지 않으며, 브라우저의 ES Module을 직접 사용하여 모듈을 로드하기 때문에 초기 빌드 시간을 단축할 수 있습니다.

  3. 코드가 변경되면 HMR로 인해 변경된 모듈만 브라우저에게 전달하기 때문에 전체 페이지를 새로고침 하지 않고도 변경사항을 즉시 반영합니다.

  4. 브라우저가 모듈을 요청하면 Vite 개발 서버는 해당 모듈을 찾아 응답하고 최신 JavaScript 문법을 지원하지 않는 브라우저를 위해 필요한 경우 ES Module로 변환하거나 디버깅을 위해 소스 맵을 제공합니다.

  5. npm run build 명령어로 Rollup을 사용하여 최적화된 번들을 생성합니다.

    Rollup 은 모듈 번들러로 프로덕션 빌드 시 사용하는 도구이며, JavaScript 모듈을 최적화된 하나의 파일이나 여러 파일로 번들링하여 배포를 위해 코드 크기를 최소화하고 성능을 향상시킨다.

6. Next14의 실행 과정

  1. npm start 명령어를 실행하면 Node.jspackage.json 파일에 정의된 scripts 섹션의 start 명령어를 실행하여 next dev 명령어가 실행된다.
  2. next dev는 내부적으로 Webpack Dev Server를 설정하고 실행한다.
  3. webpack dev server는 파일들을 번들링하고 메모리에 저장하여 http://localhost:3000에서 개발 서버를 시작한다.
  4. 브라우저에서 http://localhost:3000을 입력하면 localhost는 특별한 도메인 이름으로 로컬 호스트를 가리키기 때문에 DNS 해석 과정 없이 바로 로컬 네트워크 인터페이스를 가리킨 후, 브라우저는 localhost를 IP 주소로 해석하고 포트 번호 3000번으로 연결을 시도한다.
  5. Next.js 개발 서버는 브라우저로부터 요청을 수신한다.
  6. 요청을 처리할때 Next.js 서버는 페이지 컴포넌트(예: pages/index.js)를 렌더링한다.
    1. 만약 페이지 컴포넌트에서 서버 컴포넌트가 있다면 서버 컴포넌트는 서버에서 실행되고 HTML을 생성하여 브라우저에 전송한다.
    2. 만약 클라이언트 컴포넌트가 있다면 클라이언트 컴포넌트는 webpack 을 사용하여 번들링되고 번들된 컴포넌트는 브라우저에 전송한다.
  7. 라우저는 Next.js 개발 서버로부터 응답받은 index.html을 로드한다.
    1. 클라이언트 컴포넌트의 JS 파일을 다운로드하고 실행한다.
    2. 서버 컴포넌트의 렌더링 결과는 이미 HTML로 응답받았음으로 클라이언트 컴포넌트가 실행되고 UI가 동적으로 업데이트된다.
  8. React 애플리케이션이 실행되어 브라우저는 UI를 사용자에게 표시한다.
    1. 서버 컴포넌트는 초기 로딩시 서버에서 렌더링된 HTML을 표시한다.
    2. 클라이언트 컴포넌트는 브라우저에서 실행되어 사용자 인터랙션을 처리한다.

마무리

webpack이 많은 일들을 대신해 준다는 것은 인지만 하고 있었지 정확히 어떤 일들을 처리해 주고, 어떤 원리로 개발서버가 시작되고 코드가 바로 반영되는지 의문을 품지 않았던 저의 모습을 되돌아보게 해주는 기회가 되어서 좋았고, 추후에는 조금 더 나아가서 번들링에 대해서도 조금 더 깊이 있게 토끼굴을 파봐야겠다고 생각했습니다.

profile
병아리에서 닭이 될 때까지

2개의 댓글

comment-user-thumbnail
2024년 9월 12일

우와... next.js에서 컴포넌트가 서버에서 렌더링된다는 게 어떤 의미인지 아리송했는데 이 글 덕분에 제대로 이해할 수 있었어요 정말 감사합니다!

1개의 답글

관련 채용 정보