Type something to search...
Next.js useOffline

Next.js useOffline

Every network-dependent app eventually hits the same UX problem: a user's connection drops mid-session, a request silently fails, and the interface gives no indication of why nothing is happening. Spinners spin forever, buttons feel broken, and the user has no idea whether to wait, retry, or give up and reload. useOffline is Next.js's answer to the "what should the UI say right now" half of that problem — a single hook that tells a Client Component whether the browser currently has network connectivity, so you can render something honest instead of a frozen loading state.

It's a small API on its own, but it's the visible tip of a much larger connectivity-handling system Next.js ships alongside it. Understanding where the hook ends and that system begins is the key to using it well, so that's where this article starts.

What Problem This Actually Solves

Browsers have had navigator.onLine and the online/offline window events for years, and you could always wire up a useState plus a couple of event listeners yourself to track connectivity in a React app. useOffline isn't reinventing that primitive — it's exposing a value that Next.js itself is already tracking internally, because Next.js needs to know about connectivity to make a much bigger decision: whether to automatically retry a navigation, a prefetch, or a Server Action that failed because the network was unavailable.

That retry behavior lives behind the experimental.useOffline flag in next.config.js. Turning it on does two things at once: it enables automatic retry of blocked navigation/prefetch/Server Action requests when the connection comes back, and it makes the useOffline hook actually functional. Without the flag enabled, calling useOffline() will always return false, silently, with no warning. That's the single most important fact about this hook and the one most likely to bite someone the first time they reach for it.

module.exports = {
  experimental: {
    useOffline: true,
  },
};

Once the flag is on, the hook stops being a stub and starts reflecting real connectivity state.

Basic Usage

The hook takes no arguments and returns a single boolean:

"use client";

import { useOffline } from "next/offline";

export function OfflineStatus() {
  const isOffline = useOffline();
  return <div>{isOffline ? "Offline" : "Online"}</div>;
}

Two things about the import path and the component type are easy to get wrong the first time:

It comes from next/offline, not next/navigation or react. This is a Next.js-specific module, separate from the router hooks you might already be reaching for muscle-memory-style.

It only works in a Client Component. Like every other stateful hook that depends on browser events, useOffline needs 'use client' at the top of the file. Calling it from a Server Component will fail the same way any other hook call from a Server Component fails — this isn't a special restriction unique to useOffline, but it's worth stating plainly since connectivity feels like it should be a "request-time" concern that a Server Component could answer. It can't; only the browser knows if it's currently online.

The Return Value, Precisely

ValueMeaning
trueThe app is offline — a network request has failed, or the browser fired an offline event.
falseThe app is online, or is still rendering on the server.

That second row matters more than it looks. false is not just "definitely online" — it's also the value you'll see during server rendering and again briefly after hydration, before the client has had a chance to determine real connectivity state. If you're building anything where a false negative during that brief window would be visually jarring (say, an aggressive "You're offline!" banner that flashes on then off within a frame), you may want to debounce showing offline UI by a few hundred milliseconds rather than reacting to the very first true you see. The docs don't call this out explicitly, but it falls straight out of the return-value table once you think through the hydration timeline.

Pattern: A Persistent Offline Banner

The most common use case is a small, unobtrusive banner that appears when connectivity drops and disappears when it's restored:

"use client";

import { useOffline } from "next/offline";

export function OfflineBanner() {
  const isOffline = useOffline();

  if (!isOffline) {
    return null;
  }

  return (
    <div role="status" className="offline-banner">
      You are offline. Some content may be unavailable.
    </div>
  );
}

The role="status" attribute is worth keeping even though it's easy to skip — it means screen readers announce the banner's appearance as a live region update rather than requiring the user to stumble across it. Since this is exactly the kind of state change a screen reader user has no other way of discovering, this is one of the few places where adding an ARIA role is unambiguously worth the two extra characters.

Mount it once, at the root, so it's visible regardless of which route the user is on:

import { OfflineBanner } from "./components/offline-banner";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html>
      <body>
        <OfflineBanner />
        {children}
      </body>
    </html>
  );
}

Pattern: Explaining a Slow Loading State

The more interesting use case — and the one that actually connects useOffline to Next.js's broader connectivity story — is inside a loading.tsx file. When a user navigates to a route while offline, Next.js can still render the prefetched static shell instantly, but any dynamic content behind a <Suspense> boundary has to wait on a network request that isn't going anywhere. Without useOffline, that just looks like an ordinary loading spinner that never resolves. With it, you can tell the user the truth:

"use client";

import { useOffline } from "next/offline";

export default function Loading() {
  const isOffline = useOffline();

  return (
    <div>
      {isOffline ? "Waiting for connection to load this page..." : "Loading..."}
    </div>
  );
}

This is where the hook stops being a cosmetic nicety and starts being genuinely useful: a spinner that silently sits there for thirty seconds reads as a bug, but the same wait with an honest "waiting for connection" label reads as expected behavior, even though nothing about the underlying request changed. When connectivity is restored, the retry system built into the useOffline flag automatically re-fires the blocked request, and the dynamic content streams in without the user needing to manually refresh.

Where This Sits Relative to Other Loading States

It's worth being explicit about what useOffline is not a replacement for. loading.tsx already gives you a route-level loading UI for the normal case where data just takes time to fetch — useOffline only changes what that same file renders when the reason for the delay is connectivity rather than latency. You still need both: a baseline loading state for the common case, and an offline-aware branch for the specific case this hook detects. Treat it as an additional conditional inside your existing loading UI, not a separate mechanism you bolt on elsewhere.

Similarly, this hook has nothing to do with service workers, the Cache API, or true offline-first architectures where an app continues to function with no network at all. If you're building a Progressive Web App that needs to serve previously-visited pages while fully offline, that's a different, larger effort involving a service worker and a web app manifest — useOffline only tells you the connectivity state so you can react to it in the UI, not cache anything on your behalf.

Common Mistakes

Forgetting to enable the config flag. By far the most common failure mode: the hook is imported and used correctly, but experimental.useOffline was never set in next.config.js, so isOffline silently stays false forever and the feature appears to simply not work. There's no runtime warning for this — check the config first if the hook seems inert.

Treating the hook as a network speed indicator. useOffline answers a binary question — is there a connection at all — not "is the connection slow" or "did this specific request fail for a reason other than connectivity" (like a 500 error, or a validation failure). Don't wire generic error-handling logic through this hook; it will misreport plenty of failures that have nothing to do with being offline.

Rendering offline UI in a Server Component. Because the hook only works client-side, some reasonable-looking refactors — pulling the banner up into a layout that's currently a Server Component, say — will simply fail to compile once you try to call the hook there. Keep the offline-aware piece isolated to a small Client Component, as shown above, and let everything around it stay a Server Component.

Assuming broad, stable browser support. This is an experimental feature as of Next.js 16.3, introduced specifically to pair with the connectivity-retry system. Treat it the way you'd treat any experimental flag: fine to try in a side project or behind a feature flag, but confirm it still behaves the way you expect before leaning on it for anything user-facing in production, since experimental APIs are explicitly subject to change.

Key Takeaways

QuestionAnswer
What does useOffline return?A booleantrue when offline, false when online or during server rendering
Where can it be called?Only inside a Client Component ('use client')
What turns it on?The experimental.useOffline flag in next.config.js — without it, it always returns false
What's it usually paired with?An offline banner, or a connectivity-aware branch inside loading.tsx
What does it not do?Detect slow connections, distinguish error types, or provide offline-first caching on its own

useOffline is a small hook doing one honest job: telling your UI the truth about connectivity so it doesn't have to fake ignorance of a state Next.js already knows about internally. The real value shows up not in the banner pattern, which is straightforward, but in the loading-state pattern — using it to turn an indefinite, unexplained wait into a wait the user actually understands.

Tags :
Share :

Related Posts

Can Next.js Be Used with GraphQL?

Can Next.js Be Used with GraphQL?

Next.js and GraphQL are two powerful technologies that have gained significant traction in the web development community. Next.js, a React-based fram

Dive Deeper
How does Next.js differ from Create React App?

How does Next.js differ from Create React App?

In the world of modern web development, React.js has emerged as a dominant force due to its flexibility, performance, and extensive ecosystem. Two po

Dive Deeper
How does Next.js handle image optimization?

How does Next.js handle image optimization?

In modern web development, image optimization plays a critical role in enhancing user experience and improving site performance. Large, unoptimized i

Dive Deeper