๐ axeldelafosse.com
๐๏ธ stemgen.dev
๐ชฉ recordpool.app
Create a universal React app using Expo and Next.js in a monorepo
Home Page: https://expo-next-monorepo-example.vercel.app
License: MIT License
๐ axeldelafosse.com
๐๏ธ stemgen.dev
๐ชฉ recordpool.app
Hi. Thanks for creating this monorepo.
I wonder whether server side rendering is possible here? I tried the example but couldn't get the SSR working.
yarn
yarn dev:next
I got the following error:
info - automatically enabled Fast Refresh for 1 custom loader
info - Using external babel configuration from /.../packages/next/.babelrc.json
info - automatically enabled Fast Refresh for 1 custom loader
HookWebpackError: Cannot read properties of undefined (reading 'replace')
at makeWebpackError (/Users/yanzhu/Documents/Project/study/js/playgrounds/test/node_modules/next/dist/compiled/webpack/bundle5.js:41664:9)
at /Users/yanzhu/Documents/Project/study/js/playgrounds/test/node_modules/next/dist/compiled/webpack/bundle5.js:25354:12
at eval (eval at create (/Users/yanzhu/Documents/Project/study/js/playgrounds/test/node_modules/next/dist/compiled/webpack/bundle5.js:140346:10), <anonymous>:12:1)
at fn (/Users/yanzhu/Documents/Project/study/js/playgrounds/test/node_modules/next/dist/compiled/webpack/bundle5.js:22775:17)
at Hook.eval [as callAsync] (eval at create (/Users/yanzhu/Documents/Project/study/js/playgrounds/test/node_modules/next/dist/compiled/webpack/bundle5.js:140346:10), <anonymous>:10:1)
at Hook.CALL_ASYNC_DELEGATE [as _callAsync] (/Users/yanzhu/Documents/Project/study/js/playgrounds/test/node_modules/next/dist/compiled/webpack/bundle5.js:140148:14)
at cont (/Users/yanzhu/Documents/Project/study/js/playgrounds/test/node_modules/next/dist/compiled/webpack/bundle5.js:25351:34)
at /Users/yanzhu/Documents/Project/study/js/playgrounds/test/node_modules/next/dist/compiled/webpack/bundle5.js:25399:10
at symbolIterator (/Users/yanzhu/Documents/Project/study/js/playgrounds/test/node_modules/next/dist/compiled/neo-async/async.js:1:14443)
at done (/Users/yanzhu/Documents/Project/study/js/playgrounds/test/node_modules/next/dist/compiled/neo-async/async.js:1:14823)
-- inner error --
TypeError: Cannot read properties of undefined (reading 'replace')
at PagesManifestPlugin.createAssets (/Users/yanzhu/Documents/Project/study/js/playgrounds/test/node_modules/next/dist/build/webpack/plugins/pages-manifest-plugin.js:35:47)
at /Users/yanzhu/Documents/Project/study/js/playgrounds/test/node_modules/next/dist/build/webpack/plugins/pages-manifest-plugin.js:47:22
at fn (/Users/yanzhu/Documents/Project/study/js/playgrounds/test/node_modules/next/dist/compiled/webpack/bundle5.js:22773:10)
at Hook.eval [as callAsync] (eval at create (/Users/yanzhu/Documents/Project/study/js/playgrounds/test/node_modules/next/dist/compiled/webpack/bundle5.js:140346:10), <anonymous>:10:1)
at Hook.CALL_ASYNC_DELEGATE [as _callAsync] (/Users/yanzhu/Documents/Project/study/js/playgrounds/test/node_modules/next/dist/compiled/webpack/bundle5.js:140148:14)
at cont (/Users/yanzhu/Documents/Project/study/js/playgrounds/test/node_modules/next/dist/compiled/webpack/bundle5.js:25351:34)
at /Users/yanzhu/Documents/Project/study/js/playgrounds/test/node_modules/next/dist/compiled/webpack/bundle5.js:25399:10
at symbolIterator (/Users/yanzhu/Documents/Project/study/js/playgrounds/test/node_modules/next/dist/compiled/neo-async/async.js:1:14443)
at done (/Users/yanzhu/Documents/Project/study/js/playgrounds/test/node_modules/next/dist/compiled/neo-async/async.js:1:14823)
at /Users/yanzhu/Documents/Project/study/js/playgrounds/test/node_modules/next/dist/compiled/neo-async/async.js:1:9429
caused by plugins in Compilation.hooks.processAssets
TypeError: Cannot read properties of undefined (reading 'replace')
at PagesManifestPlugin.createAssets (/Users/yanzhu/Documents/Project/study/js/playgrounds/test/node_modules/next/dist/build/webpack/plugins/pages-manifest-plugin.js:35:47)
at /Users/yanzhu/Documents/Project/study/js/playgrounds/test/node_modules/next/dist/build/webpack/plugins/pages-manifest-plugin.js:47:22
at fn (/Users/yanzhu/Documents/Project/study/js/playgrounds/test/node_modules/next/dist/compiled/webpack/bundle5.js:22773:10)
at Hook.eval [as callAsync] (eval at create (/Users/yanzhu/Documents/Project/study/js/playgrounds/test/node_modules/next/dist/compiled/webpack/bundle5.js:140346:10), <anonymous>:10:1)
at Hook.CALL_ASYNC_DELEGATE [as _callAsync] (/Users/yanzhu/Documents/Project/study/js/playgrounds/test/node_modules/next/dist/compiled/webpack/bundle5.js:140148:14)
at cont (/Users/yanzhu/Documents/Project/study/js/playgrounds/test/node_modules/next/dist/compiled/webpack/bundle5.js:25351:34)
at /Users/yanzhu/Documents/Project/study/js/playgrounds/test/node_modules/next/dist/compiled/webpack/bundle5.js:25399:10
at symbolIterator (/Users/yanzhu/Documents/Project/study/js/playgrounds/test/node_modules/next/dist/compiled/neo-async/async.js:1:14443)
at done (/Users/yanzhu/Documents/Project/study/js/playgrounds/test/node_modules/next/dist/compiled/neo-async/async.js:1:14823)
at /Users/yanzhu/Documents/Project/study/js/playgrounds/test/node_modules/next/dist/compiled/neo-async/async.js:1:9429
PS: The line info - automatically enabled Fast Refresh for 1 custom loader
has come out twice...
What could be wrong here?
Could you tell me the best way to integrate an application created with Expo + Bare Workflow in monorepo Lerna or NX?
Hey, this may not be the best place to ask this but I am wondering how you guys (@axeldelafosse & @nandorojo) would recommend integrating NextJS API routes?
I have been adapting my NextJS PWA over to expo using this repo as a template. I used SWR originally so a lot of my data fetches look like:
const { data: positions, error } = useSWR<Position[]>(
user?.token && user?.accountId
? ["/api/positions", user.token, user.accountId]
: null,
fetcher
)
}
I want to maintain this as best as possible and allow both the expo app & next site to use these endpoints.
I am now using swr-react-native for data fetching.
I have a few (non-elegant) ideas about how to go about this, but thought it best to ask if you had any thoughts on how best to approach it?
Thanks for a great resource here! Curious if you're open to adding a license so I can understand the copyright restrictions of using parts of this code / approach.
Thanks!
Hi @axeldelafosse,
before I went ahead to create a PR, I would like to know if we're on the same page.
Since this package is using app.config.js, we don't have the magic Intellisense automatically using a VSCode plugin. But what can be done is:
main
from index.js
to index.ts
Change contents like so:
import { ExpoConfig } from '@expo/config-types';
declare var process: {
env: {
STAGE: "development" | "staging" | "production",
SCHEME: string,
}
}
const STAGE = process.env.STAGE;
const SCHEME = process.env.SCHEME ?? 'aaa.bbb.ccc';
const envConfig = {
development: {
scheme: `${SCHEME}.development`,
icon: './assets/icon.development.png',
backgroundColor: '#FF0000'
},
staging: {
scheme: `${SCHEME}.staging`,
icon: './assets/icon.staging.png',
backgroundColor: '#8000FF'
},
production: {
scheme: SCHEME,
icon: './assets/icon.png',
backgroundColor: '#1610FF'
}
};
const stageConfig = envConfig[STAGE ?? 'development'];
const config: ExpoConfig = {
.....
What do you think?
It seems that router.on('routeChangeComplete', path => ...)
returns the asPath
for the path
argument. I need to clarify this still. But I'm trying to do something like this: router.push('/dashboard/my-artists/djkhaled', '/@djkhaled')
where the URL should show the asPath
/@djhkhaled
.
However, it seems that the LinkTo
component is trying to navigate to /@djkhaled
instead of the href
. This is breaking for me on Web, since my React Navigation linking config expects the more normal URL. So I'm going to look more into this.
It's possible we read from the Router
directly inside of the routeChangeComplete
event, since I would assume that that's all it's passing down to us anyway.
However, what is the desired behavior? Do we want React Navigation reading the as
path, or the href
? I think it should be the href
, since that lets us use the correct URLs for navigating within a specific tab, etc.
This isn't a bug, but rather a discussion over desired behavior. I think React Navigation should deal with the normal paths, rather than asPath
s.
However, this wouldn't work for native deep linking.
In my case, I have a rewrite in Next.js. Any URL to /@:slug
โ /artists/[slug]
.
However, for deep linking into an app, if I send you beatgig.com/@djkhaled
on like iMessage, and you open it from your phone, the native app will handle that as /@djkhaled
, so we'd have to rewrite this here as well, presumably inside of getPathFromState
. ๐ฅฒ
Wanted to hear you thoughts about adding a basic navigation to the example repo?
IMHO one of the trickiest part about combining these two frameworks is to figure out navigation like Fernando Rojo showed in the lastes nextjs.conf
Hi All,
Amazing work so far, massive fan of this repo!
Remix being the new SSR framework from the guys who made React Router (Its pretty amazing).
www.remix.run <----------- pure awesomness
Just throwing this out there to hear your thoughts, do you think a Nextjs could be easily swapped out in favour of using Remix in this monorepo, would we be able to use shared screens?
Thanks for any help on this :)
๐
I spent the weekend testing the repository creating a responsive navigation, but I ran into a problem with Icons and Images.
Trying with Expo Vector Icons and React Native's Image Component. In the Expo mobile application, the icon appears perfectly, however in NextJS when my navigation is rendered, the icon or image does not appear.
I would like to understand what happens and if possible a tip on how I can create an image component to be used in both NextJS and Expo, both for icons in menus and for the rest of the application.
This application I'm making is a study only, I was interested in the repository and would like to see how it can be applied to my workflow.
I'm having this problem. I believe it's trying to load in the server side, that's why the error, but not sure how to solve it:
ReferenceError: requestAnimationFrame is not defined
at eval (webpack-internal:///../../node_modules/react-native-reanimated/lib/reanimated1/core/AnimatedNode.js:94:117)
at Object.../../node_modules/react-native-reanimated/lib/reanimated1/core/AnimatedNode.js (/home/daniel/projects/labook/packages/web/.next/server/pages/_app.js:2050:1)
at __webpack_require__ (/home/daniel/projects/labook/packages/web/.next/server/webpack-runtime.js:33:42)
at eval (webpack-internal:///../../node_modules/react-native-reanimated/lib/reanimated1/core/AnimatedEvent.js:16:71)
at Object.../../node_modules/react-native-reanimated/lib/reanimated1/core/AnimatedEvent.js (/home/daniel/projects/labook/packages/web/.next/server/pages/_app.js:2028:1)
at __webpack_require__ (/home/daniel/projects/labook/packages/web/.next/server/webpack-runtime.js:33:42)
at eval (webpack-internal:///../../node_modules/react-native-reanimated/lib/createAnimatedComponent.js:18:89)
at Object.../../node_modules/react-native-reanimated/lib/createAnimatedComponent.js (/home/daniel/projects/labook/packages/web/.next/server/pages/_app.js:1797:1)
at __webpack_require__ (/home/daniel/projects/labook/packages/web/.next/server/webpack-runtime.js:33:42)
at eval (webpack-internal:///../../node_modules/react-native-reanimated/lib/Animated.js:7:82)
Sometimes, you want to reuse the same screen inside of different pages. For example, you might have /dashboard/user/artists/[slug]
and /artists/[slug]
, which both render the ArtistScreen
components.
Assuming these have different stacks in their Next.js pages, it's important that you set different names for inside of the react-navigation stacks.
For example, here these two stacks render the ArtistScreen
, but the name must differ:
/artists/[slug]
-> ArtistsStack
-> <Screen name="artist" component={ArtistScreen} />
/my-artists/[slug]
-> MyArtistsStack
-> <Screen name="myArtist" component={ArtistScreen} />
You have to give a different name to the screen so that it can be uniquely identified within its stack relative to the root navigator.
If you don't do this, you get weird race conditions with back buttons on Web.
Leaving this here to document later.
On native, if you have a stack singleton, you can just do this:
<NativeStack.Screen
component={ArtistScreen}
name="artist"
getId={({ params }) => params?.slug}
/>
<NativeStack.Screen
component={ArtistScreen}
name="myArtist"
getId={({ params }) => params?.slug}
/>
There is a very specific case that breaks the pop
logic on web used for our custom BackButton
behavior on the stack.
If you're on a screen, and hit back, it typically works. However, if the screen you're going back to satisfies the following condition, it breaks:
The screen you're going back to satisfies the linking path ''
. That is, if you're going to myurl.com/
, then this screen handles that route.
You can never click the back button and end up at the /
route.
Take this linking config:
const linking = useLinkingConfig({
prefixes: [Linking.makeUrl('/')],
config: {
screens: {
artistsTab: {
path: '',
initialRouteName: 'artists',
screens: {
artist: 'artists/:slug',
},
},
},
},
})
In this case, we have 2 routes in play:
/
routes to the artists
screen name (ArtistsListScreen
component)/artists/:slug
routes to the artist
screen name (ArtistDetailScreen
component)If you're in the artistsTab
, and an artist screen is open (artists/djkhaled
), clicking back should go to /
, opening the artists
screen.
Going "back" from our custom BackButton
component while on artists/djkhaled
instead sends you back to /artists
. This is entirely incorrect. It seems like it's doing this:
""
, then instead it takes the screen name of that route (in this case artists
) and uses that in the URL.
Similarly, when clicking back to a screen that had a parameter (artists/djkhaled
), I have also had it go back to artists?slug=djkhaled
), which was breaking the app, since my next.js page at pages/artists
didn't have access to the artist detail screen.
A workaround I don't love is this: Add a getPathFromState
to your linking config, which transforms your initialRouteName
bug and re-routes it to the home screen.
import { getPathFromState } from '@react-navigation/native'
const rootScreenName = 'artists'
const linking = useLinkingConfig({
prefixes: [Linking.makeUrl('/')],
getPathFromState(state, options) {
const path = getPathFromState<ReactNavigation.RootParamList>(
state,
options
)
if (path == '/' + rootScreenName) {
return '/'
}
if (path == '/' + rootScreenName + '?') {
return '/' // TODO not sure what to do when this happens
}
return path
},
config: {
screens: {
artistsTab: {
path: '',
initialRouteName: rootScreenName,
screens: {
artist: 'artists/:slug',
},
},
},
},
})
I only have this in my app right now, so I would need to build a repro here still.
I have a cool example of shallow routing. It works great with a react navigation stack.
Say you have a stack with these screens:
artists
โ /artists
artist
โ /artists/:slug
When you're visiting either Next.js page, you're rendering the same stack.
The code looks like this:
// pages/artists/[slug] & pages/artists use the same component here
export default function ArtistPage() {
return (
<NativeStack.Navigator screenOptions={screenOptions}>
<NativeStack.Screen
name="artist"
component={ArtistScreen}
getId={({ params }) => params?.slug}
/>
<NativeStack.Screen
name="artists"
component={ArtistsScreen}
/>
</NativeStack.Navigator>
)
}
A typical way to route from /artists
โ artists/:slug
would be to do something like this:
router.push('/artists/drake')
However, this misses out on amazing Next.js feature: shallow routing.
Since every Next.js page is a React Navigation stack in this paradigm, we can take advantage of Next.js shallow routing out of the box. In fact, this is better than normal Next.js routing. It's amazing.
Say were on the artists page at the URL /artists
.
To open an artist with shallow routing, we can do this:
router.push('/artists?slug=drake', '/artists/drake', { shallow: true })
And there we have it! That's literally all we have to do. Now, React Navigation will navigate on the client-side only, using the asPath
set by /artists/drake
.
Okay, but can we do better? Writing the href
argument as /artists?slug=drake
is a bit annoying. For example, what if our component doesn't know which screen it's navigating from?
Rather than hard code the first path, we can dynamically generate the href
:
import Router from 'next/router'
// ...
const openArtist = () => {
const as = '/artists/drake'
let href = Router.router && {
pathname: Router.pathname,
query: { ...Router.query, slug: 'drake' }
}
href = href || as
router.push(href, as, { shallow: true })
}
Okay, so what happened here?
Well, first we make sure that Router
exists. If it doesn't, that means we're on native โ in which case, we don't care about shallow routing, so we fall back to the actual URL.
Next, if Router
exists, we just need to tell Next.js that we want to stay on the same URL we're already on, so we set the href.pathname = Router.pathname
.
Finally, we set the query equal to the current query + any new query parameters we want our screen(s) to access { ...Router.query, slug: 'drake' }
.
And there we have it. We've now navigated from the artists
screen to artist
, cross-platform, using shallow routing. If you go back, your scroll position is preserved, components aren't unmounted, giving users a great UX. In fact, I think this navigation pattern is better than typical Next router, since we can use it on like every page!
There are plans to create documentation from issues like this one, but I'm writing this here so that people along the way can get a sense of different patterns we're working on.
Hey guys!
Awesome project here.
I was playing around with it to check its functionality and where it may lead to, and possibilities are endless. Thanks a lot for sharing your knowledge and this great monorepo configuration.
I was able to deploy the nextjs app to AWS Amplify (just a bit of struggles because of AWS documentation was not super clear) but at the end work perfectly. That side of my CI/CD is going great.
The other side that is the expo publish flow for the native app, is not going so great. The publish completes successfully in the CLI but when trying to open in the Expo Go app, using the QR code, it hangs with a black or white screen. Tried several iPhones and the result is always the same. Rarely enough, the app works perfectly in development, even when putting the production mode on.
I tried to log errors to Sentry or something suspecting a crash might be happening, but with no success. I think it's related to something (maybe js) not loading properly, but my current knowledge is not letting me find the root cause.
To reproduce you can clone the repo, change the owner in the app.config.js to be able to publish to your own expo account and then:
yarn install
cd packages/expo
yarn publish:production
You'll get a successful output from Expo CLI as follows:
Bundle Size
โ index.ios.js (Hermes) 825 kB
โ index.android.js (Hermes) 825 kB
โ index.ios.js.map (Hermes) 3.45 MB
โ index.android.js.map (Hermes) 3.45 MB
๐ก JavaScript bundle sizes affect startup time. Learn more: https://expo.fyi/javascript-bundle-sizes
Analyzing assets
Saving assets
No assets changed, skipped.
Processing asset bundle patterns:
- /home/mora260/Projects/Learning/Mono/expo-next-monorepo-example-main/packages/expo/**/*
Uploading JavaScript bundles
Publish complete
๐ Manifest: https://exp.host/@amoraz/example/index.exp?release-channel=production&sdkVersion=43.0.0 Learn more: https://expo.fyi/manifest-url
โ๏ธ Project page: https://expo.dev/@amoraz/example?release-channel=production Learn more: https://expo.fyi/project-page
Done in 13.48s.
But if you go to that link and try to open it with the Expo Go iOS App, it will fail to start and show the Hello World.
Can you guys point me in the right direction on how to troubleshoot this issue?
Thanks a lot for any help!
Hi,
I was wondering if there's any example of using path aliases in the project?
I have been using path aliases in my standalone expo project. It was working fine and I just had to configure tsconfig.json and babel.config.js in order to make them work. However, I am not sure how we can use path aliases in such a project structure?
Hello! First, thank you for all of the work on this repo.
I was wondering if you have intellisense working for VSCode? When writing code in the App package intellisense works fine for other code written in the same package (ex: importing a customized button), but it doesn't work for any of the packages installed in the Expo package (react native, dripsy, etc.). If I install the package in the App package (understood that I should not do this) then everything works as expected, but I'm hoping there's an easy fix that I'm missing.
Thanks!
So far I managed to run on NextJS, it's running smooth. I having problems running the expo part. (I'm not using expo-dev-client
for now, just want to make it work first)
console.log
is not working, does not give me any messagesHi,
I have installed everything correctly but still, I am facing this issue.
next-images TypeError: unsupported file type: undefined
Here's my next.config.js
const { withExpo } = require('@expo/next-adapter')
const withPlugins = require('next-compose-plugins')
const withFonts = require('next-fonts')
const withImages = require('next-images')
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true'
})
const withTM = require('next-transpile-modules')([
'app',
'@gorhom/bottom-sheet',
'react-doorman',
'react-native-doorman',
'@gorhom/portal',
'dripsy',
'@dripsy/core'
])
const nextConfig = {}
module.exports = withPlugins(
[
withTM,
withFonts,
withImages,
withBundleAnalyzer,
[withExpo, { projectRoot: __dirname + '/../..' }]
],
nextConfig
)
One thing that's a bit confusing: if I do push({ pathname: '/artists/[slug]', query: { offer: 100, slug: 'djkhaled' })
, it creates /artists/djkhaled?offer=100
.
In that case, offer
is "100"
, which is a string.
Using the parse
function on useParam
, we can turn this into a number:
useParam('offer', { parse: Number, initial: 0 })
However, useParam
currently only runs the parse
function on web. The reason being, on web, we know query params will always start out as strings, whereas on Native, they might be sent as actual primitive values, since this is possible for people to do with navigate('artist', { offer: 100 })
.
In order to achieve the same parse
-ing on both native and web, you have to add the parse
option to your linking config, in addition to your useParam
hook.
In reality, this should be set only once: in your linking config. But there are a few issues with that:
useParam
is set up to expect you to parse within the hook, not from the linking config, and the types resemble that. It would require a refactor of this hookparse
only in the linking
config (which would be ideal), then I need to figure out how React Navigation does this under the hood, and copy the same logic into useParam
. This is probably doable, just a bit annoying.On Web, it's harder to restrict users from giving you a messed up URL. A big reason that useParam
has the parse
function is to avoid people giving you bad states: offer=hello
when offer
should be a number.
On Native, this isn't as likely to happen, since people can't type a URL (unless they're deep linking, I guess, but that's a really rare case).
The ideal API: set parse
and stringify
in your linking config, and useParam
just knows what do to on Web.
In fact...it's possible we could just use the react-navigation
params on Web too. Maybe that would simplify all of this, rather than reading from nextRouter.query
.
I tried doing expo upgrade and ran into some issues with getting the app to load. Can we update this example to support the new expo?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.