Documentation Index
Fetch the complete documentation index at: https://mintlify.com/vercel/next.js/llms.txt
Use this file to discover all available pages before exploring further.
Next.js fully supports building Single-Page Applications (SPAs). You can start with a strict SPA and progressively add server features as your needs grow.
What is a SPA?
A strict SPA:
- Serves one HTML file (
index.html)
- Handles all routing and data fetching in the browser with JavaScript
- Does not reload the full page on navigation
Next.js addresses the common pitfalls of strict SPAs: large initial JavaScript bundles and client-side data waterfalls.
Why use Next.js for SPAs?
- Automatic code splitting — Multiple HTML entry points per route, reducing bundle size
- Fast navigation —
next/link prefetches routes, providing instant transitions
- URL-persisted state — Route state is preserved in the URL for sharing and linking
- Progressive enhancement — Add React Server Components, Server Actions, and more as needed
Patterns
Data fetching with context and use()
Fetch data in a parent Server Component (or root layout) and pass the Promise to a Client Component via context. This starts data fetching on the server early, avoiding client waterfalls:
import { UserProvider } from './user-provider'
import { getUser } from './user' // server-side function
export default function RootLayout({ children }: { children: React.ReactNode }) {
let userPromise = getUser() // do NOT await
return (
<html lang="en">
<body>
<UserProvider userPromise={userPromise}>{children}</UserProvider>
</body>
</html>
)
}
'use client'
import { createContext, useContext, ReactNode } from 'react'
type User = any
type UserContextType = { userPromise: Promise<User | null> }
const UserContext = createContext<UserContextType | null>(null)
export function useUser(): UserContextType {
const context = useContext(UserContext)
if (context === null) throw new Error('useUser must be used within a UserProvider')
return context
}
export function UserProvider({
children,
userPromise,
}: {
children: ReactNode
userPromise: Promise<User | null>
}) {
return <UserContext.Provider value={{ userPromise }}>{children}</UserContext.Provider>
}
Unwrap the Promise in any Client Component with React’s use():
'use client'
import { use } from 'react'
import { useUser } from './user-provider'
export function Profile() {
const { userPromise } = useUser()
const user = use(userPromise) // suspends until resolved
return '...'
}
Data fetching with SWR
With SWR 2.3.0+ and React 19+, you can provide server-fetched data as SWR fallback without changing existing client code:
import { SWRConfig } from 'swr'
import { getUser } from './user'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<SWRConfig
value={{
fallback: {
'/api/user': getUser(), // not awaited
},
}}
>
{children}
</SWRConfig>
)
}
'use client'
import useSWR from 'swr'
export function Profile() {
const { data } = useSWR('/api/user', (url) => fetch(url).then((r) => r.json()))
return '...'
}
The fallback data is included in the initial HTML and immediately available to useSWR. SWR polling and revalidation still run client-side.
Rendering only in the browser
Disable prerendering for Client Components that depend on browser APIs:
import dynamic from 'next/dynamic'
const ClientOnlyComponent = dynamic(() => import('./component'), { ssr: false })
Shallow routing
Update the URL without triggering a full navigation using the native History API. These calls integrate with Next.js hooks like usePathname and useSearchParams:
'use client'
import { useSearchParams } from 'next/navigation'
export default function SortProducts() {
const searchParams = useSearchParams()
function updateSorting(sortOrder: string) {
const urlSearchParams = new URLSearchParams(searchParams.toString())
urlSearchParams.set('sort', sortOrder)
window.history.pushState(null, '', `?${urlSearchParams.toString()}`)
}
return (
<>
<button onClick={() => updateSorting('asc')}>Sort Ascending</button>
<button onClick={() => updateSorting('desc')}>Sort Descending</button>
</>
)
}
Using Server Actions in Client Components
Progressively replace API route boilerplate with Server Actions:
'use server'
export async function create() {}
'use client'
import { create } from './actions'
export function Button() {
return <button onClick={() => create()}>Create</button>
}
Static export
Generate a fully static site with automatic code splitting per route:
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
output: 'export',
}
export default nextConfig
After next build, the out/ folder contains separate HTML files per route.
Next.js server features are not supported with static exports. See static exports for the full list.
Migrating from Create React App or Vite
Next.js provides migration guides for existing SPAs: