Open7

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

t0m0120t0m0120

ExpoはWebに対応しているが、SPAかSSGでしかリリースできない。
SEOのためにSSRやISRしたいので、WebはNext.jsで開発する。

できるだけWebとNativeでコードを共通化して、工数を減らしたい。
solitoとtamaguiの組み合わせで、コードをできるだけ共通化して開発してみる。
https://solito.dev/
https://tamagui.dev/

t0m0120t0m0120

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
t0m0120t0m0120

作られたフォルダ構成は下記の様な形。
expoとnextに関してはいつもどおりの中身。
packagesに共通のものを入れていき、appsそれぞれで使っていく形っぽい。

..
├── apps                  # アプリケーション関連のコード
│   ├── expo              # Expoアプリケーション(モバイル版)
│   └── next              # Next.jsアプリケーション(Web版)
├── packages              # 共通のライブラリやコンポーネント
│   ├── app               # アプリケーション関連の共有ライブラリ
│   ├── config            # 設定ファイルや設定関連のコード
│   └── ui                # 共通のUIコンポーネントやスタイル

それぞれのホームスクリーンのtsxを見てみると、HomeScreenのComponentを読み込んでいるだけ。

expo/app/index.tsx
import { HomeScreen } from 'app/features/home/screen'
import { Stack } from 'expo-router'

export default function Screen() {
  return (
    <>
      <Stack.Screen
        options={{
          title: 'Home',
        }}
      />
      <HomeScreen />
    </>
  )
}
next/app/page.tsx
'use client'
import { HomeScreen } from 'app/features/home/screen'
export default HomeScreen

全体的に、TamaguiでScreenを表示しているが、リンク部分だけはsolitoを使っています。
solitoのuseLinkHookを使って、Next.js/Expo両方に対応したリンクのPropsを作成し渡すようにしています。

app/features/home/screen.tsx
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>
  )
}
t0m0120t0m0120

Appディレクトリに共通Componentなどを入れていくようになっている。

app/
├── features/
├── provider/
└──  navigation/

featuresはScreenではなく、機能単位で分割されたComponent.
providerはラップする必要のある処理を置くフォルダ。

t0m0120t0m0120

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

t0m0120t0m0120

cloudflare worker に deployする際

中身はNext.jsなので、apps/next配下でopenntext.jsを使うことでcloudflare worker上にデプロイできた。

下記のドキュメントでopennext.js周りの内容が古いので、このままでは動かない。
https://developers.cloudflare.com/workers/frameworks/framework-guides/nextjs/

opennext.js本家のドキュメントを参考にする。
https://opennext.js.org/cloudflare

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" }
package.json
"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出したら修正してもらえたので、そのうち更新されるはず。
https://github.com/cloudflare/cloudflare-docs/issues/18936

404 Pageでエラー

Cloudflare Worker上でデプロイした本番環境にexample.com/aaaaaなど、404になるURLへアクセスするとInternal Error が表示される。

opennext.jsでは、pagesに対応していないのが原因。
exampleとして入っているpagesディレクトリをすべて削除し、デプロイし直すことで404pageが無事表示された。