Modal component with Next.js

In the last couple of years, Server Side Rendering(SSR) has become a big trend in modern web development. One of the most popular SSR solutions for React developers is Next.js and there are some common UI tasks that can be challenging when working in mixed server/client environment. In this tutorial, I will show you how to create a basic Modal component from scratch in a Next.js application.

Before we start

Important: This tutorial was updated for Next v13. If you are using older version you can refer to the Git repository of the old codebase where Next v10 was used(with Node v14).

First, we need to initiate a new Next.js project using npx or yarn.

npx create-next-app
# or
yarn create next-app

The main purpose of every modal is to appear on top of every other element on the page. For this reason, it’s a good practice to place it in a separated DOM node, outside the one that holds the rest of the UI. However, in a classical React SPA, the app renders inside a specified DOM node, so how do we In such scenarios, React’s API offers a feature called Portal. It allows components to render in a specific DOM node outside the root one that the application uses. It’s pretty easy to achieve in a SPA because we can modify the main index.html that holds all the rest of the app. However, Next.js application is very different from a SPA, so we don’t have such a file at our disposal. The way to modify the root DOM structure of our app is to use the _document.js file, which is provided by the library. You can read more about it in the official documentation. Please create this file inside the pages folder and add the following content:

import Document, { Html, Head, Main, NextScript } from "next/document";

class MainDocument extends Document {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx);
    return { ...initialProps };
  }

  render() {
    return (
      <Html>
        <Head />
        <body>
          <Main />
          <NextScript />
          {/*Below we add the modal wrapper*/}
          <div id="modal-root"></div>
        </body>
      </Html>
    );
  }
}

export default MainDocument;

Basically, we used the default code in the component and inserted the DOM node where our Modal will be rendered.

Create the Modal component

Now it’s time to focus on the Modal component itself. Let’s create components/Modal.js and add this code to it.

import React from "react";
import ReactDOM from "react-dom";

const Modal = ({ onClose, children, title }) => {
    const handleCloseClick = (e) => {
        e.preventDefault();
        onClose();
    };

    const modalContent = (
        <div className="modal-overlay">
            <div className="modal-wrapper">
                <div className="modal">
                    <div className="modal-header">
                        <a href="#" onClick={handleCloseClick}>
                            x
                        </a>
                    </div>
                    {title && <h1>{title}</h1>}
                    <div className="modal-body">{children}</div>
                </div>
            </div>
        </div>
    );

    return ReactDOM.createPortal(
        modalContent,
        document.getElementById("modal-root")
    );
};

export default Modal

And here is the styling, which I placed in styles/globals.css which should be imported to pages/_app.js :

.modal-wrapper {
  width: 500px;
  height: 600px;
}

.modal {
  background: white;
  height:100%;
  width:100%;
  border-radius: 15px;
  padding: 15px;
}

.modal-overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: rgba(0, 0, 0, 0.5);
}

.modal-body {
  padding-top: 10px;
}

.modal-header {
  display: flex;
  justify-content: flex-end;
  font-size: 25px;
}

There is no need to explain every line of CSS here, the most important thing to remember is to have an overlay(.modal-overlay), which should take up the whole screen and have absolute positioning. Inside we center the Modal element.

Here is how I used the newly created Modal component in pages/index.js:

import Modal from "src/components/Modal";
import {useState} from "react";


export default function Home() {
  const [showModal, setShowModal] = useState(false);

  return (
      <div>
        <button onClick={() => setShowModal(true)}>Open Modal</button>
        {showModal &&
            <Modal onClose={() => setShowModal(false)}>
                Hello from the modal!
            </Modal>
        }
      </div>
  )
}

Let’s test it now:

Simple modal with React and Next.js

Tadaa! It works as expected.

What next?

Make sure to look at the 2nd part of this tutorial – I have enhanced the modal and allow it to be closed when the user clicks outside of it.

I hope you enjoyed this tutorial. Check out my blog for more dev recipes.