Volvic RBX
Volvic RBX

Reputation: 65

Expo, React Native: Issue with URL & Refreshing

Hey guys I am having a small issue, I have been building a test add for the following platforms [ Web, Phones, Desktop ]

The following frameworks I am using,

My issue is when on the PWA build after building the app, I am having an issue where if a user wants to directly navigate to a specific path OR refreshes the page the website returns 404 when clearly works in the following example GIF's below.

-- Expo dev tool: https://gyazo.com/0abc4161810cb14e74543b3dcb854b49

-- After running expo build:web then after expo finished building the PWA I run npx serve web-build : https://gyazo.com/ed699dcc4b13830647552b07ee112c62

I am not sure what added config I need to do or what? I have been searching my issue and cannot find anything

If anyone can help would be you guys !!

ADDED NOTE >>>

I haven't coded a login system or any type of cashing etc this is just components with one page having a button that sends a user to a page(Profile)

App.js

import LoginScreen from './views/login'
import ProfileScreen from './views/profile'

export default function App() {
  return React.createElement(
    (Platform.OS == 'web') ? web_navigator : mobile_navigator
  );
}

function mobile_navigator() {
  const Tab = createBottomTabNavigator();
  return (
    <NavigationContainer>
        <Tab.Navigator
        screenOptions={{
            headerShown: false,
            tabBarStyle: {
              backgroundColor: "#242629",
              borderTopWidth: 0
            },
            tabBarOptions: {
              showLabel: false,
            }
        }}
        
        >
  
          <Tab.Screen name="Login" component={LoginScreen} />

          <Tab.Screen name="Profile" component={ProfileScreen} />
        </Tab.Navigator>
    </NavigationContainer>
  );
}

function web_navigator() {
  const Stack = createStackNavigator();
  
  return (
      <NavigationContainer linking={{
        config: {
          screens: {
            Login: "/",
            Profile: "/Profile/:Username?"
          }
        },
        }}>
        <Stack.Navigator
        screenOptions={{
          headerShown: false
        }}>
          <Stack.Screen name="Login" component={LoginScreen} />
          <Stack.Screen name="Profile" component={ProfileScreen} />
        </Stack.Navigator>
      </NavigationContainer>
  );
}

Login.js

    const Login = ({navigation}) => {
      const [userName, setUserName] = useState('');
      const [userPassword, setUserPassword] = useState('');
    
        return (
          <View style={styles.Body} >
    
            {Platform.OS === 'web' ?
          
            <View style={styles.Body_left} >
              <Image 
                source={require("../assets/LeftPanel3.png")} 
                style={styles.Body_left_Picture} 
              />
            </View>
    
            : null}
    
            <SafeAreaView  style={styles.Body_right} >
                <View style={styles.Body_Container} >
                  <Image 
                    source={require("../assets/PlaceHolderLogo.png")} 
                    style={styles.Logo} 
                  />
                  <Text h1 style={styles.Login_Title}>Let's Sign In</Text>
                  <Text style={styles.Login_Title_Child}>Welcome Back!</Text>
    
                  <View style={styles.Input_Group} >
                    <TextInput value={userName} placeholder={'Enter Username'} placeholderTextColor="#fff" onChangeText={(inputOne) => setUserName(inputOne)} style={styles.Input} />
                    <TextInput value={userPassword} placeholder={'Enter Password'} placeholderTextColor="#fff" onChangeText={(inputTwo) => setUserPassword(inputTwo)} style={styles.Input} />
    
                    <Pressable style={styles.Btn_Main} onPress={() => navigation.navigate("Profile", {Username: userName})}>
                      <Text style={styles.Btn_Text}>Sign In</Text>
                    </Pressable>
                  </View>
    
                </View>
            </SafeAreaView >
    
          </View>
        );
    }
    
    export default Login;

Profile.js

export default function App({route}) {

  const Profile_Username = route.params ? route.params.Username : "UnKnown"

  const Profile_Picture = "https://www.trickscity.com/wp-content/uploads/2019/02/pubg-dp.jpeg"

  return (
      <View style={{ position: 'relative', backgroundColor: "#242629", flex: 1 }} >
        
        <View style={{ position: 'relative', backgroundColor: "#242629", flex: 0.3 }} ></View>


        

        <View style={{ position: 'relative', backgroundColor: "#16161a", flex: 1 }} >

          <View style={styles.ProfileParent} >

            <View style={styles.ProfilePictureParent}>
              <Image 
                source={{uri: Profile_Picture}} 
                style={styles.ProfilePicture} 
              />
            </View>

            <View style={styles.ProfileTitleBox}>
              <Text style={styles.ProfileName}>{Profile_Username}</Text>
              <Text style={styles.ProfileTag}>Member</Text>
            </View>

          </View>

        </View>


      </View>
      
  );
}

---[ PWA Files ]---

serve.json

{
  "headers": [
    {
      "source": "static/**/*.js",
      "headers": [
        {
          "key": "Cache-Control",
          "value": "public, max-age=31536000, immutable"
        }
      ]
    }
  ]
}

manifest.json

{
  "background_color": "#ffffff",
  "display": "standalone",
  "lang": "en",
  "name": "DevBuild",
  "short_name": "DevBuild",
  "start_url": "/?utm_source=web_app_manifest",
  "orientation": "portrait",
  "icons": [
    {
      "src": "\\pwa\\chrome-icon\\chrome-icon-144.png",
      "sizes": "144x144",
      "type": "image/png"
    },
    {
      "src": "\\pwa\\chrome-icon\\chrome-icon-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "\\pwa\\chrome-icon\\chrome-icon-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

Upvotes: 2

Views: 2784

Answers (3)

Ovidiu Cristescu
Ovidiu Cristescu

Reputation: 1053

Indeed, this is not an issue with Expo or with React Native. You need to configure your hosting provider to handle client-side routing.

This is what worked for me with Vercel, expo-router and Metro bundler:

Source: https://github.com/expo/expo-cli/issues/2983#issuecomment-1593022078

  1. Add this in a vercel.json file inside the root of your project.
{
  "rewrites": [
    { "source": "/:path*", "destination": "/index.html"}
  ]
}
  1. Add your whole repository to Vercel.
  2. Your build and development settings should look like this:
  • Framework preset: Other
  • Build command: npx expo export --platform web
  • Output directory: dist

image

On the same thread, you will find other solutions for other situations as well:

Upvotes: 0

pfndesign
pfndesign

Reputation: 174

your problem is not the expo or react-native, you need to use htaccess in your host to send all of your requests to index.html where your PWA is . the first time you are navigating to your PWA it will load the index.html and each route is loaded within that HTML file with the URL address bar changing according to your linking config but when you refresh the page host think that it has to go to subfolders because it has no idea that your routes don't exist outside of your PWA

simply create a .htaccess file inside of your PWA folder and add this code to it, it should solve your problem

<IfModule mod_rewrite.c>
  RewriteEngine On  
  RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR]
  RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
  RewriteRule ^ - [L]
  RewriteRule ^ /index.html [L]
</IfModule>

for your local environment edit serve.json and add code bellow

"rewrites": [
    { "source": "/**", "destination": "/index.html" }
]

for more info about rewrites, you can check https://github.com/vercel/serve-handler#options

Upvotes: 2

Volvic RBX
Volvic RBX

Reputation: 65

Solution

Thanks to @pfndesign

Told me to edit the serve.json to include

  "rewrites": [
    { "source": "/**", "destination": "/index.html"}
  ]

Note I am not 100% sure on what it does but it worked for me,

pfndesign also linked me to this: https://github.com/vercel/serve-handler#options

Hope this helps anyone reading this :D

Upvotes: 4

Related Questions