|
| 1 | +--- |
| 2 | +title: Offline navigations |
| 3 | +description: Learn how to enable offline hard reloads and navigation recovery with Cache Components. |
| 4 | +nav_title: Offline navigations |
| 5 | +version: experimental |
| 6 | +related: |
| 7 | + title: API Reference |
| 8 | + description: Learn more about the APIs used in this guide. |
| 9 | + links: |
| 10 | + - app/api-reference/config/next-config-js/offlineNavigations |
| 11 | + - app/api-reference/functions/use-offline |
| 12 | + - app/api-reference/config/next-config-js/cacheComponents |
| 13 | +--- |
| 14 | + |
| 15 | +Offline navigations help an App Router app keep working when a user reloads or opens a same-origin route while their browser is offline. |
| 16 | + |
| 17 | +When a route has been loaded or prefetched, Next.js can restore that route from browser-private navigation data instead of showing the browser's network error page. If the browser does not have everything it needs for the route, the app shows a clear offline miss. |
| 18 | + |
| 19 | +Use offline navigations for read-only route recovery. It does not make every route available offline, sync mutations, or replace a custom Progressive Web App strategy. |
| 20 | + |
| 21 | +## Enable offline navigations |
| 22 | + |
| 23 | +Offline navigations require [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents): |
| 24 | + |
| 25 | +```ts filename="next.config.ts" highlight={4,6} |
| 26 | +import type { NextConfig } from 'next' |
| 27 | + |
| 28 | +const nextConfig: NextConfig = { |
| 29 | + cacheComponents: true, |
| 30 | + experimental: { |
| 31 | + offlineNavigations: true, |
| 32 | + }, |
| 33 | +} |
| 34 | + |
| 35 | +export default nextConfig |
| 36 | +``` |
| 37 | + |
| 38 | +```js filename="next.config.js" highlight={3,5} |
| 39 | +/** @type {import('next').NextConfig} */ |
| 40 | +const nextConfig = { |
| 41 | + cacheComponents: true, |
| 42 | + experimental: { |
| 43 | + offlineNavigations: true, |
| 44 | + }, |
| 45 | +} |
| 46 | + |
| 47 | +module.exports = nextConfig |
| 48 | +``` |
| 49 | + |
| 50 | +You do not need to add your own service worker to enable this behavior. `offlineNavigations` configures the required router features automatically. |
| 51 | + |
| 52 | +## Show offline state |
| 53 | + |
| 54 | +Use [`useOffline`](/docs/app/api-reference/functions/use-offline) from `next/offline` to show whether the app is currently offline: |
| 55 | + |
| 56 | +```tsx filename="app/offline-status.tsx" switcher |
| 57 | +'use client' |
| 58 | + |
| 59 | +import { useOffline } from 'next/offline' |
| 60 | + |
| 61 | +export function OfflineStatus() { |
| 62 | + const isOffline = useOffline() |
| 63 | + return <p>{isOffline ? 'Offline' : 'Online'}</p> |
| 64 | +} |
| 65 | +``` |
| 66 | + |
| 67 | +```jsx filename="app/offline-status.js" switcher |
| 68 | +'use client' |
| 69 | + |
| 70 | +import { useOffline } from 'next/offline' |
| 71 | + |
| 72 | +export function OfflineStatus() { |
| 73 | + const isOffline = useOffline() |
| 74 | + return <p>{isOffline ? 'Offline' : 'Online'}</p> |
| 75 | +} |
| 76 | +``` |
| 77 | + |
| 78 | +Render the indicator from a shared layout so it is visible after a hard reload: |
| 79 | + |
| 80 | +```tsx filename="app/layout.tsx" switcher |
| 81 | +import { OfflineStatus } from './offline-status' |
| 82 | + |
| 83 | +export default function RootLayout({ |
| 84 | + children, |
| 85 | +}: { |
| 86 | + children: React.ReactNode |
| 87 | +}) { |
| 88 | + return ( |
| 89 | + <html lang="en"> |
| 90 | + <body> |
| 91 | + <OfflineStatus /> |
| 92 | + {children} |
| 93 | + </body> |
| 94 | + </html> |
| 95 | + ) |
| 96 | +} |
| 97 | +``` |
| 98 | + |
| 99 | +```jsx filename="app/layout.js" switcher |
| 100 | +import { OfflineStatus } from './offline-status' |
| 101 | + |
| 102 | +export default function RootLayout({ children }) { |
| 103 | + return ( |
| 104 | + <html lang="en"> |
| 105 | + <body> |
| 106 | + <OfflineStatus /> |
| 107 | + {children} |
| 108 | + </body> |
| 109 | + </html> |
| 110 | + ) |
| 111 | +} |
| 112 | +``` |
| 113 | + |
| 114 | +## Make routes available offline |
| 115 | + |
| 116 | +Offline navigations work for routes the current browser has already loaded or prefetched. The most reliable way to make an important route available later is to prefetch it before the user needs it offline. |
| 117 | + |
| 118 | +Use `<Link>` prefetching or [`router.prefetch`](/docs/app/api-reference/functions/use-router#userouter) for routes users are likely to need later: |
| 119 | + |
| 120 | +```tsx filename="app/prefetch-report-button.tsx" switcher |
| 121 | +'use client' |
| 122 | + |
| 123 | +import { useRouter } from 'next/navigation' |
| 124 | + |
| 125 | +export function PrefetchReportButton() { |
| 126 | + const router = useRouter() |
| 127 | + |
| 128 | + return ( |
| 129 | + <button onClick={() => router.prefetch('/reports/weekly')}> |
| 130 | + Make weekly report available offline |
| 131 | + </button> |
| 132 | + ) |
| 133 | +} |
| 134 | +``` |
| 135 | + |
| 136 | +```jsx filename="app/prefetch-report-button.js" switcher |
| 137 | +'use client' |
| 138 | + |
| 139 | +import { useRouter } from 'next/navigation' |
| 140 | + |
| 141 | +export function PrefetchReportButton() { |
| 142 | + const router = useRouter() |
| 143 | + |
| 144 | + return ( |
| 145 | + <button onClick={() => router.prefetch('/reports/weekly')}> |
| 146 | + Make weekly report available offline |
| 147 | + </button> |
| 148 | + ) |
| 149 | +} |
| 150 | +``` |
| 151 | + |
| 152 | +If the route has everything it needs, a hard reload or document navigation can render it while offline. If route data is missing, the app shows an offline miss state instead of guessing or rendering partial UI. |
| 153 | + |
| 154 | +## What to expect |
| 155 | + |
| 156 | +Offline navigations are browser-private and same-origin: |
| 157 | + |
| 158 | +- A route can be restored only in the browser profile where it was loaded or prefetched. |
| 159 | +- Dynamic routes can be restored after the browser has learned and cached the matching route. |
| 160 | +- A route can miss offline if it was never loaded, was not fully prefetched, or was invalidated. |
| 161 | +- Offline recovery also needs the JavaScript and CSS assets for the current build to be available in the browser cache. If those assets were not cached, were evicted, or belong to another deployment, the route cannot boot offline. |
| 162 | +- Browser storage can be cleared by users, browser settings, storage pressure, or DevTools. If storage is cleared, offline recovery may not start. |
| 163 | +- Mutations, Server Actions, Route Handlers, and POST requests still require the network. |
| 164 | + |
| 165 | +Offline navigations do not cache arbitrary `fetch` responses, cookies, local storage, or your app's own data stores. If your app stores data separately, continue to manage that storage yourself. |
| 166 | + |
| 167 | +## Keep offline data fresh |
| 168 | + |
| 169 | +Offline navigations follow normal App Router cache invalidation. Use the same APIs you already use to refresh client and server data: |
| 170 | + |
| 171 | +- [`router.refresh()`](/docs/app/api-reference/functions/use-router#userouter) refreshes dynamic route data and invalidates offline navigation data for the affected route. |
| 172 | +- [`refresh()`](/docs/app/api-reference/functions/refresh) refreshes client router data from a Server Action. |
| 173 | +- [`updateTag()`](/docs/app/api-reference/functions/updateTag) and [`revalidatePath()`](/docs/app/api-reference/functions/revalidatePath) invalidate server data and any offline navigation data that depends on it. |
| 174 | +- Mutating cookies in a Server Action invalidates the client router cache, including offline navigation data. |
| 175 | + |
| 176 | +For sign out, account switches, workspace switches, or client-only auth changes, use the same refresh or revalidation API you would use for the online app. Offline navigations mirror those invalidations instead of exposing a separate offline-specific reset API. |
| 177 | + |
| 178 | +## Test offline navigations |
| 179 | + |
| 180 | +Offline navigations do not run in `next dev`. Test with a production build: |
| 181 | + |
| 182 | +```bash |
| 183 | +next build |
| 184 | +next start |
| 185 | +``` |
| 186 | + |
| 187 | +Then in a browser: |
| 188 | + |
| 189 | +1. Visit the route online. |
| 190 | +2. Prefetch or navigate to any routes you expect to restore offline. |
| 191 | +3. Open DevTools and switch the network to **Offline**. |
| 192 | +4. Reload the route or open a cached route URL. |
| 193 | + |
| 194 | +If the route has valid offline navigation data, it renders and [`useOffline`](/docs/app/api-reference/functions/use-offline) returns `true`. If required data is missing, the app shows an offline miss state. |
| 195 | + |
| 196 | +> **Good to know**: |
| 197 | +> |
| 198 | +> - `localhost` is treated as a secure context, so service worker registration works with `next start` locally. |
| 199 | +> - Static export integration is not included in this experimental API. |
| 200 | +> - For push notifications, background sync, offline mutations, or custom asset caching, use a custom PWA strategy. |
0 commit comments