웹 호스팅, 배포 포스팅이랑 Docker 포스팅에서 언급했지만, 웹 애플리케이션을 개발하고 배포, 운용하는 과정은 상당히 복잡하다.

2024.04.11 - [Cloud Computing] - [Docker] Docker? 개념과 간단한 설명

 

[Docker] Docker? 개념과 간단한 설명

 

jghdg1234.tistory.com

2024.04.11 - [Cloud Computing] - 웹 서비스 배포, 호스팅

 

웹 서비스 배포, 호스팅

내가 처음 GUI수업을 들었을때가 생각난다. 처음 HTML로 못생긴 웹을 찍어내고, 그래도 내 딴엔 자료구조수업만 들었어서 처음으로 내 손으로 뭔가 웹사이트를 만든게 너무 신기했다. 그래서 이걸

jghdg1234.tistory.com

 

자 그럼, 이번에는 내가 만든 React feedback웹 애플리케이션을 Dockerize해 보도록 하겠다. 먼저 '도커화' 즉, dockerfile을 만들고, 이미지로 변환을 시켜야 컨테이너에서 운용 할 수 있다고 했는데, 어떻게 해야 할지 단계별로 살펴보자.

 

그 전에, DockerHub에 대해 정말 간단히 짚고 넘어가 보자. Dockerhub은 간단히 말해 우리가 흔히 생각하는 Appstore같은 것이다. 우리가 필요한 앱들을 우리 컴퓨터에 다운 받아서 쓰듯이, Docker Hub에서 우리는 program을 다운받는 대신 'image'라는것을 다운받는다. 그리고 program을 실행하면 process가 생성되는것 처럼, docker의 image을 실행하는것을 container 라고 부른다. 이렇게 비유한 이유는 뭐냐면, 하나의 프로그램이 여러개의 독립적인 프로세스를 가질 수 있듯이, 하나의 image는 여러개의 container를 실행 할 수 있다.

출처 : 생활코딩 Youtube

위 그림처럼 hub에서 이미지를 다운받는것을 'pull'이라고 하며, 그 이미지를 컨테이너에서 실행 시키는것을 'run'이라고 한다.

1. Docker를 설치한다.

Docker공식 홈페이지를 가면 OS에 따라 다운로드 가능한 버전들이 잘 나와 있다. 설치가 되면 터미널에 docker --version을 확인해 주는것을 항상 잊지 말자!

 

Docker가 정상적으로 설치 되었으면, 이제 나의 react app이 잘 돌아가는지 확인 해 보자. npm start를 했을때 정상적으로 페이지들이 렌더링 된다면 ok!

 

2. DockerFile 작성하기

이제, 가장 먼저 dockerfile을 작성해야 한다. 위의 Docker 포스팅에 가 보면 알겠지만, 먼저 dockerfile을 작성 한 후, 그 파일을 이미지로 변환 시켜야 한다.

 

먼저, 패키지 구조는 아래와 같다. 

이렇게 루트 디렉토리에 DockerFile이라는 파일을 하나 생성해 준다.

 

  • DockerFile 구성
FROM node:18-alpine

WORKDIR /app

COPY package.json package-lock.json ./

RUN npm install 

COPY . .

RUN npm run build

EXPOSE 3000

CMD [ "npm", "start" ]

 

FROM node:18-alpine Node.js 버전 18이 설치된 alpine Linux를 기반 이미지로 사용


WORKDIR /src Docker 컨테이너 내에서 작업할 디렉토리를 app/로 설정


COPY package.json package-lock.json호스트 컴퓨터의 package.json와 package-lock.json 파일을 작업 디렉토리에 복사


RUN npm install 명령을 실행하여 필요한 패키지들을 설치


COPY . . 호스트 컴퓨터의 모든 파일을 작업 디렉토리에 복사


EXPOSE 3000 컨테이너의 3000번 포트를 외부에 노출. 애플리케이션이 해당 포트에서 실행될 것임을 나타냄


CMD [ "npm", "start" ] 컨테이너가 실행되면 npm start 명령을 실행

 

  • 컨테이너 이미지 생성
#형식 : 
$ docker build -t <컨테이너 이미지 이름> .

$ docker build . -t feedback-app

성공적으로 생성이 되면 위와 같은 터미널 화면을 볼 수 있을것이다.

 

여기서 한번 더 확인해야 할것이 있는데, 우리는 방금 image를 생성 했으므로 Docker Desktop으로 이동해 이미지가 잘 생성이 되었는지 한번 더 확인 해 준다.

 

 

3. 생성된 image를 기반으로 continaer 실행하기

 

기본적인 형식은 아래와 같다.

$ docker run -d --name <컨테이너 이름> -p 3000:3000 <실행할 이미지 이름>

 

 

이제, 여기 Docker Desktop에 가서 containers를 보면, 우리가 방금 실행한 컨테이너가 올라와 있는것을 볼 수 있다.

 

자 그럼, 이제 우리가 생성한 container가 localhost:3000에서 잘 돌아가는지 한번 확인 해 보자.

 

잘 돌아간다! 이제 DockerDesktop에서 실행중인 container를 정지버튼을 눌러 멈춰보고 실행해 보자. 그러면 이제 실행이 안 될 것이다.

 

짠! 

 

이렇게 간단하게 Docker를 이용해 React애플리케이션을 image화 하고, container를 실행 시켜 보았다. 아직은 초기 단계이지만, 나중에 가면 더 복잡해 질것이다.

이번에는 React를 사용한 웹 애플리케이션에서 페이지간의 라우팅을 어떻게 하는지 정말 간단히 짚고 넘어가겠다.

 

이번 프로젝트에서 요구하는 사이트를 예로 들어보자. 이번 페이지의 규모는 그렇게 큰 규모가 아니기 때문에(아직까지는) 일단은 2개의 페이지만 있다. 로그인페이지, 피드백 페이지.

 

로그인 구현은 아직 하지 못했기 때문에 이번에는 간단히 로그인 버튼을 누르면 피드백 페이지로 이동하도록 라우팅을 설정 하도록 하겠다.

 

리액트에서 라우팅이란, 

단일 페이지 애플리케이션(Single Page Application, SPA) 내에서 사용자가 다양한 페이지를 탐색할 수 있도록 하는 메커니즘을 말한다. 전통적인 웹 애플리케이션에서 페이지 간의 이동은 새로운 페이지 요청을 서버에 보내고, 서버는 새로운 페이지를 클라이언트에 전송하는 방식으로 이루어진다. 이 과정에서 페이지 전체가 새로고침되며, 사용자는 그동안 딜레이를 겪게 된다.

그러나 리액트와 같은 SPA 프레임워크나 라이브러리에서는 클라이언트 사이드에서 라우팅을 처리한다. 즉, 사용자가 링크를 클릭할 때마다 서버에 새 페이지를 요청하는 대신, 리액트가 미리 로드해둔 컴포넌트를 화면에 렌더링하여 페이지를 변경한다. 이 과정에서 실제 페이지 전환은 발생하지 않으며, URL만 변경됨으로써 사용자에게 여러 페이지가 있는 것처럼 느껴지게 한다.

 

리액트 라우팅의 장점:

1. 속도: 모든 리소스가 처음에 한 번만 로드되고, 필요한 컴포넌트만 선택적으로 업데이트되므로 페이지 전환 시 새로고침이 필요 없어 속도가 매우 빠르다.
2. UX: 페이지를 새로고침하지 않기 때문에 훨씬 향상된 UX를 가진다. 사용자는 끊김 없는 화면이동과 즉각적인 페이지 전환을 경험할 수 있다.
3. 효율적인 자원 사용: 필요한 컴포넌트만 로드하고 렌더링하기 때문에, 네트워크 사용량과 서버 부하가 감소한다.
4. 검색 엔진 최적화(SEO)**: 리액트 라우팅 라이브러리 중 일부는 서버 사이드 렌더링(SSR)이나 정적 사이트 생성을 지원하여 SEO 문제를 해결한다.

새로고침 여부:
리액트 라우팅을 사용하면, 사용자가 애플리케이션 내에서 페이지 간에 이동할 때 웹 페이지의 새로고침 없이 뷰가 변경된다. URL은 변경되지만, 이는 브라우저의 History API를 통해 관리되며, 실제로 페이지를 새로 불러오는 것이 아니라 애플리케이션 상태의 변경을 반영한다.

예제 설명:

예를 들어, 로그인 페이지에서 피드백 페이지로의 이동을 구현하려면, 리액트의 `react-router-dom` 라이브러리를 사용하여 라우팅을 설정한다. 로그인 버튼을 누르면, `useNavigate` 훅을 사용하여 피드백 페이지로의 경로를 지정할 수 있다. 이러한 방식으로, 리액트 애플리케이션에서는 사용자의 액션에 따라 끊김 없는 페이지 전환을 구현할 수 있다.


 

자 그럼, 이번 프로젝트에서의 예시를 한번 보자.

 

먼저, 올바른 디렉토리에서 npm install react-router-dom 를 입력해 router-dom을 설치해준다. 여기서 잘 확인해야 할 것은, 현재 설치된 react와 router-dom의 버전에서 불일치가 발생하지 않도록 해야한다.(나도 단지 이것때문에 30분동안 스택오버플로우를 뒤졌다...)

 

설치가 되었으면, package.json으로 이동해 dependencies안에 제대로 설치가 되었는지 확인한다.

 

  "name": "feedback_collection_web_app",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.17.0",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^13.5.0",
    "again": "^0.0.1",
    "axios": "^1.6.7",
    "bootstrap": "^5.3.2",
    "react": "^18.2.0",
    "react-bootstrap": "^2.10.0",
    "react-dom": "^18.2.0",
    "react-hook-form": "^7.51.2",
    "react-modal": "^3.16.1",
    "react-router-dom": "^6.22.3",
    "react-scripts": "5.0.1",
    "react-toastify": "^10.0.4",
    "web-vitals": "^2.1.4"
  },

 

이렇게  "react-router-dom": "^6.22.3" 설치가 된것이 보이면, router는 제대로 설치가 된 것이다. 먼저, login page에서 login이라는 버튼을 누르면 feedback page로 이동 할 수 있도록 코드를 살펴보자.

 

먼저, loginpage.js로 이동해 아래처럼 import를 해 준다.

import { useNavigate } from 'react-router-dom';

 

 

React Router v5까지는 페이지 이동을 위해 useHistory 훅을 사용했었다. 그러나 v6부터는 useNavigate로 대체되어, 라우팅 및 페이지 이동 관련 기능을 좀 더 직관적이고 간결하게 사용할 수 있게 되었다. useNavigate 훅은 라우터의 history 스택에 직접적으로 접근하지 않고도 페이지 이동이나 뒤로 가기 등의 작업을 수행할 수 있게 해준다.

 

 

 

자 그럼, 우리는 login버튼 하나만 가지고 있으므로, 그 버튼에 대한 핸들러 함수를 추가한다. 단, 핸들러를 추가 하기 전, navigation을 담당 할 상수를 선언 해 준다.

const navigate = useNavigate();

 

그 다음 핸들러 함수를 작성 해 준다.

 

    const handleLogin = (event) => {
        event.preventDefault();
        toast.info('Navigating to VT CAS login page...');
        // login stuff going on
        setTimeout(() => {
          navigate('/FeedbackForm');
        }, 2000)
    };


...



          <Button onClick={handleLogin} style={{ backgroundColor: 'maroon', borderColor: 'maroon', 
          padding: '10px 20px', fontSize: '16px' }} type="submit">CAS Login</Button>

 

나는 핸들러 함수에 setTimeout함수를 추가해 toast로 유저들에게 로그인 페이지로 이동 중이라고 알려주었다. 저기서 위에서 선언한 상수 'navigate'에다가 ( 를 열고, import한 FeedbackForm이라는 컴포넌트를 넣어준다. 이렇게 하면 버튼이 클릭되었을때, navigation 상수가 알아서 페이지 간 전환을 담당 할 것이다. 이렇게 보니까 정말 간단하다.. 나는 의존성부터 시작해서 여러가지 애를 먹었지만 다음에는 이런 일이 없었으면 좋겠다.

 

이렇게 loginPage.js에서 라우팅을 담당하는 함수를 작성 한 후, React의 엔트리 포인트인 index.js에서 이제 페이지들을 어떻게 렌더링 할지 결정 해야한다.

 

현재 나의 index.js는

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import reportWebVitals from './reportWebVitals';
// Bootstrap CSS
import "bootstrap/dist/css/bootstrap.min.css";
// Bootstrap Bundle JS
import "bootstrap/dist/js/bootstrap.bundle.min";

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

 

그냥 이렇게 <App /> 컴포넌트를 받아 렌더링을 하기 때문에 나는 App.js를 수정해야 한다.

import FeedBackForm from './pages/FeedBackForm';
import LoginPage from './pages/loginPage';
import React from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';


function App() {
  return (
    <React.StrictMode>
    <BrowserRouter>
    <Routes>
      <Route path="/" element={<LoginPage />} />
      <Route path="/FeedbackForm" element={<FeedBackForm />} />
    </Routes>
    </BrowserRouter>
    </React.StrictMode>
  );
}

export default App;

 

보다시피 위에 BrowserRouter, Routes, Route를 import 한 후, 제일 상단을 <BrowserRouter>로 감싼다. 그 후, <Routes> 태그 안에는 우리가 이동하고싶은 페이지들의 경로를 <Route>태그 안에 넣어 준다. 여기서 짚고 넘어가야 할 것이 있다.

 

먼저, element = {<>}안에 들어 가야 할 내용은 우리가 import한 페이지 컴포넌트들을 넣어주면 된다.

 

그리고, 

위에서 보이는 <Route path ="/"는 루트 경로로 매핑 한다는 뜻이다. 즉, path에서 / 를 가진 컴포넌트가 웹을 실행할때 가장 먼저 렌더링 될 페이지이다. 보통의 웹 페이지라면 서비스를 이용하기 전에 로그인을 먼저 해야함으로 나는 login page를 루트 경로로 설정 해 줬다.(이걸 몰라서 화면에 아무것도 뜨지 않아 또 새벽 두시에 스오플을 뒤졌다...)

 

이렇게 모든 과정을 마친 후,

이렇게 페이지간 이동을 할 수 있게 되었다!

+ Recent posts