Maintance Mode in Next.js Applications

By Agnieszka Wojtas

Featured image

When creating and managing a project, it is important to take into account the moments when the application will be unavailable due to maintenance. In the case of CMS systems, such as WordPress, there are various plug-ins that allow you to veil your site while you make changes or test new functionality.

But how to implement maintenance mode in Next.js? Is it as easy as configuring a plugin on WordPress for a few minutes?

Of course it is!

Challenge

While searching for a solution for maintenance mode in Next.js applications, I came across a lack of available tools that met my expectations in terms of speed, flexibility and ease of use.

Solution

To start, we create a route.ts file in the app -> api root folder, which will be responsible for handling the request for our maintenance mode:

import { serialize } from "cookie";

export async function POST(request: Request, params: { slug: string }) {
 const data: { password: string } = await request.json();
 const password = data.password;

 const expirationTime = 4 * 60 * 60; // 4 hours
 const cookie = serialize(process.env.PASSWORD_COOKIE_NAME!, "true", {
   httpOnly: true,
   path: "/",
   maxAge: expirationTime,
 });

 if (process.env.PAGE_PASSWORD !== password) {
   return new Response("incorrect password", {
     status: 401,
   });
 }

 return new Response("password correct", {
   status: 200,
   headers: {
     "Set-Cookie": cookie,
   },
 });
}

The next step will be to create a component, the appearance of which, the user will see immediately after entering the site. Below is a sample design that can be customized:

"use client";

import React, { useState, useEffect } from "react";

const PasswordPromptDialog = () => {
 const [password, setPassword] = useState("");
 const [passwordIncorrect, setPasswordIncorrect] = useState(false);
 const [noPassword, setNoPassword] = useState(false);

 const handleSubmit = async (e: React.FormEvent) => {
   e.preventDefault();

   const request = await fetch(`/api`, {
     body: JSON.stringify({ password }),
     headers: { "Content-Type": "application/json" },
     method: "post",
   });

   if (password) {
     if (request.status !== 200) {
       return setNoPassword(false), setPasswordIncorrect(true);
     } else {
       window.location.reload();
     }
   } else {
     return setPasswordIncorrect(false), setNoPassword(true);
   }
 };

 useEffect(() => {
   setPassword("");
 }, [passwordIncorrect]);

 return (
   <div>
     <p>Maintenance mode is enabled!</p>
     <div>
       <form onSubmit={handleSubmit}>
         <label htmlFor="password">Password:</label>
         <input
           type="password"
           id="password"
           value={password}
           onChange={(e) => setPassword(e.target.value)}
         />
         <button type="submit">
           Log In
         </button>
       </form>
       {noPassword && (
         <p
           style={{
             color: "red",
             fontSize: "14px",
           }}
         >
           Enter the password.
         </p>
       )}
       {passwordIncorrect && (
         <p
           style={{
             color: "red",
             fontSize: "14px",
           }}
         >
           The entered password is incorrect.
         </p>
       )}
     </div>
   </div>
 );
};

export default PasswordPromptDialog;

The last step, will be to call the PasswordPromptDialogcomponent in the layout.tsx file as follows:

  • in order to display this mode, we check the value of the environment variable MAINTENANCE_MODE,
  • if the maintenance mode is to be enabled, we verify the contents of the cookies to determine user authentication and then display our maintenance mode content — otherwise, the default page content is displayed.
import { cookies } from "next/headers";
import PasswordPromptDialog from "@/components/PasswordPromptDialog/PasswordPromptDialog";

import "@/styles/globals.scss";

export default async function RootLayout({
 children,
}: {
 children: React.ReactNode;
}) {
 const isMaintenanceMode = process.env.MAINTENANCE_MODE === "true";

 if (isMaintenanceMode) {
   const cookieStore = cookies();
   const loginCookies = cookieStore.get(
     `${process.env.PASSWORD_COOKIE_NAME!}`
   );
   const isLoggedIn = !!loginCookies?.value;

   if (!isLoggedIn) {
     return (
       <html lang="pl">
         <body>
           <PasswordPromptDialog />;
         </body>
       </html>
     );
   }
 }

 return (
   <html lang="pl">
     <body>{children}</body>
   </html>
 );
}

At the very end, just add the following environment variables to the project (.env) with fixed values:

MAINTENANCE_MODE=FALSE
PASSWORD_COOKIE_NAME=authorized
SEARCH_QUERY_NAME=password
PAGE_PASSWORD=YourPasswordForWebsite

Summary

Implementing maintenance mode in Next.js applications, contrary to appearances, is crucial when developing and managing a project. Although CMS systems, such as WordPress, offer a variety of plugins that allow you to obscure your site during maintenance work, implementing maintenance mode in Next.js may seem more challenging.

However, with the solution presented here, which relies on the use of cookies to store authentication information, creating maintenance mode becomes as simple an operation as configuring a plug-in in a CMS. Thanks to the use of cookies, we not only maintain flexibility and speed, but also increase the level of security by preventing JavaScript scripts from reading data (setting the httpOnly option).

In summary, the above solution allows quick and easy implementation of maintenance mode in Next.js applications, while giving the user full control over the appearance of the "veil" and authentication. In addition, the ability to quickly switch maintenance mode by changing the value of an environment variable makes our solution extremely flexible and adaptable to different design needs.

Other blog posts

Medusa vs Magento: Total cost of ownership

Magento, compared to Medusa, may lead to higher long-term costs due to its licensing model and the risk associated with the gradual decline in the popularity of the PHP language...

Medusa vs Magento: Performance comparison

This comparison is about seeing if Magento, with its new headless approach, can match the performance of platforms built to be headless from day one...

Tell us about your project

Got a project in mind? Let's make it happen!

By clicking “Send Message” you grant us, i.e., Rigby, consent for email marketing of our services as part of the communication regarding your project. You may withdraw your consent, for example via hello@rigbyjs.com.
More information
placeholder

Grzegorz Tomaka

Co-CEO & Co-founder

LinkedIn icon
placeholder

Jakub Zbaski

Co-CEO & Co-founder

LinkedIn icon