How to create a portal in Next Js

Last updated : Jul 30, 2023 12:00 AM

Creating a portal in Next Js differs from creating a portal in React Js. React Js generally run on the client side, while Next Js run on the server side. My modal renders on the client side. Therefore, in Next Js, I must ensure that the DOM is ready and the container is available to mount the portal.

Here are the steps to create a portal in Next Js.

  1. Create a _document.js file
  2. Create a DOM node in the _document.js to mount the portal
  3. Mount the portal to the DOM node

I use Typescript. You should too if possible. So my file extension is tsx.

1. Create a _document.js file

My _document.tsx resides in the pages folder. The _document.tsx is the equivalent of the index.html in React Js. If you don't have a _document.tsx, create one inside the pages folder.

_document.tsxDescription
import { Html, Head, Main, NextScript } from 'next/document'
export default function Document() {
  return (
    <Html>
      <Head />
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  )
}

2. Create a div element in the _document.js

I created a div

in the _document.tsx. That is where I mount my portal.

div element in the _document.jsDescription
import { Html, Head, Main, NextScript } from 'next/document'
export default function Document() {
  return (
    <Html>
      <Head />
      <body>
        <Main />
        <div id="portal" />
        <NextScript />
      </body>
    </Html>
  )
}

3. Mount the portal to the DOM node

Not it is the time to create a component to create a portal and mount it to the DOM node.

Portal componentDescription
import { useRef, useEffect, useState, ReactNode } from 'react'
import { createPortal } from 'react-dom'
import styles from "./Overlay.module.css"

interface PortalProps {
    children: ReactNode
}

export const Portal = (props: OverlayProps) => {
  const ref = useRef<Element | null>(null)
  const [mounted, setMounted] = useState(false)
  
  useEffect(() => {
    ref.current = document.querySelector<HTMLElement>("#portal")
    setMounted(true)
  }, [])

  return (mounted && ref.current) ? createPortal(<div className={styles.overlay}>{props.children}</div>, ref.current) : null
}

Here are the styles to go with the portal

StylesDescription
.overlay {
    display: block;
    position: fixed;
    padding-top: 100px;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    overflow: auto;
    background-color: rgba(0,0,0,0.4);
    z-index: 2;
}

I am doing a few things differently here. I use a ref to hold the DOM node. That ensures I get the same reference to the Node in every re-render.

I use the state variable mounted to ensure my component is mounted and usEffect is executed. Then I use both those conditions before creating the portal. That is important.

The usage of this component is easy.

UsageDescription
<Portal>
  <p>Next Js Portal by LearnBestCoding</p>
</Portal>
Lance

By: Lance

Hi, I'm Lance Raney, a dedicated Fullstack Developer based in Oklahoma with over 15 years of exp

Read more...