Reactへの移行戦略

2023/07/06に公開

こんにちは!CastingONEの大沼です。

はじめに

弊社ではNuxt2で作られていますが、Vue.js2系は今年中(2023年12月末)でサポートが切れてしまいます。Nuxt3にアップグレードするかReactにリプレイスするか色々検討しましたが、最終的には以下の理由からReactへリプレイスする方針に決まりました。

  • Vue.js3系の移行コストが高い
    • 2系から3系へのアップグレードにあたって破壊的な変更が多く、コードの修正がかなり必要
      • filtersが使えなくなる
      • v-modelの設定方法が変わる
      • etc.
    • 3系に対応されていないライブラリが多々あり、ライブラリの再選定が必要になるケースがある
    • UIライブラリで使用しているVuetifyが2系と3系の変更点が多く、その調整が必要
      • v-data-tableがExperimentalになっているなど、そもそもVuetifyのサポートがまだ追いついておらず別ライブラリにするか自前で作るかの検討も必要
  • エンジニア市場がReactの方が多い
    • どの求人サイトを見てもVue.jsよりReactの方が多い

Reactへリプレイスすることになりましたが、通常の機能開発は止めずに並行で進めていきたいと考えており、その辺の環境構築と運用を整えましたので記事にまとめました。

リプレイス先のサイトで使用するライブラリ

今回Reactへのリプレイスにあたって、フレームワークなどの主要なライブラリは以下のものを使用する予定です。比較のために現状Nuxtで使用しているライブラリも併せて載せておきます。

現環境(Vue.js) リプレイス先(React)
フレームワーク Nuxt2系 Next.js
UIフレームワーク Vuetify2系 MUI
API通信 axios axios & tanstack/react-query
store Vuex Redux
バリデーション Vuetify標準機能(rules) react-hook-form & yup
日付ライブラリ moment date-fns

画面単位でのリプレイス戦略

移行を並行して行えるようにするために、サイトを2つ用意して、Next.jsでの画面が実装出来次第そちらの画面へ切り替わる仕組みを考えました。先頭に /r/ がついている場合はNext.jsのサイトへリライトして同一ドメイン上でNext.jsアプリも表示できるようにしました。
イメージとしては以下のようになります。

Reactリプレイスイメージ

このような構成にすることで以下のようなメリットがあります。

  • Reactリプレイスを並行して進められる
    • Reactリプレイスの作業が独立しているため、通常の開発を阻害しない
  • localStorageなどドメインに紐づいた情報が共有可能
    • サイトが別でホスティングされていても、リライトによって同一ドメイン上で画面を表示させているため、localStorageにあるデータや認証情報などを共通して使える

次から詳細の実装について説明します。

Netlifyを使ったNext.jsアプリへリライトする仕組み

まず前提としてNext.jsのbaseUrlを/r/から始まるように設定しておきます。これでNext.jsアプリは必ず/r/から始まるURLからアクセスするようにします。こうすることで画面だけでなく、画像などのリソースも/r/から始まるようになり、リライト設定がシンプルになります。
弊社ではサイトをNetlifyでホスティングしているため、リライトの設定は_redirectsファイルに以下のような設定を書きました。

_redirects
# Next.jsページへリライト
/r/*  https://[Next.jsサイトのドメイン]/r/:splat 200

なお、フロントエンドはSPAで動いているため、NuxtからNext.jsアプリへ画面を切り替える際は画面をリフレッシュする必要があります。一瞬画面が白くなる瞬間がありますが、そこはPdMと相談して許容してもらいました。

Nuxt→Next.jsへの切り替え設定

既存のNuxt画面からNext.jsの画面へ切り替える際は、ページコンポーネントのmeta情報に reactPage: true というフラグを付与し、それをmiddlewareで検知してNext.jsアプリへ切り替えるようにしました。

ページコンポーネント
@Component({
  meta: {
    reactPage: true
  }
})
export default class RedirectReactPage extends Vue {

Next.jsへのリライトは/r/さえ先頭についていれば良いため、location.hrefに/r/だけ付与してページリフレッシュします。

Reactページへリダイレクトするmiddleware
import { Middleware } from '@nuxt/types'
import { RouteMeta, Route } from 'vue-router'

function getMergedMeta(route: Route): RouteMeta {
  // 型が何故かあってなかったので配列にキャストする
  const metas = (route.meta || []) as RouteMeta[]
  return Object.assign({}, ...metas)
}

const middleware: Middleware = ({ route }) => {
  const mergedMeta = getMergedMeta(route)

  if (
    // Reactページへの遷移を止めるか
    process.env.PREVENT_REDIRECT_REACT_PAGE !== 'true' &&
    mergedMeta.reactPage
  ) {
    // リダイレクト処理中にページが開くと困るので、解決しないPromiseを返して待ってもらう
    return new Promise(() => {
      // Reactアプリページに遷移する
      location.href = `/r${route.path}`
    })
  }
}

export default middleware

なお、コード上にあるPREVENT_REDIRECT_REACT_PAGEフラグですが、これをつけておくことで切り替える前のNuxtページを確認することができるようにしました。

Reactページへリダイレクトする確認スナックバー

Next.js→Nuxtへの切り替え設定

今度は逆にNext.jsからNuxtアプリに戻る場合についてです。目的のURLへページリフレッシュしながら遷移すれば良いので基本的にはaタグにhrefを設定するだけで良いのですが、トップページなどNext.jsアプリ単体で動かしている場合はNext.jsのrouterで遷移したいケースがあります。その時を考慮して、Nuxtアプリを動かしているドメイン上かを判断した上でリンク設定をします。なお、baseUrlで設定されている/r/はルーティング設定上は現れないので、Next.js、Nuxtどちらの遷移についてもhrefは変わらずに設定できます。

Nuxtアプリへ遷移するリンクコンポーネント
import { Link as MuiLink } from '@mui/material'
import { FC, ReactNode } from 'react'

import { RouterLink } from './RouterLink'

export type NuxtPageLinkProps = {
  href: string
  children: ReactNode
}

/**
 * Nuxt2アプリへ遷移するためのリンク。
 * プロキシ設定されておらず遷移できないときはNext.js内でルーティングすることを考慮
 */
export const NuxtPageLink: FC<NuxtPageLinkProps> = (props) => {
  const IS_NUXT2_APP_ORIGIN =
    window.location.origin === process.env.NUXT2_APP_URL
  if (IS_NUXT2_APP_ORIGIN) {
    // 単純に画面リフレッシュする単純なaタグ
    return <MuiLink href={props.href}>{props.children}</MuiLink>
  }
  // SPAで画面遷移されるrouter link
  return <RouterLink href={props.href}>{props.children}</RouterLink>
}

リプレイス準備に向けた施策

以上が画面ごとにリプレイスするための仕組みでした。あとは画面ごとにリプレイスしていくだけですが、開発メンバーがReactに慣れていない人もいるため、以下のような取り組みをしてReact力も高めております。

React勉強会

Vue.jsにはない機能を重点的に行い、Reactについての理解を深めました。
内容については以下の記事で紹介されておりますので、興味がある方はこちらもご覧ください。

https://zenn.dev/castingone_dev/articles/cc3ca4f2eed849

Reactリプレイスモブプロ会

実際に画面をリプレイスする場合どういうふうになるのかをみんなで見ながらリプレイスする時間を設けています。

おわりに

以上がReactへ段階的に移行する方針でした。切り戻しも容易で通常の開発を阻害させずにリプレイスも進められて、我ながら良い仕組みができたかなと思っております。後は計画通りにリプレイスが進められたらなと思っています。

弊社ではいっしょに働いてくれるエンジニアを募集中です。社員でもフリーランスでも、フルタイムでも短時間の副業でも大歓迎なので、気軽にご連絡ください!

https://www.wantedly.com/projects/1130967

https://www.wantedly.com/projects/768663

Discussion