├── .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 | 
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 | 
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 | [](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 | [](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 |