How to Use Router Push Nextjs to Pass State Between Pages
Use the router.push method from next/router with a URL object containing a query property to serialize state into the URL, then deserialize it on the destination page using useRouter().query.
The router push nextjs pattern is essential for programmatic navigation with data transfer in React applications. In the vercel/next.js repository, the client-side router implementation in packages/next/src/client/router.ts exposes the push method that forwards navigation requests to the internal router reducer. This architecture ensures that state passed via query parameters persists across both client-side transitions and server-side rendering contexts defined in packages/next/src/server/lib/router-server.ts.
Understanding Router Push Nextjs Architecture
The Next.js router implementation spans several critical files that handle state transfer. When you invoke router.push(url, as?, options?), the call flows through packages/next/src/client/router.ts into the router reducer at packages/next/src/client/components/router-reducer/router-reducer.ts. This reducer updates the shared router context defined in packages/next/src/shared/lib/router-context.shared-runtime.ts, which is consumed by the useRouter() hook on both client and server sides.
The core data structures reside in packages/next/src/shared/lib/router/router.ts, ensuring that query parameters remain accessible during server-side rendering (SSR) and static site generation (SSG). This design guarantees that state encoded in the URL survives hard reloads and direct URL access.
Method 1: Pass State via URL Query Parameters
Encoding state in the URL query string is the most reliable method for router push nextjs operations. This approach works consistently across server-rendered pages, client-side navigations, and hard reloads.
Encoding State on the Origin Page
Serialize complex objects using JSON.stringify and pass them through the query property of the URL object:
// pages/first.tsx
import { useRouter } from 'next/router';
import { useCallback } from 'react';
export default function FirstPage() {
const router = useRouter();
const goToSecond = useCallback(() => {
const data = { name: 'Alice', age: 30 };
router.push(
{
pathname: '/second',
query: { payload: JSON.stringify(data) },
},
undefined,
{ shallow: false }
);
}, [router]);
return (
<div>
<h1>First Page</h1>
<button onClick={goToSecond}>Go to Second Page (with state)</button>
</div>
);
}
Source reference: router.push implementation – packages/next/src/client/router.ts
Decoding State on the Destination Page
Access the query parameters using useRouter and deserialize the data:
// pages/second.tsx
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
export default function SecondPage() {
const { query } = useRouter();
const [payload, setPayload] = useState<{ name: string; age: number } | null>(null);
useEffect(() => {
if (query.payload) {
try {
setPayload(JSON.parse(query.payload as string));
} catch {
// handle malformed data
}
}
}, [query.payload]);
return (
<div>
<h1>Second Page</h1>
{payload ? (
<p>
Received name: <strong>{payload.name}</strong>, age: <strong>{payload.age}</strong>
</p>
) : (
<p>No data received.</p>
)}
</div>
);
}
Source reference: useRouter reads from the shared router context – packages/next/src/shared/lib/router-context.shared-runtime.ts
Method 2: Shallow Routing with Router Push Nextjs
When you need to update the URL and component props without re-fetching data, use the shallow option. This modifies the query string without triggering getServerSideProps, getStaticProps, or getInitialProps.
router.push(
{ pathname: '/dashboard', query: { tab: 'settings' } },
undefined,
{ shallow: true }
);
Source reference: shallow flag handling – packages/next/src/client/components/router-reducer/router-reducer.ts
Method 3: Masking URLs with the As Parameter
The second argument to router.push (the as parameter) allows you to mask the actual URL shown in the browser address bar while preserving the underlying query parameters for the page component.
router.push(
{ pathname: '/profile/[id]', query: { id: '123', ref: 'email' } },
'/profile/123',
{ shallow: false }
);
In this example, the user sees /profile/123 in the address bar, but the page receives query.id as '123' and query.ref as 'email'. The underlying data is stored in the router context defined in packages/next/src/shared/lib/router/router.ts.
Source reference: "as" URL handling – packages/next/src/client/router.ts
Summary
- Query parameters are the most reliable method for
router push nextjsstate transfer, persisting across hard reloads and SSR/SSG contexts. - Serialization is required for complex objects; use
JSON.stringifybefore passing toqueryandJSON.parsewhen reading viauseRouter().query. - Shallow routing updates the URL without data refetching, controlled by the
shallow: trueoption processed inpackages/next/src/client/components/router-reducer/router-reducer.ts. - URL masking via the
asparameter hides query parameters from the address bar while maintaining access to them in the router context frompackages/next/src/shared/lib/router-context.shared-runtime.ts.
Frequently Asked Questions
Can I pass complex objects directly through router push nextjs without serializing?
No, the Next.js router only accepts serializable values in the query object. According to the implementation in packages/next/src/shared/lib/router/router.ts, query parameters are encoded into the URL string, which requires primitive values or strings. For complex objects, you must use JSON.stringify before passing the data and JSON.parse when retrieving it with useRouter().query.
Does shallow routing affect getServerSideProps or getStaticProps?
Yes, shallow routing specifically prevents getServerSideProps, getStaticProps, and getInitialProps from running. When you set shallow: true in the options object passed to router.push, the router reducer in packages/next/src/client/components/router-reducer/router-reducer.ts updates the URL and component props without triggering data fetching methods, making it ideal for UI state changes that don't require new server data.
How does the Next.js router handle state during server-side rendering?
During server-side rendering, the router hydrates query parameters from the incoming request URL. The server-side implementation in packages/next/src/server/lib/router-server.ts ensures that query strings are parsed and made available to the router context. When using router push nextjs with query parameters, the same data is accessible on both the server (during SSR) and the client (during hydration), ensuring consistency across rendering modes.
What is the difference between using router.push and the Link component for state transfer?
Both router.push and the Link component utilize the same underlying router context from packages/next/src/shared/lib/router-context.shared-runtime.ts, but router.push is imperative while Link is declarative. Use router.push when you need to programmatically navigate after business logic (like form submission) or when you need to pass complex serialized state. Use Link for static navigation where the destination is known at render time, though you can still pass query parameters via the href object in both approaches.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →