This project was bootstrapped with Aetherspace, the Evergreen repo setup for all your full-product, universal app development needs {...π} Enabling the project to be built for Web, iOS, Android, PWA, Static, SSR, API routes and GraphQL at write-once efficiency, while also documenting your code automatically with Zod & Storybook.
β Aetherspace, GREEN stack & template benefits? π
π¦ Anouncement post
β‘οΈ Quickstart example
π Core Concepts
π - 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 & React-Native for write-once UI
- Evergreen components (extendable, themable, with docs & types)
- Expo for easy mobile development, deployment and testing
- Next.js for web, SEO, Static & Server rendering, API & Web-Vitals
The core idea is writing your app code or features just once with Typescript and React-Native, 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, Aetherspace's setup for the GREEN stack aims to do the same for cross-platform app development.
- Cross-platform from the start
- Take what works, make it better
- Single sources of truth
- Write once, use anywhere
- Documentation drives adoption
Aetherspace is an opinionated framework that fills in the gaps of working and building with the GREEN stack:
- How should I handle responsive design?
- How do I avoid web layout shift when react-native styling does not support media queries or classnames?
- How can I expose / read public env vars across multiple platforms?
- How do I take advantage of optimisations like
next/image
on 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 quick and easy ride to cross-platform success.
One annoying thing about figuring this stack out on your own is when packages you're using require custom configs for babel, webpack or otherwise. With Expo and Next.js, it often happens that updating e.g. a single babel.config.js
used for both Expo 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 separate, 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 three types of workspaces:
/apps/*
for all expo & next.js versions of your apps (consumes'features'
π)/features/*
features of your app, grouped together by feature name (consumes'packages'
π)/packages/*
for all shared dependencies / library code used in multiple apps or features
βββ apps/
β βββ expo/ π Where all Expo & mobile specific config for {app-name} lives
β βββ app.json β‘οΈ Expo app config (e.g. App name, icon, landscape / tablet support)
β βββ app/ β‘οΈ File-based Routing & Navigation Setup for mobile (using 'app-core/screens/')
β βββ (generated)/ β‘οΈ File based routing generated from `routes/` in features or packages
β βββ _layout.tsx β‘οΈ Root layout for all app screens (e.g. tab bar, drawer, etc.)
β βββ index.tsx β‘οΈ Home & starting screen for the app
β βββ 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 core expo & react-native dependencies
β βββ tsconfig.json β‘οΈ Typescript config for Expo
β βββ webpack.config.js β‘οΈ Enables PWA browser testing with Expo (no SSR)
β
β βββ next/ π Where all Next.js, Server & API config for {app-name} lives
β βββ public/ β‘οΈ favicon, app icons & other static assets (e.g. images & fonts)
β βββ app/ β‘οΈ File-based Routing & Navigation Setup for Web (using 'app-core/screens/')
β βββ (generated)/ β‘οΈ File based routing generated from `routes/` in features or packages
β βββ head.tsx β‘οΈ HTML wrapper for head & meta tags (+ SSR styles)
β βββ layout.tsx β‘οΈ Root layout for all web pages (e.g. headers / footers / nav)
β βββ page.tsx β‘οΈ Web Homepage (e.g. using 'app-core/screens/HomeScreen.tsx')
β βββ api/ β‘οΈ directory based api routes (using 'app-core/resolvers/')
β βββ graphql.ts β‘οΈ GraphQL client from 'app-core/graphql/'
β βββ 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 core next.js dependencies
β βββ tsconfig.json β‘οΈ Typescript config for Next.js
|
|ββ features/
β βββ app-core/ π Where all core 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 in API routes or GraphQL API
β βββ screens/ β‘οΈ Page templates used in App.tsx and next.js's 'app/' directory
β βββ routes/ β‘οΈ Write-once routing for both web & mobile (see 'app/(generated)/' in expo & next)
β βββ package.json β‘οΈ config required by yarn-workspaces, lists dependencies that don't fit anywhere else
β βββ {app-feature}/ π Code shared across apps, ideally same structure as 'features/app-core'
β βββ package.json β‘οΈ config required by yarn-workspaces, list dependencies specific to this feature
β
βββ packages/
β βββ @aetherspace/ β‘οΈ Primitives, utils & helpers for working with the GREEN stack
β βββ schemas/ β‘οΈ A set of Zod powered schema utils for building single sources of truth
β βββ @config/ β‘οΈ list of ts & other configs to use / extend from in next or expo apps
β βββ {comp-lib}/ π Code shared across apps, ideally same structure as 'features/app-core'
β βββ package.json β‘οΈ yarn-workspace config, list dependencies specific to this package
β
βββ node_modules/ β‘οΈ Contains all modules for the entire monorepo
βββ package.json β‘οΈ Root yarn-workspaces configuration + helper scripts, core developer only dependencies
βββ turbo.json β‘οΈ Monorepo config, manages dependencies in build scripts + caching of tasks
π‘
{app-feature}
,{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/next
- Entry for web where only next.js related config/setup for an app should live. Should list only core next.js related deps & polyfills./apps/expo
- Entry for mobile where only expo related config/setup for an app should live. Should list only core react-native and expo related deps./features/{app}-core
- Where most of your core app specific UI, logic and screens will live. Should list app dependencies not listed elsewhere.
In each of these folders there's a package.json
file, where a name
property should be specified to identify that workspace. This name can then be referenced during installs via e.g.
yarn workspace app add {package-name}
yarn expo-cli install {package-name}
Which will run yarn workspace expo-app expo-cli install {package-name}
under the hood.
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 the
<Link>
component fromaetherspace/navigation
- Use tailwind to style UI responsively on web / mobile with
<AetherView tw="sm:px-2">
/tailwind-rn
- Add illustrations or icons with
react-native-svg
- Bring the power of GraphQL to JSON or REST apis with
aetherResolver()
and schemas as single sources of truth. - Document your components and APIs with Storybook.
- Deploy to vercel by importing your repo in their UI (view live example)
- Deploy to netlify via this guide (view live)
Possible next steps:
- Animate UI elements with
react-native-reanimated
ormoti
- Add auth with AuthSession (Expo Examples)
For users:
- Solutions built for how they prefer to use software, whether that's on a phone, tablet or desktop.
- Can share any page or feature with a link, which will open in the correct app or browser.
- Full feature parity across all platforms.
For developers:
- Write-once UI, logic, routing, data fetching & resolvers
- Easily onboard new devs to the project with auto-generated Storybook docs
- Save time & reduce risk by defining data structure once, instead of 4 times for types, graphql, docs & validation
For businesses:
- Speed and flexibility to build/update features and pages for any platform.
- Reach more users by being available on more devices.
- Free organic leads from web SEO, which you can easily guide to mobile where higher conversions happen.
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) π°
Key takeaway: Always build or upsell more platforms / devices the app could run on
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 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`
- Expo, React Native, Expo-Router docs
- Yarn Workspaces, Turborepo docs
- Next.js, app-directory, React-Native-Web docs
- Apollo Server docs
See the Core Concepts section
Deeplinks on mobile come out of the box with Expo-Router, which Aetherspace's Universal Routing uses under the hood.
See the README on Automation or the Anouncement post
See the Icon Management README
Check out the License and its FAQ section.
Generate a new repo from the Aetherspace template and (optionally) include all branches.
Set this repo as the upstream if you want to include the git commit history:
git remote add upstream [email protected]:Aetherspace/green-stack-starter.git
git fetch upstream
git pull --rebase upstream main
Install packages: yarn install
Run on web & mobile: yarn dev
Run with Storybook docs: yarn dev:docs
Continue with >>> Quickstart > Core Concepts > Recommended Way of Working
...
Thorr / @codinsonn.dev's personal website & app.
Built with the GREEN stack, Aetherspace, Expo-Router & the Next.js App Dir