- Global state management with React Context See [PR #1]
- Global state management with Recoil See [PR #2]
we don't know which one brings the best developer experience, with the must pragmatic approach (giving us flexibility in different code bases)
We don't have a default when starting projects, leaving it up to the moment, and risking the stack diverging
Official Docs -- React Context
[PR #1] React Context experiment -- Vercel Preview
- β Β Official React API, not going anywhere in the future
- β Β No extra concepts to learn if already familiar with React
- β Β Actively maintained as part of React by Meta
- β Β Often used under the hood by other state management libraries (optimised by them though)
- β Β Automatically picked up out of the box by React Devtools
- π€Β Still needs to be paired with other official React hooks to turn it into a state manager
- π€Β Bare bones / βbuild your own solutionβ still leaves room for different interpretations
- π€Β Can cause issues during SSR where server & browser state get out of sync
- π€Β Context API can be slow at times, often requires memoization in large apps [Docs]
- πΒ Performance issues might get solved with React's new
useSelectedContext
hook
Official Docs -- Recoiljs.org
[PR #2] React Context experiment -- Vercel Preview
- β Straightforward DX, feels like using regular React state
- β Separating read-only & read-write APIs
- β Β Minimal overhead on top of normal hooks
- β Β Isolates global state into atoms to avoid unnecessary / unrelated re-renders
- β Β Promoted / maintained by (devs working for) Meta
- β Β 269,471 weekly downloads [NPM]
- β Β Actively maintained π (bi-)weekly updates & releases [GitHub]
- π€Β Not an official API though, might fall out of flavour / tank in popularity eventually
This project was bootstrapped with Aetherspace, the Evergreen repo setup for all your full-stack cross platform app development needs {...π} Enabling the project to be built for Web, iOS, Android, PWA, Static, SSR, REST and GraphQL all at once π
Install packages: yarn install
Run with docs: yarn dev
Run on web & mobile: yarn dev-mobile
β Aetherspace, GREEN stack & Template Benefits? π
π - What is the GREEN stack?
π - What is Aetherspace?
π€ - Why start with a turbo/monorepo?
π - File structure and installing new packages.
πΎ - Benefits and next steps.
π€·ββοΈ - When not to use the GREEN stack.
π - Relevant Docs.
In short GREEN stands for these 5 core technologies:
- GraphQL for typed and self documenting APIs
- React-Native and React-Native-Web for write-once UI
- Evergreen components that run anywhere (as well as Electron)
- Expo for easy web + mobile dev, deploys and testing
- Next.js for SEO, Static Exports, API, SSR & Web-Vitals
The core idea this tech stack enables you to achieve boils down to writing your app code or features once with Javascript and React, yet make it available on any platform or device without double implementations or the need for different development teams.
Think of it as Unity for React Apps. Just like Unity aims to make cross console game development a lot easier for (indie) game devs, the GREEN stack aims to do the same for cross-platform app development.
Aetherspace is an opinionated framework I've made that fills in the gaps of working and building with the GREEN stack:
- How should I handle responsive design?
- How do I avoid SSR layout shift when react-native styling does not support media queries or classnames?
- How can I expose / read public env vars across multiple platforms?
- Wait, how do I take advantage of next/image on the web when that's not available in React-Native?
- What's the best way to style and animate my UI elements for both web and mobile?
Just to name a few.
While the stack itself is very powerful, figuring out how to get set up and do certain things in a write-once way can be frustrating and time consuming. To save you time figuring it all out on your own, Aetherspace contains a bunch of packages, utils and best-practices to set you up for a free and easy ride to cross-platform success.
Aetherspace is also fully optional. Usage of the UI primitives, React hooks and JS utils provided by
packages/aetherspace
is recommended but not required.
Provided you throw out the examples and edit some helper scripts in the
package.json
files, you could even delete the package entirely and still be left with a great GREEN stack starter.
More on Aetherspace in the πΎ Benefits and Next steps section or AETHERSPACE.md
and CODEGEN.md
.
One very annoying thing about figuring stuff out on your own is when packages you're using require custom configuration for webpack, babel or otherwise. It often happens that updating e.g. a single babel.config.js
used for both React-Native and Next.js will fix usage on either, but then break the other.
Using a monorepo with different entry points for Next.js and Expo allows us to keep configs more seperate, and therefore allow more confident updating of packages and configs without accidentally breaking other platforms.
In this starter template, we've opted to use turborepo with yarn workspaces. We'll list some basics in the next section, but for a deeper understanding please refer to their documentation for more info.
This starter monorepo has two types of workspaces:
/apps/*
for all expo & next.js versions of your apps/packages/*
for all shared dependencies / library code used in multiple apps
βββ apps/
β βββ {app-name}/ π Where all cross-platform code for {app-name} lives
β βββ components/ β‘οΈ Molecules / Atoms / Common UI used in 'screens/'
β βββ graphql/ β‘οΈ Shared code for the GraphQL API client (optional)
β βββ resolvers/ β‘οΈ Shared resolvers used in both REST or GraphQL API
β βββ screens/ β‘οΈ Page templates used in App.tsx and next.js's 'pages/' directory
β βββ package.json β‘οΈ config required by yarn-workspaces, no dependencies
β
β βββ {app-name}-expo/ π Where all Expo & mobile specific config for {app-name} lives
β βββ app.json β‘οΈ Expo app config (e.g. landscape / tablet support)
β βββ App.tsx β‘οΈ Mobile Entrypoint & Navigation Setup (using '{app-name}/screens/')
β βββ babel.config.js β‘οΈ Babel transpilation config for Expo
β βββ index.js β‘οΈ Mobile entrypoint loader for App.tsx
β βββ metro.config.js β‘οΈ Metro bundler config for react-native
β βββ package.json β‘οΈ yarn-workspace config, lists expo & non-next.js dependencies
β βββ tsconfig.json β‘οΈ Typescript config for Expo
β βββ webpack.config.js β‘οΈ Enables PWA browser testing with Expo (no SSR)
β
β βββ {app-name}-next/ π Where all Next.js, Server & API config for {app-name} lives
β βββ public/ β‘οΈ favicon, app icons & other static assets (e.g. images & fonts)
β βββ src/
β βββ pages/ β‘οΈ directory based routes (using '{app-name}/screens/')
β βββ api/ β‘οΈ directory based api routes (using '{app-name}/resolvers/')
β βββ graphql.ts β‘οΈ GraphQL client from '{app-name}/graphql/'
β βββ _app.tsx β‘οΈ App Layout Wrapper (e.g. headers / footers / navigation)
β βββ _document.tsx β‘οΈ HTML wrapper for head, body & meta tags (+ SSR styles)
β βββ index.tsx β‘οΈ Homepage (e.g. using '{app-name}/screens/HomeScreen.tsx')
β βββ babel.config.js β‘οΈ Babel transpilation config for Next.js
β βββ next.config.js β‘οΈ Next.js config, modules to transpile & plugins to support
β βββ package.json β‘οΈ yarn-workspaces config, lists ONLY next.js dependencies
β βββ tsconfig.json β‘οΈ Typescript config for Next.js
β
βββ packages/
β βββ @aetherspace/ β‘οΈ Primitives, utils & helpers for working with the GREEN stack
β βββ @config/ β‘οΈ list of ts & other configs to use / extend from in next or expo apps
β βββ @scripts/ β‘οΈ scripts that help streamline things like codegen & managing assets
β βββ {comp-lib}/ π Code shared across apps, ideally same structure as 'apps/{app-name}'
β βββ package.json β‘οΈ yarn-workspace config, doesn't need deps unless published
β
βββ node_modules/ β‘οΈ Contains all modules for this monorepo
βββ package.json β‘οΈ Root yarn-workspaces configuration + helper scripts, no deps
π‘ `{app-name}` & `{comp-lib}` are just placeholders and you **can** have multiple of these
For every app you're building in this monorepo, you'll need a few folders:
/apps/app
- Where most of your app's UI, logic and Screens will live. Shouldn't have any dependencies./apps/app-next
- Entry for web where only next.js related config/setup for an app should live. Should list only next.js related dependencies & polyfills./apps/app-expo
- Entry for mobile where only expo related config/setup for an app should live. Should list all react(-native) and non next.js related dependencies.
In each of these folders own package.json
file, a name
property should be specified to identify that workspace. This name can then be referenced during installs via e.g.
yarn workspace app-next add next-images
yarn workspace app-expo add moti
It's also advised to see app workspaces as fully seperate from other apps:
For example,
/apps/app
should not import or reference anything from/apps/some-other-app
. If you do need to embed a certain screen or component from one app in another, it's best to extract it to its own shared library workspace instead (toggle below for info π)
π‘ `/packages/*` workspaces for e.g. component libraries
Packages aim to provide common building blocks or logic for both apps and other packages. They do not need to differentiate between entry points with /packages/...-next
and /packages/...-expo
.
Like /apps/
workspaces, they do also require their own package.json
and name
, and installing dependencies can work exactly the same:
yarn workspace component-library add -D @types/react
However, unless you will be publishing the package to NPM, it may be best to just install any dependencies in the consuming apps' /apps/{app-name}-next
or /apps/{app-name}-expo
workspace instead.
A good example of a library package usable by multiple app workspaces in this monorepo is the
/packages/aetherspace
workspace. It contains UI primitives likeAetherView
,AetherImage
&AetherLink
that are small wrappers for & recommended over react-native's ownView
,Text
&Image
components.
If you've read the sections above, It's likely the ease of use, time saving capabilities and scalability of this stack & template are clear.
The starter repo comes with some opinionated extra packages and abilities.
Here's a list of what you can start doing out of the box:
- Link pages and screens cross platform with
expo-next-react-navigation
or<AetherLink>
- Use tailwind to style UI responsively on web / mobile with
<AetherView tw="sm:px-2">
/tailwind-rn
- Animate UI elements with
<AetherView.Animated>
/react-native-reanimated
/moti
- Add illustrations or icons with
react-native-svg
- Bring the power of GraphQL to JSON or REST apis with
aetherResolver()
and Schemas. - Add auth with AuthSession (Expo Examples)
- Document your components and APIs with Storybook.
- Deploy to vercel with
yarn deploy
orvercel --prod --no-clipboard
(view live) - Deploy to netlify via this guide (view live)
If you'd like to continue learning about Aetherspace and the GREEN stack, there are more detailed guides, tips and best-practices in:
AETHERSPACE.md
,CODEGEN.md
,NAVIGATION.md
&API.md
(Aetherspace & Codegen)STYLING.md
,ANIMATING.md
&DOCUMENTING.md
(GREEN stack How-tos)
Whether you're a startup or established company, having both web and mobile apps is a great competitive advantage. There are many stories of market leaders suddenly being overtaken because the competition were able to move faster or had more devices their solution was available on for their customers.
This stack makes it near effortless to enable extra platforms. It helps keep teams small and enables them to move fast when building new pages or features for phones, tablets and/or the web.
More deliverables for less time invested in turn means flexibility in one or more of these areas:
- ... negotiation room about budget or deadlines (in case of client work)
- ... π° to be distributed among the entire team
- ... π available for experimentation
- ... budget available to market the product
Show full ππ to π°π°π° Comparison
Let's talk Return on Investment:
π = time required = devs / teams / resources invested
π° = deliverable sale value = costs to build + profit margin
ROI = π -> sold for -> π°
Web only project ROI = ππ -> π°π°
- π Web Front-End π°
- π General Back-End (REST / GraphQL + Templates / SSR) π°
Native iOS + Android project ROI = πππ -> π°π°π°
- π iOS App with Swift π°
- π Android app with Java π°
- π API Back-End (REST / GraphQL) π°
React-Native Mobile App ROI = ππ -> π°π° to π°π°π°
- π iOS + Android App with RN π°(π°)
- π API Back-End (REST / GraphQL) π°
Expo Mobile + PWA ROI = ππ ->π°π° to π°π°π°π°
- π iOS + Android + PWA with Expo & RN (Web without SSR) π°(π°π°)
- π API Back-End (REST / GraphQL) π°
Now, things get really interesting when you try to compare full cross-platform apps
Full Cross Platform with Separate Dev Teams ROI = πππππππΒ ->Β π°π°π°π°π°π°π°
- π Web Front-End π°
- π iOS App with Swift π°
- π Android app with Java π°
- π Windows App Dev Team π°
- π MacOS App Dev Team π°
- π Linux App Dev Team π°
- π API Back-End (REST / GraphQL) π°
Full Cross Platform with GREEN stack ROI = ππΒ -> π°π° to π°π°π°π°π°π°π°
- π Web (PWA & SSR & Web Vitals) + iOS + Android + Windows + MacOS + Linux π°(π°π°π°π°π°)
- π Back-End (REST + GraphQL + SSR + Static Exports + ISSG + universal JS utils thanks to Next.js) π°
The GREEN stack is unlikely to be the best fit when your project...
- ... will always be web only π Use
next.js
- ... will always be mobile only π Use
Expo
- ... will always be desktop only π Use
Electron
+React
/Vue
/Svelte
- ... is very Bluetooth / AR / VR / XR heavy π Go native with
Swift
/Java
- ... is a console game π Use
Unity
instead - ... is not using React π Use
Svelte
/Vue
+Ionic
- ... has no real need for Server Rendering, SEO or Web-Vitals π Use
Expo
(+ Web Support) - ... is using React, but the project is too far along and has no budget, time or people to refactor π€·ββοΈ
If your project has required dependencies / SDKs / libraries that are either not available in JS, are not extractable to API calls or cannot function cross-platform, this may also not be a good solution for your use-case*.
π * However, for JS libs, you could always try adding cross platform support yourself with `patch-package`