search
React star Featured

How to Fix React Router 404 Error on Page Refresh Error

Learn how to fix React Router 404 errors on page refresh. Complete guide with solutions for client-side routing and server configuration.

person By Gautam Sharma
calendar_today January 2, 2026
schedule 12 min read
React React Router Routing Error Handling SPA Deployment

The React Router 404 error on page refresh is a common issue developers encounter when implementing client-side routing in React applications. This error occurs because the server doesn’t know how to handle client-side routes and returns a 404 error when users directly access or refresh deep links.

This comprehensive guide provides complete solutions to resolve the React Router 404 error with practical examples and server configuration techniques.


Understanding the React Router 404 Error

React Router handles client-side routing, but when users refresh the page or directly access a route, the server receives the request and doesn’t know about the client-side routes, resulting in a 404 error.

Common Error Scenarios:

  • 404 Not Found when accessing /about directly
  • Page not found after refreshing /dashboard route
  • Cannot GET /users/123 error in browser
  • Route not found when sharing deep links

Common Causes and Solutions

1. Server Configuration Issues

The most common cause is server misconfiguration for handling client-side routes.

❌ Problem Scenario:

// Server configured for static file serving only
// When accessing /about, server looks for /about.html which doesn't exist
// This causes 404 error

✅ Solution: Configure Server for SPA

// Express.js server configuration for React Router
const express = require('express');
const path = require('path');
const app = express();

// Serve static files
app.use(express.static(path.join(__dirname, 'build')));

// Handle React Router routes
app.get('*', (req, res) => {
  // Always serve index.html for client-side routing
  res.sendFile(path.join(__dirname, 'build', 'index.html'));
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

2. BrowserRouter vs HashRouter

Using BrowserRouter without proper server configuration causes 404 errors.

❌ Problem Scenario:

// Using BrowserRouter without server configuration
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';

function App() {
  return (
    <Router>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
      </Routes>
    </Router>
  );
}

✅ Solution: Use HashRouter or Configure Server

// Option 1: Use HashRouter (URLs will have #)
import { HashRouter as Router, Routes, Route } from 'react-router-dom';

function App() {
  return (
    <Router>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
      </Routes>
    </Router>
  );
}

// Option 2: Keep BrowserRouter with proper server config (recommended)
// (Server configuration shown above)

Solution 1: Server-Side Configuration

Configure your server to handle client-side routing properly.

Apache Configuration (.htaccess):

# .htaccess file for Apache servers
Options -MultiViews
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.html [QR,L]

Nginx Configuration:

# nginx.conf or site configuration
server {
  listen 80;
  server_name example.com;
  root /path/to/your/build;
  index index.html;

  # Handle client-side routing
  location / {
    try_files $uri $uri/ /index.html;
  }
}

Netlify Configuration:

# netlify.toml
[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200

Vercel Configuration:

// vercel.json
{
  "rewrites": [
    {
      "source": "/(.*)",
      "destination": "/index.html"
    }
  ]
}

Solution 2: Express.js Server Setup

Create a proper Express.js server for React Router.

// server.js
const express = require('express');
const path = require('path');
const app = express();

// Serve static files from the React app build directory
app.use(express.static(path.join(__dirname, 'build')));

// API routes (if you have backend APIs)
app.use('/api', require('./api')); // Your API routes

// Catch all handler: send back React's index.html file for any non-API routes
app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname, 'build', 'index.html'));
});

const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});

Advanced Express Configuration:

// advanced-server.js
const express = require('express');
const path = require('path');
const compression = require('compression');
const app = express();

// Enable compression
app.use(compression());

// Serve static files
app.use(express.static(path.join(__dirname, 'build'), {
  maxAge: '1y', // Cache static files for 1 year
  etag: false   // Disable etag for static files
}));

// API routes
app.use('/api', require('./api'));

// Handle all other routes with React Router
app.get('*', (req, res) => {
  // Check if it's an API request
  if (req.url.startsWith('/api/')) {
    // This shouldn't happen, but just in case
    res.status(404).json({ error: 'API endpoint not found' });
  } else {
    // Serve the React app
    res.sendFile(path.join(__dirname, 'build', 'index.html'));
  }
});

const PORT = process.env.PORT || process.env.REACT_APP_PORT || 5000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

Solution 3: React Router Configuration

Properly configure React Router for production.

// App.js - Proper React Router setup
import React from 'react';
import {
  BrowserRouter,
  Routes,
  Route,
  Navigate
} from 'react-router-dom';

// Import your components
import Home from './components/Home';
import About from './components/About';
import Contact from './components/Contact';
import Dashboard from './components/Dashboard';
import NotFound from './components/NotFound';
import Layout from './components/Layout';

function App() {
  return (
    <BrowserRouter>
      <Layout>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/contact" element={<Contact />} />
          <Route path="/dashboard" element={<Dashboard />} />
          
          {/* Handle nested routes */}
          <Route path="/dashboard/*" element={<Dashboard />} />
          
          {/* Redirect from old routes */}
          <Route path="/old-about" element={<Navigate to="/about" replace />} />
          
          {/* 404 page - must be last route */}
          <Route path="*" element={<NotFound />} />
        </Routes>
      </Layout>
    </BrowserRouter>
  );
}

export default App;

Custom 404 Component:

// components/NotFound.js
import React from 'react';
import { Link } from 'react-router-dom';

function NotFound() {
  return (
    <div className="not-found">
      <h1>404 - Page Not Found</h1>
      <p>The page you're looking for doesn't exist.</p>
      <Link to="/">Go back to home</Link>
    </div>
  );
}

export default NotFound;

Solution 4: Development vs Production Setup

Handle routing differently in development and production.

// App.js - Environment-aware routing
import React from 'react';
import {
  BrowserRouter,
  HashRouter,
  Routes,
  Route
} from 'react-router-dom';

// Import components
import Home from './components/Home';
import About from './components/About';
import Contact from './components/Contact';

function App() {
  // Use different router based on environment
  const Router = process.env.NODE_ENV === 'production' 
    ? BrowserRouter 
    : HashRouter;

  return (
    <Router>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
      </Routes>
    </Router>
  );
}

export default App;

Development Server Configuration:

// webpack.config.js for development
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
  },
  devServer: {
    contentBase: path.join(__dirname, 'dist'),
    port: 3000,
    // Enable history API fallback for React Router
    historyApiFallback: {
      // Specific routes to handle
      rewrites: [
        { from: /^\/$/, to: '/index.html' },
        { from: /^\/subpage/, to: '/index.html' },
        { from: /./, to: '/index.html' }
      ]
    },
    open: true,
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ],
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
        },
      },
    ],
  },
};

Solution 5: Next.js Alternative

Consider using Next.js for better server-side routing.

// pages/index.js
export default function Home() {
  return <div>Home Page</div>;
}

// pages/about.js
export default function About() {
  return <div>About Page</div>;
}

// pages/users/[id].js
export default function User({ user }) {
  return <div>User: {user.name}</div>;
}

// pages/api/users/[id].js
export default function handler(req, res) {
  // API route
  res.status(200).json({ name: 'John Doe' });
}

Next.js with Custom Server:

// server.js
const { createServer } = require('http');
const { parse } = require('url');
const next = require('next');

const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();

app.prepare().then(() => {
  createServer((req, res) => {
    const parsedUrl = parse(req.url, true);
    handle(req, res, parsedUrl);
  }).listen(3000, (err) => {
    if (err) throw err;
    console.log('> Ready on http://localhost:3000');
  });
});

Solution 6: Error Boundary for Routing Issues

Implement error boundaries to handle routing errors gracefully.

// components/RouterErrorBoundary.js
import React from 'react';
import { Link } from 'react-router-dom';

class RouterErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  componentDidCatch(error, errorInfo) {
    console.error('Routing error:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return (
        <div className="error-boundary">
          <h2>Something went wrong with routing.</h2>
          <p>Error: {this.state.error?.message}</p>
          <Link to="/">Go back to home</Link>
        </div>
      );
    }

    return this.props.children;
  }
}

// App.js with error boundary
import React from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import RouterErrorBoundary from './components/RouterErrorBoundary';
import Home from './components/Home';
import About from './components/About';

function App() {
  return (
    <BrowserRouter>
      <RouterErrorBoundary>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
        </Routes>
      </RouterErrorBoundary>
    </BrowserRouter>
  );
}

export default App;

Solution 7: Testing and Debugging

Test your routing setup to ensure it works correctly.

// test/routing.test.js
import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import App from '../App';

test('should render home page', () => {
  render(
    <MemoryRouter initialEntries={['/']}>
      <App />
    </MemoryRouter>
  );
  
  expect(screen.getByText(/home/i)).toBeInTheDocument();
});

test('should render about page', () => {
  render(
    <MemoryRouter initialEntries={['/about']}>
      <App />
    </MemoryRouter>
  );
  
  expect(screen.getByText(/about/i)).toBeInTheDocument();
});

// Test 404 page
test('should render 404 page for unknown routes', () => {
  render(
    <MemoryRouter initialEntries={['/unknown-route']}>
      <App />
    </MemoryRouter>
  );
  
  expect(screen.getByText(/404/i)).toBeInTheDocument();
});

Debugging Utilities:

// utils/routing-debug.js
export const debugRouting = (location) => {
  if (process.env.NODE_ENV === 'development') {
    console.log('Current route:', location.pathname);
    console.log('Query params:', location.search);
    console.log('Hash:', location.hash);
  }
};

// Component using debug utilities
import { useLocation } from 'react-router-dom';
import { debugRouting } from '../utils/routing-debug';

function DebugComponent() {
  const location = useLocation();
  
  useEffect(() => {
    debugRouting(location);
  }, [location]);

  return <div>Debug Component</div>;
}

Solution 8: Advanced Routing Patterns

Handle complex routing scenarios.

// Advanced routing with protected routes
import React from 'react';
import {
  BrowserRouter,
  Routes,
  Route,
  Navigate
} from 'react-router-dom';

// Protected route component
function ProtectedRoute({ children, isAuthenticated }) {
  return isAuthenticated ? children : <Navigate to="/login" replace />;
}

// Lazy loading with error boundaries
const LazyHome = React.lazy(() => import('./components/Home'));
const LazyAbout = React.lazy(() => import('./components/About'));

function App() {
  const [isAuthenticated, setIsAuthenticated] = useState(false);

  return (
    <BrowserRouter>
      <Routes>
        <Route 
          path="/" 
          element={
            <React.Suspense fallback={<div>Loading...</div>}>
              <LazyHome />
            </React.Suspense>
          } 
        />
        
        <Route 
          path="/about" 
          element={
            <React.Suspense fallback={<div>Loading...</div>}>
              <LazyAbout />
            </React.Suspense>
          } 
        />
        
        <Route 
          path="/dashboard" 
          element={
            <ProtectedRoute isAuthenticated={isAuthenticated}>
              <Dashboard />
            </ProtectedRoute>
          } 
        />
        
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}

Performance Considerations

Optimized Routing:

// Performance-optimized routing
import React, { useMemo } from 'react';
import {
  BrowserRouter,
  Routes,
  Route,
  useLocation
} from 'react-router-dom';

// Route performance monitoring
function RoutePerformance() {
  const location = useLocation();
  const startTime = useMemo(() => performance.now(), [location.pathname]);

  useEffect(() => {
    const endTime = performance.now();
    const renderTime = endTime - startTime;
    
    if (process.env.NODE_ENV === 'development') {
      console.log(`Route ${location.pathname} rendered in ${renderTime}ms`);
    }
  }, [location.pathname, startTime]);

  return null;
}

function App() {
  return (
    <BrowserRouter>
      <RoutePerformance />
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
      </Routes>
    </BrowserRouter>
  );
}

Security Considerations

Secure Routing:

// Secure routing implementation
import { useNavigate } from 'react-router-dom';

function SecureComponent() {
  const navigate = useNavigate();

  const handleNavigation = (path) => {
    // Validate path to prevent open redirect vulnerabilities
    if (isValidPath(path)) {
      navigate(path);
    } else {
      console.error('Invalid navigation path:', path);
      navigate('/error');
    }
  };

  const isValidPath = (path) => {
    // Implement path validation logic
    return /^\/[a-zA-Z0-9-_/]*$/.test(path);
  };

  return (
    <div>
      <button onClick={() => handleNavigation('/dashboard')}>
        Go to Dashboard
      </button>
    </div>
  );
}

Common Mistakes to Avoid

1. Not Configuring Server for SPA:

// ❌ Don't do this
// Server only serves static files without handling client routes

2. Using BrowserRouter Without Server Config:

// ❌ Don't do this
function BadApp() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/about" element={<About />} />
        {/* This will cause 404 on refresh without server config */}
      </Routes>
    </BrowserRouter>
  );
}

3. Missing 404 Route:

// ❌ Don't do this
function BadRoutes() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
      {/* Missing catch-all route for 404 */}
    </Routes>
  );
}

Alternative Solutions

Using React DevTools:

// Component optimized for React DevTools
function DevToolsOptimizedComponent() {
  return (
    <div data-testid="routing-component">
      <Routes>
        <Route path="/" element={<Home />} />
      </Routes>
    </div>
  );
}

Feature Detection:

// Check for routing features
function RoutingFeatureChecker() {
  const [supportsHistory, setSupportsHistory] = useState(false);

  useEffect(() => {
    setSupportsHistory(!!window.history.pushState);
  }, []);

  return (
    <div>
      {supportsHistory ? 'History API supported' : 'History API not supported'}
    </div>
  );
}

Troubleshooting Checklist

When encountering React Router 404 errors:

  1. Check Server Configuration: Ensure server handles client-side routes
  2. Verify Router Type: Confirm BrowserRouter vs HashRouter usage
  3. Test Direct Access: Access routes directly in browser
  4. Check Build Process: Verify production build works locally
  5. Review Redirects: Ensure proper redirect configuration
  6. Validate Route Order: Confirm 404 route is last
  7. Test Deployment: Verify configuration on deployment platform

Conclusion

The React Router 404 error on page refresh occurs when the server doesn’t know how to handle client-side routes. By properly configuring your server to serve the index.html file for all routes, using appropriate router types, and implementing proper error handling, you can resolve this issue and ensure smooth client-side routing in your React applications.

The key to resolving this error is understanding the difference between client-side and server-side routing, implementing proper server configuration, and testing your routing setup thoroughly. Whether you’re using Express.js, Apache, Nginx, or deployment platforms like Netlify and Vercel, the solutions provided in this guide will help you create robust routing in your React applications.

Remember to always test your routing in production-like environments, implement proper error handling, and use appropriate router types based on your deployment requirements.

Gautam Sharma

About Gautam Sharma

Full-stack developer and tech blogger sharing coding tutorials and best practices

Related Articles

React

How to Fix React app works locally but not after build Error

Learn how to fix React apps that work locally but fail after build. Complete guide with solutions for production deployment and build optimization.

January 2, 2026
React

How to Solve React Blank Page After Deploy & Build Error Tutorial

Learn how to fix React blank page errors after deployment. Complete guide with solutions for production builds and deployment optimization.

January 1, 2026
React

Fix: Blank page after deploying React app on Vercel / Netlify - Complete Guide

Learn how to fix blank page errors when deploying React apps on Vercel and Netlify. This guide covers routing, build issues, and deployment best practices.

January 1, 2026