Next.js - Shallow Routing
Next.js - Shallow Routing
Section titled “Next.js - Shallow Routing”Shallow routing is a feature specific to the Pages Router in Next.js. It allows you to change the URL path (including query parameters) without re-running data fetching methods like getServerSideProps, getStaticProps, or getInitialProps.
This is useful when you only need to update the URL for state that is already available on the client-side, such as tracking filters, tabs, or pagination state in the URL, without triggering a full server-side data refetch.
Note: The concept of shallow routing is less relevant in the App Router due to its different architecture focusing on Server Components and React Server Actions, where URL updates often naturally trigger server-side logic or specific component re-renders as needed.
To perform shallow routing in the Pages Router, you use the router.push or router.replace methods from the useRouter hook, passing the shallow: true option.
Let’s update a sample page (e.g., pages/index.js) to demonstrate shallow routing. Assume this page uses getServerSideProps to fetch some initial data.
import { useRouter } from 'next/router';import Head from 'next/head';import { useEffect, useState } from 'react';
function HomePage(props) { const router = useRouter(); // Example client-side state that we want to reflect in the URL const [counter, setCounter] = useState(0);
// Update counter state based on URL query parameter on initial load/route change useEffect(() => { if (router.query.counter) { setCounter(parseInt(router.query.counter, 10) || 0); } }, [router.query.counter]);
const handleIncrement = () => { const newCounter = counter + 1; setCounter(newCounter); // Update the URL query parameter without re-running getServerSideProps router.push(`/?counter=${newCounter}`, undefined, { shallow: true }); };
return ( <> <Head> <title>Welcome to Next.js! (Shallow Routing)</title> </Head> <div> <h1>Welcome to Next.js!</h1> <p>Demonstrating Shallow Routing</p> <p>Initial Stars Count (from getServerSideProps): {props.stars}</p> <p>Client-side Counter: {counter}</p> <button onClick={handleIncrement}>Increment Counter (Shallow Update URL)</button> <br /> <p>Current URL Query: {JSON.stringify(router.query)}</p> <p>(Check your browser console logs to see if getServerSideProps runs on button click)</p> </div> </> );}
export async function getServerSideProps(context) { console.log('getServerSideProps executed!'); // Log to see when this runs // Example: Fetch data that should only load on initial navigation or full refresh const res = await fetch('https://api.github.com/repos/vercel/next.js'); const json = await res.json(); return { props: { stars: json.stargazers_count || 'N/A' }, };}
export default HomePage;In this example:
- We use
getServerSidePropsto fetch GitHub stars, logging to the console when it runs. - We maintain a client-side
counterstate. - An
useEffecthook synchronizes thecounterstate if a?counter=query parameter exists in the URL. - The
handleIncrementfunction updates the client state and then usesrouter.pushwith{ shallow: true }to append the new counter value to the URL’s query string.
Running the Development Server
Section titled “Running the Development Server”Start the server if it’s not running:
npm run dev
# Example Output:# ready - started server on 0.0.0.0:3000, url: http://localhost:3000# event - compiled client and server successfully ...Verify Output
Section titled “Verify Output”-
Open
http://localhost:3000in your browser. Check your server console (where you rannpm run dev). You should see the ‘getServerSideProps executed!’ log message. -
Click the ‘Increment Counter (Shallow Update URL)’ button multiple times.
-
Observe the following:
- The counter value displayed on the page increases.
- The URL in the browser’s address bar updates (e.g.,
/?counter=1,/?counter=2, etc.). - Check your server console again. You should not see the ‘getServerSideProps executed!’ message logged after clicking the button. This confirms that the data fetching function did not re-run, thanks to
shallow: true.
-
Refresh the page (e.g., when the URL is
/?counter=3). Now, you will see ‘getServerSideProps executed!’ in the server console because a full page load occurred. TheuseEffecthook will then read thecounter=3fromrouter.queryand set the initial client-side counter state accordingly.