When I was in the beginning phase of my web development journey, I faced a highly frustrating issue: my web app worked perfectly fine locally, but the moment I deployed it, hitting the refresh button threw a nasty 404 Not Found error.
The fix is usually just a few lines of configuration code in your root directory. But as developers, we shouldn't just patch bugs without understanding them.
Let’s deep dive into why this happens.
The Catch: Browsers Don't Speak React
Let's look at a React application as an example. We write our frontend code using JSX (an extension of JavaScript that allows us to write HTML-like structures wrapped in JS logic). However, web browsers only inherently understand three things: HTML, CSS, and standard JavaScript.
This is where a modern build tool like Vite comes into the picture.
When you run your build command, Vite compiles your entire JSX codebase, dependencies, and assets into a highly optimized bundle—typically consisting of a single index.html file and a few compressed JavaScript/CSS files.
When a user first visits your deployed site, the server sends down this primary index.html file along with the JavaScript bundle. Now, your entire application logic resides right inside the browser.
The Core Problem: Client-Side Routing vs. Deployed Servers
When you use a library like React Router and click on a link (e.g., navigating from Home to /about), notice what actually happens:
- The browser does NOT send a request to the server.
- Instead, the JavaScript bundle intercepts the click, prevents the default page load, manually updates the URL in the address bar, and swaps out the UI components smoothly.
This is called Client-Side Routing.
However, everything changes the moment a user hits Refresh while sitting on https://your-app.com/about.
When you refresh, the browser bypasses your JavaScript bundle completely. It treats the URL as a direct request and asks the deployment server: "Hey, please give me the physical file located at /about."
Because hosting platforms don't keep your project directory exactly as you uploaded it—they only serve your compiled build folder—the server searches for a folder or file named about and finds nothing. Since the file doesn't exist on the server's hard drive, it returns a 404 Not Found error.
The Solution: Rewriting Server Traffic
To fix this, we need to tell the hosting platform's server: "Hey, no matter what path the user asks for, always fall back and serve the main index.html file, then let our JavaScript bundle handle the routing."
Here is how to configure it across different deployment environments:
1. Vercel (vercel.json)
Create a file named vercel.json in the root directory of your project:
{
"rewrites": [
{
"source": "/(.*)",
"destination": "/index.html"
}
]
}
How this "Magic" File Works
Whenever a request comes in matching /(.*) (which is a regular expression meaning "anything after the forward slash", such as /about or /profile/settings), Vercel's server reads this configuration. Instead of throwing a 404, it serves the main index.html file to the user.
Wait... What About Static Files Like Favicons?
You might wonder: If every single URL redirects to index.html, how does the server serve static files like favicon.ico or images that aren't bundled inside the main JS file?
The server first searches the build directory to see if a physical file matches the requested path exactly. If it finds your favicon.ico file, it serves it immediately. It only triggers the rewrite rule and falls back to index.html if the requested file cannot be found.
2. Netlify (_redirects)
Create a file named _redirects (with no file extension) in your public/build root folder:
/* /index.html 200
3. Apache (.htaccess)
Create a file named .htaccess in your site's root directory:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>
4. Nginx (nginx.conf)
Inside your server block configuration file, update the location / directive:
location / {
try_files $uri $uri/ /index.html;
}
5. AWS Amplify
Go to the AWS Amplify Console, navigate to Rewrites and redirects under App Settings, and add the following rule:
-
Source address:
</^[^.]+$|\.(?!(css|gif|ico|jpg|js|png|txt|svg|woff|woff2|ttf|map|json)$)([^.]+$)/> -
Target address:
/index.html -
Type:
200 (Rewrite)
Conclusion
The "404 on refresh" bug is a classic rite of passage for frontend developers. Solving it requires understanding that in a Single Page Application, the server's only job is to deliver the initial bundle, while JavaScript takes care of the rest. By setting up simple server rewrite rules, you ensure a seamless user experience across your entire application!
Top comments (0)