How to Fix: ReferenceError: HTMLElement is not defined when running in dev

5 min read

Fixing ReferenceError: HTMLElement is not defined in Next.js dev mode

This error appears when code that expects a browser-only DOM API runs during server-side execution. In this case, HTMLElement exists in the browser, but it does not exist in the Node.js runtime that powers Next.js development rendering. The result is a crash as soon as the page is evaluated on the server.

Understanding the Root Cause

The issue happens because Next.js renders components in more than one environment. During npm run dev, part of your application is evaluated on the server first. If a package, component, or utility references HTMLElement, window, document, or other DOM globals at module scope or during initial render, the server throws:

ReferenceError: HTMLElement is not defined

Why? Because Node.js is not a browser. It has no DOM implementation by default, so objects like HTMLElement are undefined.

This commonly happens in one of these scenarios:

  • A third-party library touches HTMLElement as soon as it is imported.
  • A component checks instanceof HTMLElement during render instead of after mount.
  • Browser-only logic is placed outside useEffect.
  • A component meant for the client is still being evaluated by the server.

In the linked reproduction, the fix is not about changing HTMLElement itself. The real fix is to prevent that browser-dependent code from running during server rendering.

Step-by-Step Solution

The safest solution is to isolate the DOM-dependent code so it only runs on the client.

1. Identify the browser-only import or logic

Look for code using any of the following:

HTMLElement
window
document
customElements
navigator

If the error happens immediately on page load, the problem is often triggered at import time.

2. Move DOM access into a client-only component

If you are using the App Router, mark the component with use client.

'use client'

import { useEffect, useState } from 'react'

export default function MyClientComponent() {
  const [ready, setReady] = useState(false)

  useEffect(() => {
    setReady(true)
  }, [])

  if (!ready) return null

  return <div>Client-only content</div>
}

This ensures the browser-dependent behavior only starts after the component mounts.

3. Dynamically import the problematic component with SSR disabled

If a third-party library references HTMLElement during import, wrap that component using next/dynamic and disable SSR.

import dynamic from 'next/dynamic'

const BrowserOnlyComponent = dynamic(
  () => import('./BrowserOnlyComponent'),
  { ssr: false }
)

export default function Page() {
  return <BrowserOnlyComponent />
}

This is often the most effective fix when the error originates inside a package you do not control.

4. Guard direct DOM checks

If you need to reference HTMLElement, always guard it before use.

if (typeof window !== 'undefined' && typeof HTMLElement !== 'undefined') {
  console.log('Browser environment detected')
}

For instance checks:

const isElement =
  typeof HTMLElement !== 'undefined' && node instanceof HTMLElement

This prevents the server from evaluating a missing global.

5. Move top-level browser code into useEffect

Do not do this:

const el = document.getElementById('app')

Do this instead:

import { useEffect } from 'react'

useEffect(() => {
  const el = document.getElementById('app')
}, [])

useEffect runs only in the browser, so DOM access is safe there.

6. If the source is a third-party package, load it lazily

Sometimes even importing a package is enough to trigger the crash. In that case, defer the import until the client is ready.

'use client'

import { useEffect, useState } from 'react'

export default function LazyLibraryWrapper() {
  const [LibComponent, setLibComponent] = useState(null)

  useEffect(() => {
    let mounted = true

    import('some-browser-only-library').then((mod) => {
      if (mounted) {
        setLibComponent(() => mod.default)
      }
    })

    return () => {
      mounted = false
    }
  }, [])

  if (!LibComponent) return null

  return <LibComponent />
}

7. Verify the fix

After applying the change:

npm run dev
  1. Open the local app.
  2. Navigate to the failing route.
  3. Confirm the server no longer throws ReferenceError: HTMLElement is not defined.
  4. Check the browser console for hydration or client-only warnings.

If you want the shortest reliable approach for most Next.js cases, use this pattern:

import dynamic from 'next/dynamic'

const SafeComponent = dynamic(() => import('./SafeComponent'), {
  ssr: false,
})

export default function Page() {
  return <SafeComponent />
}

Then keep all DOM-related logic inside that component.

Common Edge Cases

  • Import side effects: Even if your component code looks safe, the imported library may reference HTMLElement at the top level.
  • Hydration mismatch: Disabling SSR can fix the crash, but if the client renders very different markup, you may hit hydration issues elsewhere.
  • App Router vs Pages Router: In the App Router, you may still need ‘use client’ even if the component feels interactive-only.
  • Type checks in utilities: Shared helper files are easy to overlook. A utility imported by both server and client code can still trigger the error.
  • Testing confusion: Some tests run in jsdom, where HTMLElement exists, but production or dev server rendering runs in Node.js, where it does not.
  • Transitive dependencies: The real source may be nested several imports deep, so the stack trace matters.

FAQ

Why does this happen in development if the app is meant for the browser?

Because Next.js dev mode still evaluates components on the server. Development does not remove server rendering behavior, so browser globals must still be protected.

Is checking typeof window !== ‘undefined’ always enough?

It helps in many cases, but not all. If the crash occurs during module import, the file may fail before your conditional check runs. In that case, use dynamic import with ssr: false or move the import into useEffect.

Should I disable SSR for the whole page?

No, not unless absolutely necessary. It is better to isolate only the browser-dependent component. That keeps the rest of the page compatible with server rendering and usually preserves performance and SEO better.

The core rule is simple: if code depends on HTMLElement, treat it as client-only code. Once you stop that logic from executing during server rendering, this Next.js development error disappears.

For deeper framework details, review the relevant Next.js documentation and compare your implementation with the linked reproduction repository.

Leave a Reply

Your email address will not be published. Required fields are marked *