├── .github ├── pull_request_template.md └── workflows │ ├── main.yml │ ├── production-android.yml │ ├── production.yml │ └── pull_request.yml ├── .gitignore ├── App.tsx ├── LICENSE.md ├── README.md ├── app.json ├── babel.config.js ├── components ├── VideoPlayer.tsx ├── page │ ├── Page.tsx │ └── PageHead.tsx └── webElements.tsx ├── config ├── config.ts ├── globalStyles.ts └── locales │ └── en.json ├── docs ├── demo.jpg └── github_preview.jpg ├── eas.json ├── hooks ├── useAnalytics.tsx ├── useI18N.ts └── useUniqueDeviceID.ts ├── lib ├── handleRestRequest.ts └── makeRestRequest.ts ├── metro.config.js ├── next-env.d.ts ├── next.config.js ├── package.json ├── pages ├── _app.tsx ├── _document.tsx ├── api │ └── test.ts ├── index.tsx └── page2.tsx ├── public ├── app-icon-adaptive.png ├── app-icon.png ├── app-splash.png ├── favicon.ico ├── favicon.png └── manifest.json ├── tsconfig.json ├── types └── global.d.ts ├── vercel.json └── yarn.lock /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | **Related ticket:** https://trello.com/c/... 2 | 3 | ### What this PR includes 4 | 5 | - [e.g. Added a new screen that does...] 6 | - [Mark PR as 🚧 DRAFT if you don’t want it to be merged] 7 | 8 | ### Checklist 9 | 10 | - [ ] I have compared design with Figma sketches 11 | - [ ] I have run `yarn fix` and solved any linting issues 12 | - [ ] There are no merge conflicts -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # Commit to `main` branch → Publish to Expo channel “staging” 2 | 3 | name: main → Expo staging 4 | 5 | on: 6 | push: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: 🏗 Setup repository 15 | uses: actions/checkout@v3 16 | 17 | - name: 🏗 Setup Node 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: 18.x 21 | cache: yarn 22 | 23 | - name: 🏗 Setup Expo 24 | uses: expo/expo-github-action@v8 25 | with: 26 | expo-version: latest 27 | token: ${{ secrets.EXPO_TOKEN }} 28 | 29 | - name: 📦 Install dependencies 30 | run: yarn install 31 | 32 | - name: 🚀 Publish app to Expo/EAS 33 | uses: expo/expo-github-action/preview@v8 34 | with: 35 | command: eas update --auto 36 | -------------------------------------------------------------------------------- /.github/workflows/production-android.yml: -------------------------------------------------------------------------------- 1 | # Commit to `production-android` branch → build with EAS 2 | 3 | name: production-android → build Android app 4 | 5 | on: 6 | push: 7 | branches: 8 | - production-android 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: 🏗 Setup repository 15 | uses: actions/checkout@v3 16 | 17 | - name: 🏗 Setup Node 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: 18.x 21 | cache: yarn 22 | 23 | - name: 🏗 Setup Expo and EAS 24 | uses: expo/expo-github-action@v8 25 | with: 26 | expo-version: latest 27 | eas-version: latest 28 | token: ${{ secrets.EXPO_TOKEN }} 29 | 30 | - name: 📦 Install dependencies 31 | run: yarn install 32 | 33 | - name: 🚀 Publish app to Expo/EAS 34 | uses: expo/expo-github-action/preview@v8 35 | with: 36 | command: eas update --auto 37 | 38 | - name: 🛠️ Build app 39 | run: EAS_BUILD_AUTOCOMMIT=1 eas build --non-interactive --profile production --platform android 40 | 41 | - name: 🚚 Submit app to TestFlight 42 | run: eas submit --latest --platform android 43 | -------------------------------------------------------------------------------- /.github/workflows/production.yml: -------------------------------------------------------------------------------- 1 | # Commit to `production` branch → build with EAS 2 | 3 | name: production → build iOS app 4 | 5 | on: 6 | push: 7 | branches: 8 | - production 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: 🏗 Setup repository 15 | uses: actions/checkout@v3 16 | 17 | - name: 🏗 Setup Node 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: 18.x 21 | cache: yarn 22 | 23 | - name: 🏗 Setup Expo and EAS 24 | uses: expo/expo-github-action@v8 25 | with: 26 | expo-version: latest 27 | eas-version: latest 28 | token: ${{ secrets.EXPO_TOKEN }} 29 | 30 | - name: 📦 Install dependencies 31 | run: yarn install 32 | 33 | - name: 🚀 Publish app to Expo/EAS 34 | uses: expo/expo-github-action/preview@v8 35 | with: 36 | command: eas update --auto 37 | 38 | - name: 🛠️ Build app 39 | run: eas build --non-interactive --profile production --platform ios 40 | 41 | - name: 🚚 Submit app to TestFlight 42 | run: eas submit --latest --platform ios 43 | -------------------------------------------------------------------------------- /.github/workflows/pull_request.yml: -------------------------------------------------------------------------------- 1 | # Publish an update every time a pull request is opened 2 | 3 | name: preview 4 | on: pull_request 5 | 6 | jobs: 7 | update: 8 | name: EAS Update 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: read 12 | pull-requests: write 13 | steps: 14 | - name: Check for EXPO_TOKEN 15 | run: | 16 | if [ -z "${{ secrets.EXPO_TOKEN }}" ]; then 17 | echo "You must provide an EXPO_TOKEN secret linked to this project's Expo account in this repo's secrets. Learn more: https://docs.expo.dev/eas-update/github-actions" 18 | exit 1 19 | fi 20 | 21 | - name: Checkout repository 22 | uses: actions/checkout@v3 23 | 24 | - name: Setup Node 25 | uses: actions/setup-node@v3 26 | with: 27 | node-version: 18.x 28 | cache: yarn 29 | 30 | - name: Setup EAS 31 | uses: expo/expo-github-action@v8 32 | with: 33 | eas-version: latest 34 | token: ${{ secrets.EXPO_TOKEN }} 35 | 36 | - name: Install dependencies 37 | run: yarn install 38 | 39 | - name: Create preview 40 | uses: expo/expo-github-action/preview@v8 41 | with: 42 | command: eas update --auto 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | node_modules/**/* 3 | .expo/* 4 | npm-debug.* 5 | *.jks 6 | *.p8 7 | *.p12 8 | *.key 9 | *.mobileprovision 10 | *.orig.* 11 | web-build/ 12 | web-report/ 13 | .next/* 14 | 15 | # dependencies 16 | /node_modules 17 | /.pnp 18 | .pnp.js 19 | 20 | # testing 21 | /coverage 22 | 23 | # next.js 24 | /.next/ 25 | /out/ 26 | 27 | # production 28 | /build 29 | 30 | # misc 31 | .DS_Store 32 | *.pem 33 | 34 | # debug 35 | npm-debug.log* 36 | yarn-debug.log* 37 | yarn-error.log* 38 | 39 | # local env files 40 | .env.local 41 | .env.development.local 42 | .env.test.local 43 | .env.production.local 44 | 45 | # vercel 46 | .vercel 47 | -------------------------------------------------------------------------------- /App.tsx: -------------------------------------------------------------------------------- 1 | import { View } from 'react-native' 2 | import { ToastProvider } from 'react-native-toast-notifications' 3 | 4 | import { GLOBAL_STYLES } from './config/globalStyles' 5 | // import { AnalyticsContextProvider } from './hooks/useAnalytics' 6 | // import Page from './components/page/Page' 7 | import IndexPage from './pages/index' 8 | 9 | export default function App (): React.ReactElement { 10 | return ( 11 | 16 | 17 | 20 | 21 | 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020, Tom Söderlund 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 4 | 5 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 6 | 7 | Source: http://opensource.org/licenses/ISC 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # reactnative-nextjs-template 2 | 3 | > NOTE: Work in progress until version is 1.0.0+ 4 | 5 | **Build native apps (iOS/Android/Windows/macOS) and an SEO-optimized web app from the same React codebase, using React Native/Expo and Next.js** 6 | 7 | (Created with `npx create-next-app -e with-expo [PROJECTNAME]`) 8 | 9 | ![reactnative-nextjs-template demo on phone](docs/github_preview.jpg) 10 | 11 | _Note: this is my v5 boilerplate for React apps. See also my [Next.js + Firebase boilerplate](https://github.com/tomsoderlund/nextjs-pwa-firebase-boilerplate)._ 12 | 13 | ## Why is this awesome? 14 | 15 | This allows you to build _both_ a native app for [iOS/Android/Windows/macOS/etc](https://reactnative.dev/docs/out-of-tree-platforms), as well as an SEO-optimized web app _from same codebase_. 16 | 17 | ### Use cases 18 | 19 | 1. You want a **same user experience** in a native app and on the web. 20 | 2. You want a **shared codebase** between a native app and a separate website. 21 | 3. You want a **shared repository** for native app and its API, powered by Next.js and Vercel serverless functions. 22 | 23 | ## Todo list 24 | 25 | - [X] Server-side rendering (SSR) 26 | - [X] SEO: Semantic tags e.g. H1, H2, H3 27 | - [X] SEO: Page metadata support 28 | - [X] Header (from `react-native-elements`) 29 | - [X] Video player 30 | - [x] Built-in REST API (`GET /api/test`) 31 | - [ ] Navigation 32 | - [ ] Flexible CSS solution 33 | - [ ] Font support 34 | - [ ] SVG support 35 | 36 | ## Demo 37 | 38 | See [**reactnative-nextjs-template** running on Vercel here](https://reactnativenextjstemplate.vercel.app/). 39 | 40 | ![reactnative-nextjs-template demo on phone](docs/demo.jpg) 41 | 42 | ## Support this project 43 | 44 | Did you or your company find `reactnative-nextjs-template` useful? Please consider giving a small donation, it helps me spend more time on open-source projects: 45 | 46 | [![Support Tom on Ko-Fi.com](https://www.tomsoderlund.com/ko-fi_tomsoderlund_50.png)](https://ko-fi.com/tomsoderlund) 47 | 48 | ## Deploying 49 | 50 | Setup and deploy your own project using this template with [Vercel](https://vercel.com): 51 | 52 | [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/import/git?s=https%3A%2F%2Fgithub.com%2Ftomsoderlund%2Freactnative-nextjs-template&env=NEXT_PUBLIC_FIREBASE_API_KEY&envDescription=Enter%20your%20public%20Firebase%20API%20Key&envLink=https://github.com/tomsoderlund/reactnative-nextjs-template#deploying-with-vercel) 53 | 54 | ## How to use 55 | 56 | > Note: If you set up your project using the Deploy button above, you only need to clone your own repo instead of this repository. 57 | 58 | Clone this repository: 59 | 60 | git clone https://github.com/tomsoderlund/reactnative-nextjs-template.git [MY_APP] 61 | 62 | cd [MY_APP] 63 | 64 | Remove the `.git` folder since you want to create a new repository 65 | 66 | rm -rf .git 67 | 68 | Install dependencies: 69 | 70 | yarn # or npm install 71 | 72 | Start it in Next.js/web mode by: 73 | 74 | yarn dev:next 75 | 76 | …then navigate to `http://localhost:3005/` 77 | 78 | Start Expo for native apps: 79 | 80 | yarn dev 81 | 82 | In production: 83 | 84 | yarn build 85 | yarn start 86 | 87 | ## Modifying the app to your needs 88 | 89 | ### Change app name 90 | 91 | Do search/replace for `reactnativenextjstemplate` to something else. Avoid hyphens/underscores because of iOS/Android bundle names. 92 | 93 | Change name in `public/manifest.json` 94 | 95 | ### Change port number 96 | 97 | Do search/replace for “3005” to something else. 98 | 99 | ## Read more 100 | 101 | - Expo’s guide on Next.js: https://docs.expo.dev/guides/using-nextjs/ 102 | - [@expo/next-adapter](https://github.com/expo/expo-cli/tree/main/packages/next-adapter) 103 | - Code example: https://github.com/expo/examples/tree/master/with-nextjs 104 | - Solito framework: https://solito.dev/ (awesome but uses it’s own components/syntax) 105 | - create-universal-app (CUA): https://github.com/chen-rn/CUA 106 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "version": "0.1.0", 4 | "runtimeVersion": "0.1.0", 5 | "slug": "reactnativenextjstemplate", 6 | "name": "React Native + Next.js", 7 | "orientation": "portrait", 8 | "icon": "./public/app-icon.png", 9 | "userInterfaceStyle": "light", 10 | "splash": { 11 | "image": "./public/app-splash.png", 12 | "resizeMode": "contain", 13 | "backgroundColor": "#ffffff" 14 | }, 15 | "assetBundlePatterns": [ 16 | "**/*" 17 | ], 18 | "ios": { 19 | "bundleIdentifier": "com.tomorroworld.reactnativenextjstemplate", 20 | "supportsTablet": true, 21 | "config": { 22 | "usesNonExemptEncryption": false 23 | } 24 | }, 25 | "android": { 26 | "package": "com.tomorroworld.reactnativenextjstemplate", 27 | "versionCode": 1000001000, 28 | "permissions": [], 29 | "adaptiveIcon": { 30 | "foregroundImage": "./public/app-icon-adaptive.png", 31 | "backgroundColor": "#ffffff" 32 | } 33 | }, 34 | "web": { 35 | "favicon": "./public/favicon.png" 36 | }, 37 | "plugins": [ 38 | [ 39 | "expo-build-properties", 40 | { 41 | "android": { 42 | "enableProguardInReleaseBuilds": true 43 | } 44 | } 45 | ] 46 | ], 47 | "extra": { 48 | "eas": { 49 | "projectId": "3073dfd4-4c5c-46d9-bfcb-169f6e8b2c37" 50 | } 51 | }, 52 | "updates": { 53 | "url": "https://u.expo.dev/3073dfd4-4c5c-46d9-bfcb-169f6e8b2c37" 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true) 3 | return { 4 | presets: ['babel-preset-expo'] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /components/VideoPlayer.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState } from 'react' 2 | import { StyleSheet, View, Button } from 'react-native' 3 | import { Video } from 'expo-av' 4 | 5 | interface VideoPlayerProps { 6 | videoUrl: string 7 | } 8 | 9 | const VideoPlayer = ({ videoUrl }: VideoPlayerProps): React.ReactElement => { 10 | const video = useRef(null) 11 | const [status, setStatus] = useState({}) 12 | return ( 13 | <> 14 |