How to create a portal in Next Js

Last updated : September 20, 2022

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.

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 <div id="portal" /> in the _document.tsx. That is where I mount my portal.

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.

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

.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.

<Portal>
  <p>Next Js Portal by LearnBestCoding</p>
</Portal>
L Raney
By: L Raney
Lance is a software engineer with over 15 years of experience in full-stack software development.
Read more...

Comments are disabled

No Comments