26

Trying to create an xterm react component in Next.js I got stuck as I'm not able to get over an error message I've never got before.

I'm trying to import a npm client-side module called xterm, but if I add the import line the application crashes.

import { Terminal } from 'xterm'

The error reads Server Error... ReferenceError: self is not defined and then shows this chunk of code as Source

module.exports = require("xterm");

According to some research I did, this has to do with Webpack and could be helped if something like this was done:

output: {
  globalObject: 'this'
}

Would you know how to fix this?

3 Answers 3

87

The error occurs because the library requires Web APIs to work, which are not available when Next.js pre-renders the page on the server-side.

In your case, xterm tries to access the window object which is not present on the server. The solution is to avoid loading xterm on the server and dynamically import it so it gets loaded on the client-side only.

There are a couple of ways to achieve this in Next.js.


#1 Using dynamic import() inside useEffect

Move the import to your component's useEffect, then dynamically import the library and add your logic there.

useEffect(() => {
    const initTerminal = async () => {
        const { Terminal } = await import('xterm')
        const term = new Terminal()
        // Add logic with `term`
    }
    initTerminal()
}, [])

#2 Using next/dynamic with ssr: false

Create a component where you add the xterm logic.

// components/terminal-component
import { Terminal } from 'xterm'

function TerminalComponent() {
    const term = new Terminal()
    // Add logic around `term`
    return <></>
}

export default TerminalComponent

Then dynamically import that component when using it.

import dynamic from 'next/dynamic'

const TerminalComponent = dynamic(() => import('<path-to>/components/terminal-component'), {
    ssr: false
})

As an alternative, you could add the logic directly when dynamically importing the library with next/dynamic to avoid having an extra file for it.

import dynamic from 'next/dynamic'

const Terminal = dynamic(
    {
        loader: () => import('xterm').then((mod) => mod.Terminal),
        render: (props, Terminal) => {
            const term = new Terminal()
            // Add logic with `term`
            return <></>
        }
    },
    {
        ssr: false
    }
)
9
  • 2
    I've solved it by doing an import { Terminal } from 'xterm' in my component and then importing that <Component /> with the dynamic() function in the parent page. I could mark your answer as correct if you mention this detail. Commented Feb 8, 2021 at 20:23
  • 1
    Ah right, I assumed Terminal was a component - Next.js dynamic must return a component. I'll update my answer. Commented Feb 8, 2021 at 20:46
  • 1
    You have no idea how your answer saved me.. thanks... Commented Apr 4, 2023 at 10:57
  • 1
    @crollywood Using use client does not make the component only render on the client-side. It simply means the component will also get rendered on the client in addition to the server-side rendering. As opposed to Server Components (components that do not use use client), where the rendering only occurs on the server. Commented Sep 8, 2023 at 12:44
  • 1
    I've crafted a complete answer in another slightly related question to handle other situation where the library can be imported server-side, but can't be used there: stackoverflow.com/a/77556387/5513532
    – Eric Burel
    Commented Dec 28, 2023 at 14:15
0

I wanted to get the default export and found it helpful to add the import with the default property like so:

const [doStuff, setDoStuff] = useState(false);

useEffect(() => {
 const exportFileAsPDF = async () => {
        const {default: html2pdf} = await import('html2pdf.js');

        if (typeof window === "undefined") return
        if (typeof document === "undefined") return

        if (content && html2pdf) {
           // use library
           HTML_CONTENT = ""
           pdfOptions = {}
           html2pdf().from(HTML_CONTENT).set(pdfOptions).save();
           setDoStuff(false)
          }
      }

    if (doStuff){
        exportFileAsPDF()
    }
}, [doStuff])

Not the answer you're looking for? Browse other questions tagged or ask your own question.