Remix 애플리케이션을 호스팅하는 방법은 무엇인가요?

Back4app 컨테이너 리믹스 커버

이 글에서는 Remix를 시작하기 위해 알아야 할 모든 것을 알려드립니다. 장단점을 살펴보고, Remix 앱을 빌드하는 방법을 시연하며, 마지막으로 Remix를 배포하는 방법을 설명합니다.

Remix란 무엇인가요?

Remix는 빠르고 매끄럽고 탄력적인 앱을 구축하기 위한 최신 풀스택 웹 프레임워크입니다. 서버 측 렌더링(SSR)의 강력한 기능을 활용하여 동적 웹 애플리케이션의 로드 시간을 단축하고 SEO를 개선할 수 있습니다.

Remix를 사용하면 개발자는 통합된 코드베이스 내에서 클라이언트 측과 서버 측 로직을 원활하게 통합할 수 있습니다. 다른 인기 있는 React 프레임워크와 비교했을 때, Remix는 여러 가지 면에서 다릅니다. 여기에는 다음이 포함됩니다:

  1. 파일 기반 라우팅 ( 라우팅 디렉터리에 있는 파일이 특정 경로를 나타냄)
  2. 기존 양식 처리 (PHP와 유사한 HTML 및 HTTP 요청 사용)
  3. 상태 관리 (상태는 서버에만 저장됨)
  4. 데이터 로딩 (컴포넌트에서 분리됨)

Remix는 2020년에 만들어졌으며 처음에는 연간 라이선스 비용으로 이용할 수 있었습니다. 이후 2021년 10월, Remix 팀은 프로젝트를 오픈소스화하기로 결정했습니다. 이제 MIT 라이선스에 따라 사용할 수 있습니다.

2022년 말, Remix는 Shopify에 21억 달러에 인수되었습니다.

Remix의 이점

Remix 프레임워크 사용의 주요 이점을 살펴보겠습니다.

파일 기반 탐색

Remix는 강력한 클라이언트 측 라우팅 솔루션인 React Router를 기반으로 구축되었습니다. 실제로 Remix는 React Router를 만든 개발자 팀이 만들었습니다.

이 프레임워크는 파일 기반 탐색 시스템을 활용하여 코드 구성을 간소화합니다. 이를 통해 개발자는 경로, 구성 요소 및 리소스를 특정 파일 또는 디렉터리에 연결할 수 있습니다.

다음은 한 가지 예입니다:

app/
└── routes/
    ├── $noteId.tsx              // matches: /<noteId>/
    ├── $noteId_.destroy.tsx     // matches: /<noteId>/destroy
    ├── $noteId_.edit.tsx        // matches: /<noteId>/edit
    ├── _index.tsx               // matches: /
    └── create.tsx               // matches: /create

무엇보다도, 중첩 라우팅을 통해 . 그 결과 로딩 시간이 단축되고 오류 처리가 개선되는 등 다양한 이점이 있습니다!

서버 측 렌더링(SSR)

Remix는 서버 측 렌더링의 강력한 기능을 활용합니다.

바닐라 React 앱에서 데이터는 일반적으로 클라이언트 측에서 가져온 다음 DOM에 주입됩니다. 이를 CSR(클라이언트 측 렌더링)이라고 합니다.

반면에 Remix는 다른 접근 방식을 취합니다. 먼저 백엔드에서 데이터를 가져오고, 가져온 데이터로 HTML을 렌더링한 다음 이를 클라이언트에 제공합니다.

서버 측 렌더링은 더 나은 성능과 더 SEO 친화적인 앱으로 이어지는 경향이 있습니다.

양식 처리

Remix는 양식 처리를 기본으로 돌아가게 합니다.

제어되는 여러 컴포넌트와 JavaScript를 사용하는 대신, 기존 HTML 양식과 HTTP 요청을 사용합니다.

양식이 제출되면 특정 경로로 HTTP 요청을 보내면 서버 측에서 action() 함수를 사용하여 처리합니다. 기존 PHP와 유사하게 작동합니다.

즉, Remix는 양식을 처리하는 데 JavaScript가 전혀 필요하지 않습니다. 이 점은 좋지만 양식 유효성 검사 및 오류 표시가 조금 더 까다로워질 수 있습니다.

상태 관리

여기서 상태는 서버와 클라이언트 데이터를 동기화하는 것을 의미합니다.

Remix를 사용하면 상태 관리가 쉬워집니다. Redux, Zustand, React Query, Apollo 또는 기타 클라이언트 측 상태 관리 라이브러리가 필요하지 않습니다.

Remix를 사용할 때 모든 상태는 서버에서 처리합니다. 클라이언트는 사실상 상태를 보유하지 않으므로 동기화 프로세스가 필요하지 않습니다.

서버에서 클라이언트로 전달되는 데이터는 loader( ) 및 action()과 같은 다양한 함수를 통해 전달됩니다.

전환 및 낙관적 UI

Remix 전환은 데이터를 미리 로드하고 애니메이션을 사용하여 페이지 간을 부드럽고 빠르게 이동합니다.

최적화 UI는 사용자 행동을 즉시 반영하여 변경 사항이 확정되기 전에 표시함으로써 웹사이트의 반응성을 높입니다.

전환과 최적화된 UI는 체감 지연 시간을 줄이고 즉각적인 피드백을 제공함으로써 사용자 경험을 크게 향상시킵니다.

Remix의 한계

Remix는 훌륭하지만 몇 가지 제한 사항이 있습니다.

복잡한

Remix는 사용하기 가장 쉬운 프레임워크는 아니며, 문서도 (이 글을 쓰는 시점에서) 최고 수준은 아닙니다.

이 프레임워크는 또한 바닐라 React와 다른 많은 것들을 수행합니다. React 개발자라면 다양한 Remix 개념을 모두 이해하는 데 시간이 걸릴 수 있습니다.

덜 인기

Remix는 아직 비교적 새로운 프레임워크입니다. 공개 출시는 2021년 10월로 거슬러 올라갑니다. Next.js와 같은 일부 경쟁사에 비해 덜 성숙하고 실전 테스트를 거쳤습니다.

GitHub 별점을 보면 Remix(별점 2만 7천 개)가 Next.js(별점 12만 개)보다 인기가 훨씬 낮다는 것을 알 수 있습니다. 이 글을 쓰는 시점에서 Remix는 Shopify를 제외한 어떤 거대 기술 기업에서도 채택하지 않았습니다.

긴밀하게 결합

Remix 앱은 프론트엔드와 백엔드가 긴밀하게 결합되어 있습니다. 이 접근 방식은 소규모 프로젝트에 적합하지만 일부 개발자는 프론트엔드와 백엔드를 분리하는 유연성을 선호합니다.

또한 프론트엔드와 백엔드를 분리하면 코드를 더 쉽게 유지 관리하고 테스트할 수 있습니다.

SSG 또는 ISR 없음

Remix는 정적 사이트 생성(SSG) 또는 점진적 정적 재생성(ISR)을 지원하지 않습니다.

Remix 애플리케이션을 배포하는 방법은 무엇인가요?

이 섹션에서는 Remix 애플리케이션을 빌드하고 배포해 보겠습니다.

전제 조건

따라 하려면 다음이 필요합니다:

프로젝트 개요

이 글 전체에서 저희는 노트 웹 앱에 대해 작업할 것입니다. 웹 앱을 통해 사용자는 노트를 만들고, 검색하고, 편집하고, 삭제할 수 있습니다.

백엔드에는 Back4app BaaS를 활용하고 프론트엔드에는 Remix 프레임워크를 사용합니다. 프론트엔드 코딩이 완료되면 Back4app 컨테이너에 배포합니다.

최종 결과물은 다음과 같이 표시됩니다:

Back4app 리믹스 노트

먼저 노트 웹 앱을 따라 해보시기 바랍니다. 이 글을 다 읽고 나면 여러분만의 Remix 애플리케이션을 배포할 수 있을 것입니다.

백엔드

이 글 섹션에서는 Back4app을 사용하여 앱의 백엔드를 구축하겠습니다.

앱 만들기

먼저 Back4app 계정에 로그인(또는 아직 계정이 필요한 경우 생성 )하세요.

로그인하면 ‘내 앱’ 포털로 리디렉션됩니다. 백엔드를 만들려면 먼저 Back4app 앱을 만들어야 합니다. 그러려면 “새 앱 만들기”를 클릭합니다.

Back4app 앱 대시보드

Back4app은 두 가지 솔루션을 제공합니다:

  1. 서비스형 백엔드(BaaS) – 본격적인 백엔드 솔루션
  2. 서비스형 컨테이너(CaaS) – Docker 기반 컨테이너 오케스트레이션 플랫폼

백엔드에서 작업 중이므로 ‘서비스형 백엔드’를 선택합니다.

Back4app BaaS 생성

앱 이름을 지정하고 다른 모든 항목은 기본값으로 두고 “만들기”를 클릭합니다.

Back4app 앱 세부 정보 만들기

플랫폼에서 백엔드에 필요한 모든 것을 설정하는 데 시간이 걸립니다. 여기에는 데이터베이스, 애플리케이션 인터페이스, 확장, 보안 등이 포함됩니다.

앱이 준비되면 앱의 실시간 데이터베이스 보기로 리디렉션됩니다.

Back4app 데이터베이스 보기

데이터베이스

이제 데이터베이스를 관리해 보겠습니다.

비교적 간단한 앱을 만드는 것이므로 하나의 모델만 필요하므로 이름을 Note로 정하겠습니다. 생성하려면 화면 왼쪽 상단의 “클래스 만들기” 버튼을 클릭합니다.

노트에 이름을 지정하고 “공개 읽기 및 쓰기 사용”을 활성화한 다음 “클래스 만들기 및 열 추가”를 클릭합니다.

Back4app 데이터베이스 클래스 생성

그런 다음 다음 열을 추가합니다:

+-----------+--------------+----------------+----------+
| Data type | Name         | Default value  | Required |
+-----------+--------------+----------------+----------+
| String    | emoji        | <leave blank>  | yes      |
+-----------+--------------+----------------+----------+
| String    | title        | <leave blank>  | yes      |
+-----------+--------------+----------------+----------+
| File      | content      | <leave blank>  | no       |
+-----------+--------------+----------------+----------+

클래스를 만들었으면 샘플 데이터로 데이터베이스를 채웁니다. 이모티콘, 제목, 콘텐츠를 입력하여 몇 가지 메모를 작성합니다. 또는 이 데이터베이스 내보내기를 가져옵니다.

백4앱 데이터베이스가 채워짐

좋아요, 그거예요!

코드를 작성하지 않고도 백엔드를 성공적으로 만들었습니다.

서비스형 백엔드에 대해 자세히 알아보려면 서비스형 백엔드란 무엇인가요?

프론트엔드

이 글 섹션에서는 Remix 프레임워크를 사용하여 앱의 프론트엔드를 구축해 보겠습니다.

create-remix

Remix 프로젝트를 부트스트랩하는 가장 쉬운 방법은 create-remix 유틸리티를 사용하는 것입니다. 이 도구는 프로덕션에 사용할 수 있는 Remix 프로젝트를 생성합니다.

디렉토리 구조와 종속성을 관리하고 번들러를 설정하는 등의 작업을 수행합니다.

다음 명령을 실행하여 새 Remix 프로젝트를 만듭니다:

$ npx create-remix@latest remix-notes

Initialize a new git repository? Yes
Install dependencies with npm?   Yes

create-remix를 사용한 적이 없는 경우 자동으로 설치됩니다.

프로젝트가 생성되면 활성 디렉터리를 해당 프로젝트로 변경합니다:

$ cd remix-notes

마지막으로 개발 서버를 시작합니다:

$ npm run dev

즐겨찾는 웹 브라우저를 열고 http://localhost:5173 으로 이동합니다. 화면에 기본 Remix 랜딩 페이지가 나타납니다.

TailwindCSS

조금 더 쉽게 만들기 위해 TailwindCSS를 사용하겠습니다. TailwindCSS는 일반 CSS를 작성하지 않고도 프론트엔드를 빠르게 구축할 수 있는 유틸리티 우선 프레임워크입니다.

먼저 npm을 사용하여 설치합니다:

$ npm install -D tailwindcss postcss autoprefixer

다음으로 tailwindcss init을 실행합니다:

$ npx tailwindcss init --ts -p

이렇게 하면 프로젝트가 구성되고 프로젝트 루트에 tailwind.config.ts 파일이 생성됩니다.

콘텐츠 속성을 다음과 같이 수정하여 어떤 파일에 유틸리티 클래스가 사용되는지 Tailwind에 알립니다:

// tailwind.config.ts

import type {Config} from "tailwindcss";

export default {
  content: ["./app/**/*.{js,jsx,ts,tsx}"],  // new
  theme: {
    extend: {},
  },
  plugins: [],
} satisfies Config;

디렉토리에 다음 내용으로 tailwind.css라는 새 파일을 만듭니다:

/* app/tailwind.css */

@tailwind base;
@tailwind components;
@tailwind utilities;

마지막으로 링크를 통해 root.tsx로 가져옵니다:

// app/root.tsx

// other imports
import {LinksFunction} from "@remix-run/node";
import stylesheet from "~/tailwind.css?url";

export const links: LinksFunction = () => [
  { rel: "stylesheet", href: stylesheet },
];

// ...

그거예요!

TailwindCSS를 성공적으로 설치했습니다.

경로

웹 앱에는 다음과 같은 엔드포인트가 있습니다:

  1. / 모든 노트를 표시합니다.
  2. /create를 사용하면 사용자가 노트를 만들 수 있습니다.
  3. / 특정 메모를 표시합니다.
  4. / /삭제는 사용자가 특정 노트를 삭제할 수 있게 해줍니다.

이러한 경로를 정의하려면 폴더에 다음 디렉터리 구조를 만듭니다:

app/
└── routes/
    ├── $noteId.tsx
    ├── $noteId_.destroy.tsx
    ├── $noteId_.edit.tsx
    ├── _index.tsx
    └── create.tsx

짐작하셨겠지만 접두사 $는 동적 매개변수에 사용되며 / 대신 .

조회수

이제 뷰를 구현해 보겠습니다.

코드의 유형 안전성을 높이기 위해 Note 데이터베이스 클래스와 유사한 Note라는 인터페이스를 만들겠습니다.

store라는 폴더를 만들고 그 안에 다음 내용으로 NoteModel.ts를 만듭니다:

// app/store/NoteModel.ts

export default interface NoteModel {
  objectId: string;
  emoji: string;
  title: string;
  content: string;
  createdAt: Date;
  updatedAt: Date;
}

그런 다음 _index.tsx, $noteId.tsxcreate.tsx에 대한 보기 코드를 붙여넣습니다:

// app/routes/_index.tsx

import {Link, NavLink} from "@remix-run/react";
import NoteModel from "~/store/NoteModel";

const notes = [
  {objectId: "1", emoji: "📝", title: "My First Note"},
  {objectId: "2", emoji: "📓", title: "My Second Note"},
  {objectId: "3", emoji: "📔", title: "My Third Note"},
] as NoteModel[];

export default function Index() {
  return (
        <>
      <Link to={`/create`}>
        <div className="bg-blue-500 hover:bg-blue-600 text-lg font-semibold text-white
         px-4 py-3 mb-2 border-2 border-blue-600 rounded-md"
        >
          + Create
        </div>
      </Link>
      {notes.map(note => (
        <NavLink key={note.objectId} to={`/${note.objectId}`}>
          <div className="hover:bg-slate-200 text-lg font-semibold
            px-4 py-3 mb-2 border-2 border-slate-300 rounded-md"
          >
            {note.emoji} {note.title}
          </div>
        </NavLink>
      ))}
    </>
  );
}
// app/routes/$noteId.tsx

import {Form} from "@remix-run/react";
import NoteModel from "~/store/NoteModel";

const note = {
  objectId: "1", emoji: "📝", title: "My First Note", content: "Content here.",
  createdAt: new Date(), updatedAt: new Date(),
} as NoteModel;

export default function NoteDetails() {
  return (
    <>
      <div className="mb-4">
        <p className="text-6xl">{note.emoji}</p>
      </div>
      <div className="mb-4">
        <h2 className="font-semibold text-2xl">{note.title}</h2>
        <p>{note.content}</p>
      </div>
      <div className="space-x-2">
        <Form
          method="post" action="destroy"
          onSubmit={(event) => event.preventDefault()}
          className="inline-block"
        >
          <button
            type="submit"
            className="bg-red-500 hover:bg-red-600 font-semibold text-white
              p-2 border-2 border-red-600 rounded-md"
          >
            Delete
          </button>
        </Form>
      </div>
    </>
  );
}
// app/routes/create.tsx

import {Form} from "@remix-run/react";

export default function NoteCreate() {
  return (
    <>
      <div className="mb-4">
        <h2 className="font-semibold text-2xl">Create Note</h2>
      </div>
      <Form method="post" className="space-y-4">
        <div>
          <label htmlFor="emoji" className="block">Emoji</label>
          <input
            type="text" id="emoji" name="emoji"
            className="w-full border-2 border-slate-300 p-2 rounded"
          />
        </div>
        <div>
          <label htmlFor="title" className="block">Title</label>
          <input
            type="text" id="title" name="title"
            className="w-full border-2 border-slate-300 p-2 rounded"
          />
        </div>
        <div>
          <label htmlFor="content" className="block">Content</label>
          <textarea
            id="content" name="content"
            className="w-full border-2 border-slate-300 p-2 rounded"
          />
        </div>
        <div>
          <button
            type="submit"
            className="bg-blue-500 hover:bg-blue-600 font-semibold
              text-white p-2 border-2 border-blue-600 rounded-md"
          >
            Create
          </button>
        </div>
      </Form>
    </>
  );
}

이 코드에는 화려한 것은 없습니다. JSX와 TailwindCSS를 함께 사용하여 사용자 인터페이스를 만들었을 뿐입니다.

눈치챘겠지만, 모든 컴포넌트는 제어되지 않습니다( 사용State()를 사용하지 않습니다). 게다가 실제 HTML 양식을 사용하고 있습니다.

이는 Remix 프레임워크가 React와 달리 HTTP 요청을 사용하여 PHP와 유사한 양식을 처리하기 때문입니다.

Parse

Back4app 기반 백엔드에 연결하는 방법에는 여러 가지가 있습니다. 사용할 수 있습니다:

  1. RESTful API
  2. GraphQL API
  3. Parse SDK

가장 쉽고 강력한 방법은 바로 Parse SDK입니다. Parse SDK는 데이터를 쉽게 쿼리하고 조작할 수 있는 다양한 유틸리티 클래스와 메서드를 제공하는 소프트웨어 개발 키트입니다.

먼저 npm을 통해 Parse를 설치하세요:

$ npm install -i parse @types/parse

다음과 같이 프로젝트 루트에 .env 파일을 만듭니다:

# .env

PARSE_APPLICATION_ID=<your_parse_app_id>
PARSE_JAVASCRIPT_KEY=<your_parse_js_key>

교체해야 합니다. <your_parse_app_id><your_parse_js_key> 를 실제 자격 증명으로 교체해야 합니다. 자격 증명을 얻으려면 앱으로 이동하여 사이드바에서 ‘앱 설정 > 서버 및 보안’을 선택합니다.

그런 다음 root.tsx 파일 상단에서 다음과 같이 Parse를 초기화합니다:

// app/root.tsx

// other imports
import Parse from "parse/node";

const PARSE_APPLICATION_ID = process.env.PARSE_APPLICATION_ID || "";
const PARSE_HOST_URL = "https://parseapi.back4app.com/";
const PARSE_JAVASCRIPT_KEY = process.env.PARSE_JAVASCRIPT_KEY || "";
Parse.initialize(PARSE_APPLICATION_ID, PARSE_JAVASCRIPT_KEY);
Parse.serverURL = PARSE_HOST_URL;

백엔드 통신 로직에 대한 뷰를 깔끔하게 유지하기 위해 api/backend.ts라는 별도의 파일을 만들겠습니다.

다음 내용으로 api/backend.ts를 생성합니다:

// app/api/backend.ts

import Parse from "parse/node";
import NoteModel from "~/store/NoteModel";

export const serializeNote = (note: Parse.Object<Parse.Attributes>): NoteModel => {
  return {
    objectId: note.id,
    emoji: note.get("emoji"),
    title: note.get("title"),
    content: note.get("content"),
    createdAt: new Date(note.get("createdAt")),
    updatedAt: new Date(note.get("updatedAt")),
  };
}

export const getNotes = async (): Promise<NoteModel[]> => {
  // ...
}

export const getNote = async (objectId: string): Promise<NoteModel | null> => {
  // ...
}

// Grab the entire file from: 
// https://github.com/duplxey/back4app-containers-remix/blob/master/app/api/backend.ts

마지막으로 뷰를 수정하여 백엔드의 데이터를 가져오고 조작합니다:

// app/routes/index.tsx

import {json} from "@remix-run/node";
import {Link, NavLink, useLoaderData} from "@remix-run/react";
import {getNotes} from "~/api/backend";

export const loader = async () => {
  const notes = await getNotes();
  return json({notes});
};

export default function Index() {
  const {notes} = useLoaderData<typeof loader>();
  return (
    // ...
  );
}
// app/routes/$noteId.tsx

import {getNote} from "~/api/backend";
import {json, LoaderFunctionArgs} from "@remix-run/node";
import {Form, useLoaderData} from "@remix-run/react";
import {invariant} from "@remix-run/router/history";

export const loader = async ({params}: LoaderFunctionArgs) => {
  invariant(params.noteId, "Missing noteId param");
  const note = await getNote(params.noteId);
  if (note == null) throw new Response("Not Found", {status: 404});
  return json({note});
};

export default function NoteDetails() {
  const {note} = useLoaderData<typeof loader>();
  return (
    // ...
  );
}
// app/routes/create.tsx

import {ActionFunctionArgs, redirect} from "@remix-run/node";
import {Form} from "@remix-run/react";
import {createNote} from "~/api/backend";

export const action = async ({request}: ActionFunctionArgs) => {
  const formData = await request.formData();
  const {emoji, title, content} = Object.fromEntries(formData) 
                                    as Record<string, string>;
  const note = await createNote(emoji, title, content);
  return redirect(`/${note?.objectId}`);
};

export default function NoteCreate() {
  return (
    // ...
  );
}
// app/routes/$noteId_.destroy.tsx

import type {ActionFunctionArgs} from "@remix-run/node";
import {redirect} from "@remix-run/node";
import {invariant} from "@remix-run/router/history";
import {deleteNote} from "~/api/backend";

export const action = async ({params}: ActionFunctionArgs) => {
  invariant(params.noteId, "Missing noteId param");
  await deleteNote(params.noteId);
  return redirect(`/`);
};

코드 요약:

  • 데이터를 로드하기 위해 Remix loader() 함수를 사용했습니다. 데이터가 로드되면 json() 함수를 통해 데이터를 JSON으로 뷰에 전달했습니다.
  • 양식 제출(예: POST)을 처리하기 위해 Remix action() 함수를 사용했습니다.
  • noteId는 매개변수로 뷰에 전달되었습니다.

이제 앱이 완전히 작동하고 Back4app 백엔드와 동기화되었을 것입니다. 몇 가지 노트를 만들고 편집한 다음 삭제하여 모든 것이 작동하는지 확인하세요.

도커라이즈 앱

이 섹션에서는 Remix 프론트엔드를 도커화하겠습니다.

Dockerfile

Dockerfile은 도커 엔진이 이미지를 구성하기 위해 수행해야 하는 단계를 간략하게 설명하는 일반 텍스트 파일입니다.

이러한 단계에는 작업 디렉터리 설정, 기본 이미지 지정, 파일 전송, 명령 실행 등이 포함됩니다.

지침은 일반적으로 모두 대문자로 표시되며 바로 뒤에 각각의 인수가 표시됩니다.

모든 지침에 대해 자세히 알아보려면 Docker파일 참조를 확인하세요.

프로젝트 루트에 다음 내용으로 Docker파일을 생성합니다:

# Dockerfile

FROM node:20

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

EXPOSE 3000

CMD ["npm", "run", "start"]

Dockerfile은 node:20 이미지를 기반으로 합니다. 먼저 작업 디렉터리를 설정하고 package.json을 복사한 다음 종속 요소를 설치합니다.

그 후 프로젝트를 빌드하고 포트 3000을 노출한 후 앱을 서비스합니다.

.dockerignore

Docker로 작업할 때는 일반적으로 가능한 한 작은 이미지를 만들려고 노력합니다.

프로젝트에는 이미지에 포함할 필요가 없는 특정 파일(예: .git, 빌드, IDE 설정)이 포함되어 있으므로 제외하겠습니다. 이를 위해 .gitignore 파일과 유사하게 작동하는 .dockerignore 파일을 만들겠습니다.

프로젝트 루트에서 .dockerignore 파일을 만듭니다:

# .dockerignore

.idea/
.cache/
build/
node_modules/

프로젝트의 필요에 따라 .dockerignore를 조정하세요.

빌드 및 테스트

Docker 이미지를 클라우드로 푸시하기 전에 로컬에서 테스트하는 것이 좋습니다.

먼저 이미지를 작성합니다:

$ docker build -t remix-notes:1.0 .

그런 다음 새로 만든 이미지를 사용하여 컨테이너를 만듭니다:

$ docker run -it -p 3000:3000
    -e PARSE_APPLICATION_ID=<your_parse_app_id> 
    -e PARSE_JAVASCRIPT_KEY=<your_parse_javascript_key> 
    -d remix-notes:1.0

교체해야 합니다. <your_parse_app_id><your_parse_javascript_key> 실제 자격 증명으로 교체해야 합니다.

이제 http://localhost:3000 에서 앱에 액세스할 수 있습니다. 도커화 프로세스 이전과 동일한 방식으로 작동해야 합니다.

GitHub로 푸시

Back4app 컨테이너에 앱을 배포하려면 먼저 소스 코드를 GitHub에 푸시해야 합니다. 이를 위해 다음 단계를 따르세요:

  1. GitHub 계정에 로그인 (또는 가입)합니다.
  2. 새 GitHub 리포지토리를 만듭니다.
  3. 로컬 프로젝트로 이동하여 초기화: git init
  4. 버전 관리 시스템에 모든 코드를 추가합니다: git add .
  5. git 원격 추가 오리진을 통해 원격 오리진을 추가한다.
  6. git commit -m "초기 커밋"을 통해 모든 코드를 커밋합니다.
  7. GitHub에 코드 푸시하기 git push origin master

앱 배포

이 마지막 섹션에서는 Back4app 컨테이너에 프론트엔드를 배포해 보겠습니다.

Back4app 계정에 로그인하고 ‘새 앱 만들기’를 클릭하여 앱 생성 프로세스를 초기화합니다.

Back4app 앱 만들기

이제 컨테이너화된 앱을 배포하고 있으므로 “서비스형 컨테이너”를 선택합니다.

Back4app 서비스형 컨테이너

다음으로 GitHub 계정을 Back4app에 연결하고 이전에 생성한 리포지토리를 가져와야 합니다. 연결되면 리포지토리를 선택합니다.

Back4app 리포지토리 선택

Back4app 컨테이너는 고급 설정이 가능합니다. 하지만 간단한 앱의 경우 다음 설정으로 충분합니다:

  1. 앱 이름: Remix 노트(또는 이름 선택)
  2. 환경 변수: PARSE_APPLICATION_ID, PARSE_JAVASCRIPT_KEY

환경 변수에 .env 파일에 사용한 값을 사용합니다.

배포 구성을 완료했으면 ‘배포’를 클릭합니다.

Back4app 환경 구성

배포가 완료될 때까지 잠시 기다리세요. 배포가 완료되면 화면 왼쪽에 있는 녹색 링크를 클릭하여 브라우저에서 앱을 엽니다.

자세한 튜토리얼은 Back4app의 컨테이너 Remix 문서를 참조하세요.

이제 끝났습니다! 이제 앱이 성공적으로 배포되었으며 제공된 링크를 통해 액세스할 수 있습니다. 또한 Back4app에서 앱에 대한 무료 SSL 인증서를 발급했습니다.

결론

비교적 새로운 프레임워크임에도 불구하고 Remix를 사용하면 개발자는 강력한 풀스택 웹 애플리케이션을 구축할 수 있습니다.

이 프레임워크는 양식 처리, 상태 관리 등 웹 애플리케이션의 여러 가지 복잡한 문제를 해결합니다.

이 글에서는 Remix 애플리케이션을 빌드하고 배포하는 방법을 배웠습니다. 이제 Back4app을 활용하여 간단한 백엔드를 만들고 Back4app 컨테이너 를 활용하여 컨테이너화된 애플리케이션을 배포할 수 있을 것입니다.

프로젝트 소스 코드는 Back4app 컨테이너 Remix 리포지토리에서 확인할 수 있습니다.


Leave a reply

Your email address will not be published.