[BUG] - localStorage.getItem is not a function
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
- start new project with
heroui init my-app - install dependencies
- start dev with
pnpm run dev - open page
- see error in console
Expected behavior
Starting the dev server without issue
Screenshots or Videos
No response
Operating System Version
macOS
Browser
Chrome
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?
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
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 clientto app/page.tsx and components/navbar.tsx
I believe node 25 breaks something to do with local storage with an experimental feature. Upgrading to next 16 resolved my issue too.
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 });