[React] 리액트 앱 배포하기

summereuna🐥·2023년 6월 22일
0

React JS

목록 보기
67/69

개발에서 프로덕션으로 넘어가는 방법 (From Development To Production)

리액트 앱 로컬 머신에서 실제 서버로 옮기기

  1. 배포 과정과 위험(Deployment Steps & Pitfalls)
  2. 서버-사이드 라우팅 vs 클라이언트-사이드 라우팅(Server-side Routing vs Client-side Routing)

📝 배포 과정


  1. 코드 작성 및 테스트 (Test Code)
  2. 코드 최적화 가능한지 살펴보기 (Optimize Code)
    • 지연 로딩(lazy loading) 가능한지 살펴보기
  3. 프로덕션용 앱 빌드하기 (Build App for Production)
    • 스크립트로 실행 > 코드 통합 및 최적화하여 작은 크기의 프로덕션 레디 패키지로 만들고 > 서버로 옮기기
    • 그러면 자동으로 사용자들에게 가능한 작은 크리고 최적화된 코드 패키지를 제공할 수 있다.
      사용자들에게 배포되는 코드는 크기가 작을 수록 좋다. 그래야 사용자가 웹사이트와 상호작용 시 로딩이 빠르기 때문이다.
  4. 프로덕션 코드 서버에 업로드 (Upload Production Code to Server)
    • 생성된 코드 패키지 서버에 업로드
    • 원하는 호스팅 선택

1. 코드 작성 및 테스트


코드 작성을 마치고 테스트까지 완료했다고 하자!


2. 코드 최적화


코드 최적화를 위해 사용하는 지연 로딩이라는 개념을 알아두자.

지연 로딩

모든 파일이 import 된 후에야 컴포넌트가 실행될 수 있는데, 작은 프로젝트는 상관이 없지만 프로젝트가 엄청나게 커진다면 모든 파일이 import되기 까지 시간이 걸릴 수 있다. 그러면 첫 화면 로딩이 느려져 사용자 경험에 좋지 않은 영향을 미칠 수 있다.
이럴 때 지연 로딩을 사용하여 코드를 최적화하여 필요할 때만 파일을 가져오게 할 수 있다.

홈페이지에 있을 때는 아직 블로그 페이지 코드가 필요하지 않다.
블로그 페이지가 참조하는 모든 코드들이 블로그 페이지를 방문했을 때만 불러와 지도록 지연 로딩을 추가해 보자.

  • 지연 로딩을 적용하려면 일단 블로그 페이지와 관련된 import문을 삭제해야 한다.

📚 필요할 때만 로딩하도록 lazy loading 만들기

1. 컴포넌트 지연

블로그 페이지 컴포넌트를 함수로 정의하여 동적으로 임포트한 블로그 페이지 컴포넌트 파일을 반환하게 만들기

  • const BlogPage = () => import("./pages/Blog");

import 함수는 프로미스를 반환한다.
하지만 유효한 컴포넌트가 되려면 반환하는 jsx 같은 값이 있어야 한다.

리액트에서는 이런 함수를 감싸는 특수한 함수를 제공한다.
lazy()로 함수를 감싸준다.

  • const BlogPage = lazy(() => import("./pages/Blog"));
    lazy()는 실행될 때 동적으로 임포트하는 () => import("./pages/Blog") 이 함수를 인자로 받는다.

  • 그러고나서 라우트의 엘리먼트를 설정할 때 리액트가 제공하는 <Suspense>컴포넌트로 BlogPage를 감싸주면 된다.

이제 BlogPage를 컴포넌트로 사용할 수 있다.

2. 로더 지연

  • 로더에 지연 로딩을 적용하려면 로더에 함수를 할당하면 된다.

  • 그리고 import("경로")를 사용하여 로더를 가져오면 동적으로 필요할 때만 임포트하게 된다.

  • import 함수는 프로미스를 반환한다. 이 비동기 프로세스를 처리하려면 시간이 소요될 수 있기 때문이다.
    async/await 사용해도 되지만 then(모듈 => 모듈.loader())으로 로딩된 모듈(파일)을 받아서 loader함수를 적용하면 된다.

  • loader함수는 Promise<Response>를 반환하므로
    이렇게 작성하면 전체 loader함수가 마지막의 loader 함수에 의해 프로미스를 반환한다.

이 모든게 지연되어 로딩된다.

  element: (
              <Suspense fallback={<p>Loading...</p>}>
                <BlogPage />
              </Suspense>
            ),
            loader: () =>
              import("./pages/Blog").then((module) => module.loader()),
          },
          //이 import 함수는 BlogPage의 이 loader가 호출될 때만 실행된다.
          //블로그 페이지를 방문할 때만 블로그 파일이 임포트되고 파일의 loader함수가 실행된다.
import { createBrowserRouter, RouterProvider } from "react-router-dom";

// import BlogPage, { loader as postsLoader } from "./pages/Blog";
import HomePage from "./pages/Home";
import PostPage, { loader as postLoader } from "./pages/Post";
import RootLayout from "./pages/Root";
import { lazy, Suspense } from "react";

//필요할 때만 로딩하도록 lazy loading 만들기
const BlogPage = lazy(() => import("./pages/Blog"));

  const router = createBrowserRouter([
  {
    path: "/",
    element: <RootLayout />,
    children: [
      {
        index: true,
        element: <HomePage />,
      },
      {
        path: "posts",
        children: [
          {
            index: true,
            element: (
              //🔥 컴포넌트 lazy loading
              <Suspense fallback={<p>Loading...</p>}>
                <BlogPage />
              </Suspense>
            ),
            loader: () =>
              import("./pages/Blog").then((module) => module.loader()),
          }, //🔥 로더 lazy loading
          { path: ":id", element: <PostPage />, loader: postLoader },
        ],
      },
    ],
  },
]);

function App() {
  return <RouterProvider router={router} />;
}

export default App;
  • 이렇게 하면 홈에서 블로그를 눌러 블로그 페이지로 넘어갈 때, 블로그 페이지와 관련된 파일들이 그제서야 로딩되는 것을 볼 수 있다.
    지연 로딩을 적용했기 때문에 자바스크립트 파일은 동적으로 다운로드 된다.

이렇게 필요할 때만 파일을 lazy loading 할 수 있다.

3. PostPage도 지연시켜 보기

  • 백엔드에서 ✅게시물 데이터를 다운로드하는 요청은 있지만, 이 ❌컴포넌트의 코드를 다운로드하는 요청은 없음

  • PostPage도 지연로딩을 적용해 보면 이렇게 컴포넌트 코드가 포스트 페이지에 방문해야만, 지연되어 로딩되는 것을 확인할 수 있다.

작은 앱에서는 지연 로딩이 필요 없지만 알아 두자~


3. 프로덕션용 앱 빌드


개발에 사용하는 코드 자체를 서버에 업로드 하지는 않는다.
현재 개발에 사용하는 코드는 가독성은 좋지만 브라우저에서 지원하지 않는 기능도 몇가지 사용 중이다.
JSX 코드는 브라우저에서 지원하지 않는다.

따라서 서버 업로드 전, 최종 사용자가 사용할 수 있는 프로덕션 용 코드로 변환해야 한다.
실제로 미리보기 웹 페이지도 변환된 코드로 브라우저에 보여주는 것이다.
우리가 npm start 명령어로 띄우는 개발 서버가 우리가 작성한 코드를 실시간으로 변환해주고 있다.

1. 일단 코드를 업로드하려면 최적화가 필요하기 때문에 개발 서버 종료(Ctrl + C)

2. npm run build

현재 이 프로젝트는 CRA로 생성해 뒀기 때문에 build 스크립트가 있는데, npm run build로 프로덕션용 앱을 빌드하면 된다.

참고
올해부터인가 CRA 더 이상 권장하지 않는다고 하니 Next.js나 vite 등으로 리액트 앱을 만들자..^^ㅠ...

  • npm run build 명령어로 build를 하면 이 과정을 통해, 업로드할 준비가 완료된 최적화된 변환 코드 번들이 생성되어 build폴더가 생성된다.

이 build 폴더에 있는 내용을 서버에 업로드하면 된다!

  • build/static 폴더 안에는 최적화된 JS 파일
    초기에 로딩되는 main코드와 동적으로 지연 로딩되는 많은 코드가 들어 있다. 이 파일에 우리가 작성한 모든 코드와 리액트 라이브러리를 포함해 우리가 사용하는 모든 서드파티 패키지가 들어 있다.
    • 비록 가독성은 매우 떨어지지만 실행할 수 있는 유효한 코드이다.

4. 서버에 프로덕션 코드 업로드


프로덕션용 앱을 빌드했으니 서버에 업로드하여 배포하면 된다.

웹사이트를 배포하는 데 중요한 점은 리액트 SPA가 정적 웹사이트라는 점이다.
(A React SPA(Single Page Appplication) is a "Static Website".)

  • HTML, CSS, JS 파일, image 파일만으로 구성되고, 서버에서 실행되어야 하는 코드가 없다.
  • 모든 코드가 브라우저에서 파싱되고 방문자의 컴퓨터에서 실행된다.
  • 따라서 정적 웹사이트 호스트를 사용하면 된다. (A Static Site Host is needed.)

서버에서 코드를 실행하는 호스팅 제공자도 따로 있는데, 예를 들어 풀스택 리액트 앱을 만든다면 그런 호스트를 사용해야 한다.

정적 웹사이트 배포하기

다양한 배포 및 호스팅 제공자가 있지만 간단하게 Firebase 호스팅을 사용하자. 공짜니깐..^^!


1. 파이어베이스에서 새로운 프로젝트 생성

2. hosting 섹션 클릭 후 시작하여 Firebase 호스팅 설정하기

1. Firebase CLI 설치

  • npm install -g firebase-tools

2. 프로젝트 초기화

터미널 창을 열어 웹 앱의 루트 디렉터리에서 다음 명령어들을 실행한다.

2-1. 구글 로그인

  • firebase login

  • 로그인 완료시 아래 창이 뜬다.

2-2. 프로젝트 설정 초기화

  • firebase init

  • 프로젝트 설정 초기화 하면 아래 질문들이 나오는데, 질문에 대답하면서 프로젝트 초기 설정을 완료하면 된다.


    ? 이 디렉토리에 대해 설정할 Firebase 기능을 선택하세요. 스페이스키를 눌러 기능를 선택한 다음 Enter 키를 눌러 선택을 확인합니다.
    호스팅: Firebase Hosting 및 (선택 사항) GitHub Action 배포 셋업
    👉 호스팅만 진행할 예정이므로 호스팅만 선택


    === 프로젝트 셋업
    먼저 이 프로젝트 디렉터리를 Firebase 프로젝트와 연결해 보겠습니다.
    firebase use --add를 실행하여 여러 프로젝트 별칭을 만들 수 있습니다,
    하지만 지금은 기본 프로젝트만 설정할 것입니다.
    ? 옵션을 선택하십시오: 기존 프로젝트 사용
    ? 이 디렉터리에 대한 기본 Firebase 프로젝트 선택:
    react-deployment-development-e348(react-deployment-development-development)
    i 프로젝트 react-deployment-demo-fe348(react-deployment-demo) 사용
    👉 파이어베이스에 만들어 둔 프로젝트 중 지금 호스팅할 프로젝트를 선택


    === 호스팅 설정
    당신의 프로젝트 디렉토리와 관련된 폴더인 public 디렉토리는 파이어베이스 배포와 함께 업로드할 호스팅 assets이 포함됩니다. 만약 assets에 build 프로세스가 있다면, build의 출력 디렉터리를 사용합니다.
    ? public 디렉토리로 사용할 디렉토리는 무엇입니까? build
    👉 build 폴더에 프로덕션용 코드가 있기 때문에 build 로 설정
    ? SPA(단일 페이지 앱)으로 구성하시겠습니까(모든 URL을 /index.html로 다시 작성하겠습니까)? Yes
    ? GitHub를 사용하여 자동 빌드 및 배포를 설정하시겠습니까? No
    ? 파일 build/index.html이 이미 있습니다. 덮어쓰겠습니까? No
    ibuild/index.html 쓰기를 건너뜁니다


    구성 정보를 firebase.json에 쓰는 중...
    프로젝트 정보를 .firebaser에 쓰는 중...


    ✔ Firebase 초기화 완료!텍스트

  • 초기설정을 마치면 아래와 같이 파이어베이스 설정 파일이 생성된다.

2-3. Firebase 호스팅에 배포

  • firebase deploy
    준비되면 웹 앱을 배포하면 된다.

  • 배포 후, url이 생성된다. 호스팅 완료!

  • 👉 배포된 웹 앱

📚 사용자 지정 도메인 추가

배포 후, Hosting 섹션에서 배포된 url에 커스텀 도메인을 추가할 수도 있다.

📚 웹 사이트 배포 중단 및 오프라인 전환

웹사이트를 오프라인으로 전환하여 접근할 수 없게 변경하려면 터미널에서 다음 명령어를 입력하면 된다.

  • firebase hosting:disable

📚 배포를 위해 프로젝트를 설정하는 방법: SPA


SPA, 즉 클라이언트 측 라우팅을 위해 필요한 환경 설정

웹사이트를 배포할 때 SPA로 설정하기 원하는지에 대한 질문이 있었는데 이 부분은 아주 중요한 부분이다.
배포한 웹사이트는 여러 페이지 간에 이동, 즉 라우트 간의 이동이 가능하다.
이는 리액트 라우터에서 제공하고 있다.

URL을 확인하고 해당하는 컴포넌트를 불러오는 과정이 브라우저에서 실행되고 있다.
그래서 react-router-dom 이라고 이름이 붙여진 것이다.
이는 클라이언트 측 패키지로, 서버에서 실행되는 것이 아니다.

이 프로젝트에는 서버에서 실행되는 코드가 없다.
네비게이션의 블로그를 클릭하면 블로그 컴포넌트로 넘어가는 로직은, 서버가 아닌 브라우저에서 실행된다.
링크를 클릭하는 대신 도메인 뒤에 /posts URL을 직접 입력해도 정상적으로 블로그 페이지로 넘어간다.
하지만 당연히 작동한다고 생각하면 안된다.

왜냐하면 실제로는, 앱을 서버에 배포하고나서, 사용자가 브라우저에 URL을 입력하면, 기술적으로 브라우저는 서버에 요청을 보내기 때문이다. 브라우저에 URL을 입력하면 언제나 웹사이트에 요청을 보내야 하므로 항상 일어나는 일이다.
서버는 요청에 응답해야 하는데, 리액트 애플리케이션을 호출하게 된다.
HTML, CSS, 앱을 구성하는 JS 코드를 응답으로 전송한다.

주 도메인이 아닌 /posts 등 경로를 추가해 요청을 보냈다면 요청에 따라 해당하는 경로가 서버에 전달된다.
그리고 기본적으로 서버는 요청한 경로에 해당하는 응답을 찾기 위해 노력한다.
하지만 당연히 서버 측에서 다양한 경로의 요청을 처리하는 로직이 없기 때문에, 해당하는 파일을 찾는데는 실패한다.
그 대신, 서버는 언제나 동일한 HTML 파일과 JS 코드를 반환하여 요청한 경로를, 클라이언트 측에서 요청한 JS 코드인 리액트 앱의 리액트 라우터로 처리하도록 만든다.

서버는 기본적으로 항상 동일한 파일을 반환하지 않는다. 기본적으로 서버는 폴더 내에서 요청에 해당하는 파일을 찾으려고 노력한다.

이런 이유 때문에, SPA로 설정할지 물어보는 질문이 있다.

  • 🌟 SPA로 설정하면,
    Firebase 서버에 어떤 경로로 요청이 오든지 index.html만 반환하도록 환경 설정된다.
    언제나 동일한 JS 파일을 요청하게 된다.
    서버 측 라우팅 대신, 클라이언트 측 라우팅을 사용하도록 설정되는 것이다.

따라서 현재 이 앱이 잘 작동되고는 있지만, SPA로 설정되어 있다는 사실을 꼭 기억해야 한다.
만약 다른 호스팅 제공자를 사용하게 될 경우, SPA로 설정하지 물어보지 않을 수도 있는데 그럴 경우 모든 요청에 대해 index.html을 반환하도록 직접 리디렉션 규칙을 설정해야 한다.

클라이언트 측 라우팅 및 서버 측 라우팅의 차이를 잘 알아두자~!

profile
Always have hope🍀 & constant passion🔥

0개의 댓글