nextui icon indicating copy to clipboard operation
nextui copied to clipboard

[BUG] - localStorage.getItem is not a function

Open JustNZ opened this issue 2 months ago • 5 comments

HeroUI Version

2.8.5

Describe the bug

I just started a new project with heroui init my-app and when installing all packages with pnpm and start it with pnpm run dev I can see the following error down below in the console.

However when I use pnpm run build and afterwards pnpm run start it works without any issues

(Use `node --trace-warnings ...` to show where the warning was created)
 ✓ Compiled / in 3.2s (4238 modules)
 ⨯ [TypeError: localStorage.getItem is not a function] {
  digest: '2082390734'
}
[TypeError: localStorage.getItem is not a function]
 ⨯ [TypeError: localStorage.getItem is not a function] { page: '/' }
(node:8684) Warning: `--localstorage-file` was provided without a valid path
(Use `node --trace-warnings ...` to show where the warning was created)
 â—‹ Compiling /_error ...
 ✓ Compiled /_error in 767ms (4569 modules)
TypeError: localStorage.getItem is not a function
    at new Promise (<anonymous>)
    at Document.getInitialProps (../../src/pages/_document.tsx:1197:15)
  1195 |    */
  1196 |   static getInitialProps(ctx: DocumentContext): Promise<DocumentInitialProps> {
> 1197 |     return ctx.defaultGetInitialProps(ctx)
       |               ^
  1198 |   }
  1199 |
  1200 |   render() {
TypeError: localStorage.getItem is not a function
    at new Promise (<anonymous>)
    at Document.getInitialProps (../../src/pages/_document.tsx:1197:15)
  1195 |    */
  1196 |   static getInitialProps(ctx: DocumentContext): Promise<DocumentInitialProps> {
> 1197 |     return ctx.defaultGetInitialProps(ctx)
       |               ^
  1198 |   }
  1199 |
  1200 |   render() {
 ⨯ TypeError: localStorage.getItem is not a function
    at new Promise (<anonymous>)
    at Document.getInitialProps (../../src/pages/_document.tsx:1197:15)
  1195 |    */
  1196 |   static getInitialProps(ctx: DocumentContext): Promise<DocumentInitialProps> {
> 1197 |     return ctx.defaultGetInitialProps(ctx)
       |               ^
  1198 |   }
  1199 |
  1200 |   render() {
 ⨯ TypeError: localStorage.getItem is not a function
    at new Promise (<anonymous>)
    at Document.getInitialProps (../../src/pages/_document.tsx:1197:15)
  1195 |    */
  1196 |   static getInitialProps(ctx: DocumentContext): Promise<DocumentInitialProps> {
> 1197 |     return ctx.defaultGetInitialProps(ctx)
       |               ^
  1198 |   }
  1199 |
  1200 |   render() {
TypeError: localStorage.getItem is not a function
    at new Promise (<anonymous>)
    at Document.getInitialProps (../../src/pages/_document.tsx:1197:15)
  1195 |    */
  1196 |   static getInitialProps(ctx: DocumentContext): Promise<DocumentInitialProps> {
> 1197 |     return ctx.defaultGetInitialProps(ctx)
       |               ^
  1198 |   }
  1199 |
  1200 |   render() {
 ⨯ unhandledRejection: TypeError: localStorage.getItem is not a function
    at new Promise (<anonymous>)
    at Document.getInitialProps (../../src/pages/_document.tsx:1197:15)
  1195 |    */
  1196 |   static getInitialProps(ctx: DocumentContext): Promise<DocumentInitialProps> {
> 1197 |     return ctx.defaultGetInitialProps(ctx)
       |               ^
  1198 |   }
  1199 |
  1200 |   render() {
 ⨯ unhandledRejection:  TypeError: localStorage.getItem is not a function
    at new Promise (<anonymous>)
    at Document.getInitialProps (../../src/pages/_document.tsx:1197:15)
  1195 |    */
  1196 |   static getInitialProps(ctx: DocumentContext): Promise<DocumentInitialProps> {
> 1197 |     return ctx.defaultGetInitialProps(ctx)
       |               ^
  1198 |   }
  1199 |
  1200 |   render() {
TypeError: localStorage.getItem is not a function
    at new Promise (<anonymous>)
TypeError: localStorage.getItem is not a function
    at new Promise (<anonymous>)
 ⨯ TypeError: localStorage.getItem is not a function
    at new Promise (<anonymous>)
[TypeError: localStorage.getItem is not a function] { page: '/' }
TypeError: localStorage.getItem is not a function
    at new Promise (<anonymous>)
 ⨯ unhandledRejection: TypeError: localStorage.getItem is not a function
    at new Promise (<anonymous>)
 ⨯ unhandledRejection:  TypeError: localStorage.getItem is not a function
    at new Promise (<anonymous>)
 GET / 500 in 6099ms
 GET / 500 in 228ms

Your Example Website or App

No response

Steps to Reproduce the Bug or Issue

  1. start new project with heroui init my-app
  2. install dependencies
  3. start dev with pnpm run dev
  4. open page
  5. see error in console

Expected behavior

Starting the dev server without issue

Screenshots or Videos

No response

Operating System Version

macOS

Browser

Chrome

JustNZ avatar Nov 09 '25 20:11 JustNZ

I'm not able to reproducible the issue. Are you using page template? Any changes made after initialising the project? Also can you share your node & pnpm version?

wingkwong avatar Nov 10 '25 05:11 wingkwong

I also did found the issue quiet odd but i tried it with the "original" nextjs template and that did work.

I just init the project with the heroui cli, install the dependencies and launch the dev server.

pnpm version: 10.20.0 node version: v25.1.0 npm version: 11.6.2

JustNZ avatar Nov 10 '25 08:11 JustNZ

I somehow sorted the issue. My next version was 15.3.1 and updating to latest (16.0.1) and following the steps down below did make it work again

  • pnpm upgrade next next-themes --latest
  • remove import { Link } from "@heroui/react"; from layout.txt
  • add use client to app/page.tsx and components/navbar.tsx

JustNZ avatar Nov 10 '25 08:11 JustNZ

I believe node 25 breaks something to do with local storage with an experimental feature. Upgrading to next 16 resolved my issue too.

coletaylor10 avatar Nov 10 '25 16:11 coletaylor10

Even if a component has 'use client' at the top, Next.js still initially renders it on the server, and only hydrates it in the browser afterward. This means the component is client-designated, but not client-executed until hydration, so any code that relies on browser-only APIs (like localStorage, window, document, ResizeObserver, etc.) will still throw errors during SSR.

In other words:

"use client" !== skip server-rendering

It only tells Next.js that the component must hydrate on the client, but it can still be pre-rendered on the server.

How to safely access browser-only APIs

You must wrap all browser-dependent logic inside useEffect or a runtime check:

useEffect(() => {
  const stored = localStorage.getItem("theme");
  // ...
}, []);

or:

if (typeof window !== "undefined") {
  localStorage.getItem("theme");
}

Key takeaway

'use client' ensures hydration on client, but does NOT disable server-side rendering.

If the component must never run on the server (rare, but valid), you need to lazy-load it dynamically:

const ClientOnly = dynamic(() => import("./Component"), { ssr: false });

MahdiTa97 avatar Dec 03 '25 09:12 MahdiTa97