react-nativeexporeact-native-webexpo-web

Deploy Expo React Native Web app to a subfolder


How can I have my mobile app also work when deployed to a web server in a subfolder?

I'm trying to create a universal app (native devices and web) using Expo and its create-expo-app boilerplate, which uses React Native and React Native for Web.

When I deploy to my web server, it will not be in the root, rather in a subfolder. When I build (npx expo build:web), upload to my server, and browse to https://<myserver>/subfolder I get a message This screen doesn't exist. There is a link to Go to home screen! which changes the browser URL, removing subfolder. The app functions correctly, but the URL does not include subfolder. Thus, this is a routing/linking issue.

I've added a property to my package.json:

  "homepage": "/subfolder",

I have seen elsewhere that <Router> or <BrowserRouter> can be given an attribute like basename={'subfolder'}, but with Expo and React Native there is no Router/BrowserRouter component. The boilerplate seems to use a completely different paradigm with NavigationContainer from @react-navigation/native.

Edit: An easy way to reproduce this is to run create-expo-app, then set homepage to /web-build/ in package.json. Run npx expo export:web which populates the web-build folder, then run npx serve or py -m http.server or some other lightweight web server in the current folder. Browsing to http://localhost:port/web-build/ yields the behavior I described.


Solution

  • I have a two-part solution, but I still don't know if this is the canonical way it should be done.

    First, in package.json change the homepage property to just a dot (i.e. "homepage": "."). This makes file references relative to the current directory. I've seen some people argue against doing this, but it seems to work.

    Second, where your screens are defined use a variable for the root path. For example:

    import { Platform } from 'react-native';
    
    var baseURL = '';
    if (Platform.OS == 'web') baseURL = '/subfolder';
    
    const linking: LinkingOptions<RootStackParamList> = {
      prefixes: [Linking.createURL('/')],
      config: {
        screens: {
          Root: {
            path: baseURL, // <- This line was added
            screens: {
              TabOne: {
                screens: {
                  TabOneScreen: 'one',
                },
              },
              TabTwo: {
                screens: {
                  TabTwoScreen: 'two',
                },
              },
            },
          },
          Modal: 'modal',
          NotFound: '*',
        },
      }
    };