Adding Maintenance Mode to a React App with Nginx Integration

When running a production-grade React application served by Nginx, maintenance mode becomes a handy tool for planned downtime. I recently implemented maintenance mode for a Vite-powered React app and learned a lot about how to make it efficient and dynamic. Here, I’ll share the steps to set it up so that your app checks for maintenance mode periodically, ensuring minimal disruption to your users.

Thank me by sharing on Twitter 🙏

Why Maintenance Mode Matters

Maintenance mode is essential when you need to pause your app’s functionality for updates, data migrations, or server-side changes. Instead of users encountering errors or partially loaded pages, a simple maintenance page ensures they know the app will be back soon.

My setup leverages Nginx to dynamically signal maintenance mode through HTTP headers. The app periodically checks the server for updates, making it responsive to changes without a full page reload.

Setting Up Nginx for Maintenance Mode

To start, I configured Nginx to send a custom HTTP header, which serves as the maintenance mode indicator.

Here’s how I modified the Nginx configuration:

Plaintext
server {
    listen 80;
    server_name my-app.com;

    location / {
        # Serve the React app
        root /path/to/vite/build;
        index index.html;

        # Add a custom header for maintenance mode
        add_header X-Maintenance-Mode "true";
    }
}

The add_header directive lets me control whether maintenance mode is active by setting X-Maintenance-Mode to "true" or "false". Updating this header doesn’t require redeploying the app; I just reload the Nginx configuration.

To apply changes, I ran:

Plaintext
sudo nginx -s reload

With this setup, Nginx dynamically informs the app about the maintenance mode status.

Building a Custom Hook for Maintenance Checks

Next, I moved to the React app. Instead of hardcoding maintenance mode, I created a custom hook that fetches the X-Maintenance-Mode header from the server. This hook checks the server every 60 seconds to detect updates dynamically.

Here’s the TypeScript code for the hook:

TypeScript
import { useState, useEffect } from 'react';

const useMaintenanceMode = (): boolean => {
  const [isMaintenanceMode, setIsMaintenanceMode] = useState(false);

  useEffect(() => {
    const checkMaintenanceMode = async () => {
      try {
        const response = await fetch(window.location.href);
        const maintenanceMode = response.headers.get('X-Maintenance-Mode');
        setIsMaintenanceMode(maintenanceMode === 'true');
      } catch (error) {
        console.error('Failed to fetch maintenance mode status:', error);
      }
    };

    // Initial check
    checkMaintenanceMode();

    // Set up an interval to recheck every 60 seconds
    const intervalId = setInterval(checkMaintenanceMode, 60000);

    // Clean up interval on component unmount
    return () => clearInterval(intervalId);
  }, []);

  return isMaintenanceMode;
};

export default useMaintenanceMode;

Here’s what’s happening in the hook:

  1. Initial Check: When the app loads, it fetches the current page to read the X-Maintenance-Mode header.
  2. 60-Second Interval: A setInterval ensures the app rechecks the status every 60 seconds.
  3. Cleanup: The clearInterval function prevents memory leaks when the component using the hook is unmounted.

Integrating the Hook with React Router

Once the hook was ready, I integrated it into the app’s routing logic. The goal was to show a maintenance page whenever maintenance mode was active. Here’s how I used the hook in the app:

TypeScript
import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import MaintenancePage from './MaintenancePage';
import HomePage from './HomePage';
import AboutPage from './AboutPage';
import useMaintenanceMode from './useMaintenanceMode';

const App: React.FC = () => {
  const isMaintenanceMode = useMaintenanceMode();

  return (
    <Router>
      {isMaintenanceMode ? (
        <Switch>
          {/* Render MaintenancePage for all routes */}
          <Route path="*" component={MaintenancePage} />
        </Switch>
      ) : (
        <Switch>
          {/* Define regular app routes */}
          <Route path="/" exact component={HomePage} />
          <Route path="/about" component={AboutPage} />
          {/* Add additional routes as needed */}
        </Switch>
      )}
    </Router>
  );
};

export default App;

With this setup:

  • If isMaintenanceMode is true, the app renders the MaintenancePage for all routes.
  • When the header value changes to false, the regular routes automatically become accessible without requiring a page reload.

Designing the Maintenance Page

The maintenance page needs to be simple and informative. Here’s the MaintenancePage component I used:

TypeScript
import React from 'react';

const MaintenancePage: React.FC = () => {
  return (
    <div style={{ textAlign: 'center', marginTop: '20vh' }}>
      <h1>Under Maintenance</h1>
      <p>Were working hard to improve your experience. Please check back later!</p>
    </div>
  );
};

export default MaintenancePage;

This minimal design ensures users know what’s happening without clutter or unnecessary detail.

Testing the Setup

Before deploying, I tested the maintenance mode implementation:

  1. Local Testing: I used a mock server to simulate Nginx’s behavior. Tools like Postman helped verify that the app correctly read the X-Maintenance-Mode header.
  2. Simulated Downtime: I switched the Nginx header between "true" and "false" to confirm the app updated dynamically.
  3. Performance Check: I verified that the periodic checks didn’t introduce noticeable delays or resource usage issues.

Deploying and Managing Maintenance Mode

After confirming the setup worked as expected, I deployed the updated app to production. Managing maintenance mode is now straightforward:

  • Activate Maintenance Mode: Update the Nginx configuration to set X-Maintenance-Mode to "true" and reload Nginx.
  • Deactivate Maintenance Mode: Change the header to "false" and reload.

Since the app checks for updates every 60 seconds, any change in the header takes effect almost immediately for active users.

Wrapping Up

Setting up a dynamic maintenance mode with Vite, React, and Nginx turned out to be both practical and efficient. By leveraging Nginx headers and a custom React hook, I built a solution that doesn’t require page reloads or redeploys to activate. This approach keeps users informed during downtime while minimizing disruption.

Maintenance mode may seem like a small feature, but it plays a big role in maintaining a polished user experience during updates. If you’re running a production app, this setup is definitely worth adding to your toolkit.

Share this:

Leave a Reply