🤮

react-native-webviewでページ遷移に詰まった話

2023/12/19に公開

この記事について

この記事では、react-native-webview を使用して既存のWebアプリをスマートフォンアプリに移行する際に直面した問題とその解決策について紹介します。特に、React NativeとNext.jsを組み合わせた開発における具体的な課題に焦点を当てています。

この記事の対象者

  • Next.jsを用いてWebサイトを構築し、React Nativeのスマートフォンアプリをreact-native-webviewを使って開発したい方。

開発環境

  • Web:
    • Next.js: 14.0.2(注: このプロジェクトはpages routerを使用しています)
  • React Native:
    • Expo: 48.0.21
    • React Native: 0.71.14
    • React Native WebView: 11.26.0

発生した問題

WebViewでページ遷移を行う際、onShouldStartLoadWithRequestがトリガーされず、読み込みのフィルタリングができませんでした。原因は、next/routernext/link を使用すると、ページがSPA(シングルページアプリケーション)のように動作し、ページ遷移のモニタリングができないことでした。

解決策

html 要素では a タグを、jsによるページ遷移では window.location.href を使用することで、onShouldStartLoadWithRequest がトリガーされることがわかりました。そのため、next/routernext/link のラッパーを作成し、WebViewからとWebでリンクの動作を切り替えるようにしました。

前提

WebViewからのアクセスを判定するために、クエリパラメータとして is_mobile=1 を設定しており、以下のコードでhooks化しています。

import { useRouter } from 'next/router';

export const useIsMobile = () => {
  const router = useRouter();
  return router.query.is_mobile === '1';
};

Linkラッパー

import NextLink from 'next/link';
import { FC } from 'react';
import { useIsMobile } from '@/hooks/useIsMobile';
import url from 'url';

type Props = Parameters<typeof NextLink>[0];

export const Link: FC<Props> = ({ children, href, ...props }) => {
  const isMobile = useIsMobile();

  if (isMobile) {
    return (
      <a href={url.format(href)} {...props}>
        {children}
      </a>
    )
  } else {
    return (
      <NextLink href={href} {...props}>
        {children}
      </NextLink>
    );
  }
};

useRouterラッパー

import { useCallback, useMemo } from 'react';
import { useRouter as useNextRouter, NextRouter } from 'next/router';
import url from 'url';

type PushParams = Parameters<NextRouter['push']>;
type ReplaceParams = Parameters<NextRouter['replace']>;

export const useRouter = () => {
  const router = useNextRouter();
  const isMobile = useMobile();

  const push = useCallback(
    (href: PushParams[0], as?: PushParams[1]) => {
      if (isMobile) {
        window.location.href = url.format(href);
        return new Promise((resolve) => {
          resolve(true);
        });
      } else {
        return router.push(href, as);
      }
    },
    [router],
  );

  const returnRouter = useMemo(() => ({ push }), [push]);

  return returnRouter;
};

※ スプレッド構文を使用して return { ...router, push } と書くと、以下のエラーが出るため避けました

エクスポートされた変数 'useRouter' が外部モジュール "/Users/hotta6163/develop/raysee/node_modules/next/dist/shared/lib/router/router" の名前 'NextHistoryState' を持っているか使用していますが、名前を指定することはできません。

うまくいかなかった試行錯誤

next/link の prefetch=false を設定し、prefetchを行わないようにすることでSPAのような動作を回避できると考えましたが、実際にはうまくいきませんでした。

最後に

自分の調べた中で、Next.jsの設定でSPAの動作を回避する方法はなかったが、実際にあればそれを使いたかった。
また、現状Webの領域はSPAが主流になっているため、今後ReactNativeWebviewの方で何かしらの対策が取れれて欲しい。

Discussion