Modal component with Next.js
In the last 2 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. In this tutorial, I will show you how to create a basic Modal component from scratch in a Next.js application.
Before we start
First, we need to initiate a new Next.js project using npx or yarn.
npx create-next-app
# or
yarn create next-app
After you enter your project name and all the dependencies are installed, we will install styled-components which we will use for styling. Feel free to use any other styling option for the application.
npm install -S styled-components
Setup the Next.js basic template and styling
The main purpose of every modal is to appear on top of the rest of the page. For this reason, it’s generally 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 in which 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, { useEffect, useRef, useState } from "react";
import ReactDOM from "react-dom";
import styled from "styled-components";
function Modal() {
const [isBrowser, setIsBrowser] = useState(false);
useEffect(() => {
setIsBrowser(true);
}, []);
if (isBrowser) {
return ReactDOM.createPortal(
<div>Hello from modal</div>,
document.getElementById("modal-root")
);
} else {
return null;
}
}
export default Modal;
Looks weird, right? Yes, it does. Working in SSR environment can be tricky sometimes. Let me explain what I’ve done here.
On the initial load of the page, the rendering happens on the server, where the window.document object is not available, so we will get an error that it is undefined. So we need to make sure that, we use the document object safely only in the browser environment. The easiest way to achieve this is by adding isBrowser boolean and set it to true in the initial render of the component. This can be done using the useEffect hook. We also need to save the reference to our modal-root DOM object somewhere, so I’ve created a ref for that. Then, underneath I conditionally create the portal depending on whether the code executes in the browser or not. The first argument of the createPortal function is the root component and the second argument is the reference to the DOM object where it will be rendered.
Now that we have the basic structure for the component, let’s add some the styling and basic logic.
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
import styled from "styled-components";
const Modal = ({ show, onClose, children, title }) => {
const [isBrowser, setIsBrowser] = useState(false);
useEffect(() => {
setIsBrowser(true);
}, []);
const handleCloseClick = (e) => {
e.preventDefault();
onClose();
};
const modalContent = show ? (
<StyledModalOverlay>
<StyledModal>
<StyledModalHeader>
<a href="#" onClick={handleCloseClick}>
x
</a>
</StyledModalHeader>
{title && <StyledModalTitle>{title}</StyledModalTitle>}
<StyledModalBody>{children}</StyledModalBody>
</StyledModal>
</StyledModalOverlay>
) : null;
if (isBrowser) {
return ReactDOM.createPortal(
modalContent,
document.getElementById("modal-root")
);
} else {
return null;
}
};
const StyledModalBody = styled.div`
padding-top: 10px;
`;
const StyledModalHeader = styled.div`
display: flex;
justify-content: flex-end;
font-size: 25px;
`;
const StyledModal = styled.div`
background: white;
width: 500px;
height: 600px;
border-radius: 15px;
padding: 15px;
`;
const StyledModalOverlay = styled.div`
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);
`;
export default Modal;
There is no need to explain every line of CSS here, the most important thing to remember is to have an overlay(StyledModalOverlay), which should take up the whole screen and have absolute positioning. Inside we center the Modal element(StyledModal). Of course, we also need some basic open/close functionality, so I’ve added the show and onClose props which will help us control the Modal from the parent component.
Here is how I used the newly created Modal component in pages/index.js:
import React, { useState } from "react";
import Modal from '../components/Modal'
export default function Home() {
const [showModal, setShowModal] = useState(false);
return (
<div>
<button onClick={() => setShowModal(true)}>Open Modal</button>
<Modal
onClose={() => setShowModal(false)}
show={showModal}
>
Hello from the modal!
</Modal>
</div>
)
}
Let’s test it now:

Tadaa! It works as expected.
What next?
Now our simple Modal component is fine but it can be improved further. In the next tutorial, I will make it close when the user clicks outside of it.
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>
)
}
I hope you enjoyed this tutorial. Check out my blog for more dev recipes.