Comments
Loading Comment Form...
Loading Comment Form...
deleted
이 웹사이트, 『기사도 댓글도 자동으로 번역되는 다국어 커뮤니티 Evame』의 메타 프레임워크를 Remix에서 Next.js로, 인프라도 Render에서 Vercel로 마이그레이션했습니다.
2주 정도 걸린 대규모 마이그레이션이었지만, 결과에는 만족하며, "소규모~중규모 사이트라면 Remix/React Router, 대규모 사이트라면 Next.js를 선택할 가치가 매우 크다"라는 결론에 이르렀습니다.
이 글에서는 Remix에서 Next.js로의 마이그레이션 과정과 Remix와 Next.js에 대한 개인적인 비교를 적습니다. 어떤 것을 사용할지 고민 중이거나, 마이그레이션을 고려 중인 분은 꼭 읽어주세요!
サイト方針の変更と規模の拡大
当初Evameは、青空文庫やプロジェクト・グーテンベルク、過去の仏典と言ったpubllic domainのテキストに対訳を付与したくて始めました。しかし開発を進めるうちに、public domainのテキストだけではなくユーザが投稿した記事にも対訳を付与できるようにしたいと考えるようになりました。そもそもEvameを作り始めた目的は知識と物語の循環であり、そのためにはユーザーが記事を投稿できるシステムが必要不可欠だと考えたからです。この方針の変更によってサイトの規模が拡大し、必要な機能が増え、大きなエコシステムを持つNext.jsに惹かれるようになりました。
Remixの今後の不明瞭さ
2024/11/22、Remix v2はReact Router v7とマージされました。 https://remix.run/blog/react-router-v7
Remix는 사라지고 React Router로 계속 이어지는 줄 알았는데, 위 블로그의 저자이기도 한 공동 설립자가 Twitter에서 다음은 Remix v3이 될 것이라고 말했습니다.
I don't have any further plans to work on RR v7. I'm personally done with that project.
— MJ (@mjackson) December 21, 2024
Remix v3 is just something I've been calling it internally. Since we haven't released a v3 yet, it would make sense to publish the next version of Remix as v3.
이름 변경은 에코시스템 전체에 변화를 강요하는 큰 일입니다. 실제로 다른 프로젝트에서 Remix v2에서 React Router v7로 업데이트했는데, Remix 관련 다양한 라이브러리를 사용할 수 없게 되어 많은 대응이 필요했고 상당한 작업량이 소요되었습니다. 다시 이름이 변경되면 동일한 대응을 해야 할 가능성이 있으며, 그렇게 되면 이번에 업데이트해도 다시 사용할 수 없게 되는 라이브러리가 많이 나올지도 모른다는 우려가 있었습니다.
React と Next.js の緊密化
https://nextjs.org/blog/next-12
React와 Next.js는 위 글에서처럼 점점 협력을 강화하고 있으며, Next.js는 다른 프레임워크보다 앞서 많은 기술을 사용하고 있습니다.
RSC가 Next.js에서 릴리스된 것은 2023년으로 이미 2년 가까이 지났는데, 아직 RSC를 사용할 수 있는 프레임워크가 거의 없다는 것이 이를 증명합니다.
앞으로도 우선적으로 신기능을 구현할 수 있다는 점은 큰 이점이라고 생각했습니다.
React 19를 완벽하게 활용할 수 있다
Remix/React Router v7에서는 route modules의 loader/action으로 데이터를 처리하기 때문에, 컴포넌트와 데이터 부분이 분리되는 경향이 있었습니다. Next.js(App Router)에서는 RSC(React Server Components)나 Server Actions, useActionState 등을 활용함으로써, 컴포넌트와 데이터의 거리가 줄어들어 개발 생산성이 향상되었습니다. 또한, 콜로케이션에 기반한 디렉터리 구성이 Remix보다 쉬워진 점도 개발 생산성 향상에 기여했습니다.
풍부한 에코시스템
Next.js의 경우 Sentry, Supabase, Stripe 등 많은 서비스의 공식 예제가 있으며, 주변 라이브러리도 충실합니다. next/font나 next/image도 훌륭해서, 이로 인해 Lighthouse의 Performance가 100점, Best Practices도 100점이 되었습니다.
마이그레이션 전에는 다음과 같은 점수였기 때문에, 에코시스템의 힘을 크게 느끼고 있습니다.(Accessibility는 노력하겠습니다.)
AI 친화적
Claude나 GPT에 Remix로 작성하라고 지시해도 Next.js 코드를 출력하는 경우가 많았습니다. 사용자가 많은 만큼 Next.js의 경우 처음부터 사용 가능한 코드를 출력하는 비율이 높아졌다고 느낍니다. 이것도 AI 시대의 새로운 에코시스템이라고 부를 수 있을지도 모릅니다.
빌드가 느리다
Remix는 HMR이 빨라서 로컬 개발이 매우 편리했습니다. Next.js는 파일에 따라 빌드에 수 초가 걸리고, 때로는 새로 고침이 필요한 등 개발 경험 측면에서는 스트레스가 있습니다.
멘탈 모델의 복잡성
Remix의 멘탈 모델은 loader→component→action이라는 간단하고 이해하기 쉬운 흐름이었습니다.
Next.js의 경우 RSC/Server Actions/revalidatePath/revalidateTag/next/dynamic/Request Memoization 등 다양한 개념이 등장합니다. 최적화되었기 때문에 Lighthouse 점수는 올랐지만, 체감상으로는 Remix 때와 속도에 차이가 없습니다. 사용자 입장에서는 체감 속도가 빠르면 상관없으므로, 자동으로 빨라지도록 해주면 좋겠다는 생각입니다.
흑마술
Remix가 출력하는 HTML은 매우 심플했습니다. 작성한 그대로 출력되는 느낌으로, 파악하기 쉬웠습니다.
Next.js는 프레임워크 고유의 로직으로 className이나 canvas 태그가 삽입되거나 디버깅이 복잡합니다. 또한, Edge Runtime과 관련하여 로컬에서는 동작하는데 본편에서는 동작하지 않는 문제에 자주 직면합니다.
먼저 브랜치를 만들고 create next를 실행하여 라이브러리 마이그레이션부터 시작했습니다. 구체적으로는 다음 라이브러리를 마이그레이션할 필요가 있었습니다.
remix-auth → auth.js
react-i18next → next-intl
remix-auth → auth.js 변경에는 DB 스키마 대응과 ORM으로 사용하고 있는 Prisma의 Edge Runtime 대응이 필요했으며, 마이그레이션 전체 중에서 이 부분이 가장 어려웠습니다.
DB 스키마는 auth.js 쪽에 맞춘 스키마를 준비하고, 기존 데이터를 마이그레이션하여 사용자 정의를 줄임으로써 업데이트 시 위험을 줄였습니다.
Edge Runtime은 Prisma Accelerate는 아직 사례가 적고 로컬과 본편의 차이가 클 것 같아 우려가 있었고, Drizzle로의 마이그레이션은 자유도가 높은 반면 작업량이 많았기 때문에, 결과적으로 Neon + Prisma를 선택하여 최소한의 작업량으로 줄였습니다.
다음으로 기존 Remix의 라우팅과 loader/action 부분을 Next.js App Router의 RSC/Server Actions로 대체했습니다. 여기서는 AI가 매우 유용했습니다. 하지만 useActionState나 Server Action은 아직 AI가 작성할 수 없기 때문에 직접 노력해야 합니다.
Server Actions에 공통 로직을 가지지 않는 노력
모든 Server Actions가 외부에 공개되기 때문에 인증 코드를 일일이 작성해야 합니다. 가능한 한 인증 로직을 정리한 함수나 Middleware로 대응할 수 없는지 검토해 봅시다.
RSC 컴포넌트 테스트
RSC는 현재 테스트 방법이 확립되어 있지 않기 때문에 비즈니스 로직이나 인증 로직을 분리하여 Unit 테스트를 가능하게 하는 것이 좋습니다.
useActionState의 형식 지정
useActionState의 반환값에 형식 정보를 부여하는 메커니즘은 아직 확립되어 있지 않습니다. 직접 정의하는 것이 현재로서는 더 좋습니다. 개인적으로는 다음과 같이 하고 있습니다.(about_hiroppy님의 트윗을 참고했습니다.)
export type ActionResponse<T = void, U = Record<string, unknown>> = {
success: boolean;
message?: string;
data?: T;
zodErrors?: typeToFlattenedError<U>["fieldErrors"];
};
폼의 형식 검증
Server Action 내에서 유효성 검사를 하는 것이 좋습니다. 개인적으로는 zod를 다음과 같이 사용하고 있습니다.
const editPageContentSchema = z.object({
slug: z.string().min(1),
title: z.string().min(1).max(100),
pageContent: z.string().min(1),
});
const parsedFormData = editPageContentSchema.safeParse({
slug: formData.get("slug"),
title: formData.get("title"),
pageContent: formData.get("pageContent"),
});
소규모~중규모 애플리케이션
최신 React 기능을 활용할 필요가 없다면 Remix/React Router는 간단하고 빠른 개발 경험을 얻기 쉽습니다. 멘탈 모델도 명확합니다.
B2B 또는 SPA라면 개인적으로 Remix/React Router를 추천합니다.
중규모~대규모 또는 에코시스템을 활용하고 싶은 경우
Next.js를 선택하면 서비스 및 라이브러리와의 연동이 쉽고, 미래의 React 업데이트에도 선행하여 대응할 수 있습니다.
B2C 또는 SSR을 하고 싶다면 Next.js를 추천합니다.
개인적으로는 "릴리스하고 성장하면 Next.js로 마이그레이션한다"는 접근 방식도 충분히 괜찮다고 생각합니다. Remix/React Router라면 Cloudflare를 사용하면 비용도 상당히 절감할 수 있습니다. 하지만 마이그레이션 시에는 역시 작업량이 많이 들기 때문에, 미래의 확장을 고려하고 있다면 처음부터 Next.js를 사용하는 것도 강력한 선택지입니다.
프레임워크, DB, 인프라 마이그레이션과 대규모 변경이었지만, 중요한 부분에 테스트 코드를 작성했던 것과 DB 관련 코드를 CQRS로 분리했던 덕분에 인증 관련 부분을 제외하고는 비교적 원활했습니다. 다시 한번 코드 품질과 아키텍처에 투자하는 것이 중요하다는 것을 실감했습니다.
이 사이트 "Evame"는 작성한 기사와 댓글을 AI가 자동 번역해 주는 다국어 커뮤니티입니다. 저도 이 마이그레이션기를 일본어로 작성하여 영어권에 공유할 수 있게 되었습니다.
관심이 있다면 좋아하는 언어로 기사를 작성하여 전 세계에 전달해 보세요!
이상 Remix/React Router v7에서 Next.js로 마이그레이션한 소감과 비교, 그리고 마이그레이션 절차입니다. 조금이라도 참고가 되는 부분이 있으면 좋겠습니다!