Expo(React Native) + Next.jsでアプリとWebでコードをできるだけ共通化して開発してみる【Tamagui/Solito】

ExpoはWebに対応しているが、SPAかSSGでしかリリースできない。
SEOのためにSSRやISRしたいので、WebはNext.jsで開発する。
できるだけWebとNativeでコードを共通化して、工数を減らしたい。
solitoとtamaguiの組み合わせで、コードをできるだけ共通化して開発してみる。

tamaguiのnpm createでFree Expo + Next テンプレートを使う。
$ npm create tamagui@latest
Creating tamagui app...
✔ Project name: test
? Pick a template: › - Use arrow-keys. Return to submit.
❯ Free - Expo + Next in a production ready monorepo
作成されたフォルダでyarn install
$ cd ./test
$ yarn
下記のコマンドでそれぞれの開発環境が立ち上がる。
// Next.js
$ yarn web
// Expo
$ yarn native
Web | Native |
---|---|
![]() |
![]() |

作られたフォルダ構成は下記の様な形。
expoとnextに関してはいつもどおりの中身。
packagesに共通のものを入れていき、appsそれぞれで使っていく形っぽい。
..
├── apps # アプリケーション関連のコード
│ ├── expo # Expoアプリケーション(モバイル版)
│ └── next # Next.jsアプリケーション(Web版)
├── packages # 共通のライブラリやコンポーネント
│ ├── app # アプリケーション関連の共有ライブラリ
│ ├── config # 設定ファイルや設定関連のコード
│ └── ui # 共通のUIコンポーネントやスタイル
それぞれのホームスクリーンのtsxを見てみると、HomeScreenのComponentを読み込んでいるだけ。
import { HomeScreen } from 'app/features/home/screen'
import { Stack } from 'expo-router'
export default function Screen() {
return (
<>
<Stack.Screen
options={{
title: 'Home',
}}
/>
<HomeScreen />
</>
)
}
'use client'
import { HomeScreen } from 'app/features/home/screen'
export default HomeScreen
全体的に、TamaguiでScreenを表示しているが、リンク部分だけはsolitoを使っています。
solitoのuseLinkHookを使って、Next.js/Expo両方に対応したリンクのPropsを作成し渡すようにしています。
import { useLink } from 'solito/navigation'
export function HomeScreen({ pagesMode = false }: { pagesMode?: boolean }) {
const linkTarget = pagesMode ? '/pages-example-user' : '/user'
const linkProps = useLink({
href: `${linkTarget}/nate`,
})
return (
<YStack f={1} >
<Button {...linkProps}>Link to user</Button>
</YStack>
)
}

Appディレクトリに共通Componentなどを入れていくようになっている。
app/
├── features/
├── provider/
└── navigation/
featuresはScreenではなく、機能単位で分割されたComponent.
providerはラップする必要のある処理を置くフォルダ。

ファイル単位でWebとNativeを分けたい場合は、 index.(web/native).tsx
と拡張子の前にweb/nativeを入れておけばそれぞれ読み込んでくれる。

デザイン的なものは下記のスクラップに

cloudflare worker に deployする際
中身はNext.jsなので、apps/next
配下でopenntext.jsを使うことでcloudflare worker上にデプロイできた。
下記のドキュメントでopennext.js周りの内容が古いので、このままでは動かない。
opennext.js本家のドキュメントを参考にする。
main = ".open-next/worker.js"
name = "my-app"
compatibility_date = "2024-09-23"
compatibility_flags = ["nodejs_compat"]
# The binding name must be "ASSETS" when the cache is enabled
assets = { directory = ".open-next/assets", binding = "ASSETS" }
"build:worker": "opennextjs-cloudflare",
"dev:worker": "wrangler dev --port 8771",
"preview:worker": "npm run build:worker && npm run dev:worker",
"deploy:worker": "npm run build:worker && wrangler deploy"
Issue出したら修正してもらえたので、そのうち更新されるはず。
404 Pageでエラー
Cloudflare Worker上でデプロイした本番環境にexample.com/aaaaa
など、404になるURLへアクセスするとInternal Error が表示される。
opennext.jsでは、pagesに対応していないのが原因。
exampleとして入っているpagesディレクトリをすべて削除し、デプロイし直すことで404pageが無事表示された。