App feels unresponsive when navigating between pages (App Router)
Unanswered
Western Screech-Owl posted this in #help-forum
Western Screech-OwlOP
Using Next.js App Router, I’m noticing that any kind of navigation — whether via
What happens:
- I click a link or button.
- A request is made to the server (?_rsc=...).
- Only after the response comes back does the new page render.
I expected the transition to be instant — UI update first, data load later (ideally using
Is this normal with App Router? Or is there a recommended pattern to get faster, more SPA-like transitions?
I’m new to Next.js, just trying to rule out a skill issue, thanks.
<Link>
, router.push()
, or even layout-level redirects — feels delayed.What happens:
- I click a link or button.
- A request is made to the server (?_rsc=...).
- Only after the response comes back does the new page render.
I expected the transition to be instant — UI update first, data load later (ideally using
<Suspense>
or client-side fetching on the page). But the whole experience feels blocked by the server.Is this normal with App Router? Or is there a recommended pattern to get faster, more SPA-like transitions?
I’m new to Next.js, just trying to rule out a skill issue, thanks.
8 Replies
American Foxhound
this is expected behavior for server-rendered routes in the App Router. The trade-off is better SEO and initial load performance, but it can feel slower for client-side navigation compared to a pure client-side app.
You cna enable client side fetching using useEffect
'use client';
import { useEffect, useState } from "react";
export default function DataComponent() {
const [data, setData] = useState(null);
useEffect(() => {
fetch("/api/data")
.then((res) => res.json())
.then(setData);
}, []);
return data ? <div>{data.message}</div> : <div>Loading...</div>;
}
import { useEffect, useState } from "react";
export default function DataComponent() {
const [data, setData] = useState(null);
useEffect(() => {
fetch("/api/data")
.then((res) => res.json())
.then(setData);
}, []);
return data ? <div>{data.message}</div> : <div>Loading...</div>;
}
American black bear
If you have some of the following happening on server:
1. Heavy computation
2. Requests not processed in parallel
3. Requests bouncing between database and server multiple times
you are going to see latency in the initial page loads. You can fix this by doing one or more of the following:
1. Cache heavy computation (if possible)
2. Cache db queries and api responses (if possible)
3. Fetch data in parallel using
4. Add
5. Fetch non relevant data to initial page load and SEO client side
1. Heavy computation
2. Requests not processed in parallel
3. Requests bouncing between database and server multiple times
you are going to see latency in the initial page loads. You can fix this by doing one or more of the following:
1. Cache heavy computation (if possible)
2. Cache db queries and api responses (if possible)
3. Fetch data in parallel using
await Promise.all([])
4. Add
loading.tsx
to routes5. Fetch non relevant data to initial page load and SEO client side
American black bear
Also try to avoid fetching data using the
Example:
References:
[1] https://react.dev/reference/react/useEffect#fetching-data-with-effects
[2] https://react.dev/reference/react/use#streaming-data-from-server-to-client
useEffect
function as it's been considered a bad practice [1] by react docs. Instead create a promise on the server pass it to client component that depends on the promise response and use that data using the "new" use
hook [2].Example:
// client side fetching done right
// app/pokemons/page.tsx
export function PokemonsPage() {
const pokemonsPromise = getPokemons() // type of this is Promise<Pokemon[]>
return (
<Suspense fallback={"loading"}>
<ClientComponent pokemonsPromise={pokemonsPromise} />
</Suspense>
)
}
// components/client-component.tsx
"use client"
export function ClientComponent(props) {
const pokemons = use(props.pokemonsPromise)
return <p>{JSON.stringify(pokemons)}</p>
}
References:
[1] https://react.dev/reference/react/useEffect#fetching-data-with-effects
[2] https://react.dev/reference/react/use#streaming-data-from-server-to-client
Western Screech-OwlOP
Thank you for your answers, fellas. Here's a little detail: I am not trying to optimize rendering performance, I am trying to regain fast, optimistic, client-side navigation. What I want:
- Navigation updates UI immediately;
- Data loads after;
- No full roundtrip to server just to “transition”.
But I think the App Router is fundamentally not built that way and there's going to be the
- Navigation updates UI immediately;
- Data loads after;
- No full roundtrip to server just to “transition”.
But I think the App Router is fundamentally not built that way and there's going to be the
rsc
request sent every time you hit the link1. don't disable link prefetch. with link prefetch enabled (by default), static pages will trigger the
2. make your pages static if possible. if not, use
rsc
request before the user actually clicks on the link, leading to instant navigations2. make your pages static if possible. if not, use
loading.tsx
or Suspense
@Western Screech-Owl Thank you for your answers, fellas. Here's a little detail: I am not trying to optimize rendering performance, I am trying to regain fast, optimistic, client-side navigation. What I want:
- Navigation updates UI immediately;
- Data loads after;
- No full roundtrip to server just to “transition”.
But I think the App Router is fundamentally not built that way and there's going to be the `rsc` request sent every time you hit the link
Sloth bear
But I think the App Router is fundamentally not built that way and there's going to be the rsc request sent every time you hit the linkYeah that's the default for dynamic pages.
You can alter this by changing
staleTimes.dynamic
(https://nextjs.org/docs/app/api-reference/config/next-config-js/staleTimes) so that even dynamic pages are cached client side for as long as you like.But it's a global setting, so the risk is that somewhere in your app you'll end up showing stale data from your client side cache.
Or you can set
prefetch=true
on a particular link so that the whole dynamic page is prefetched and you'll get instant navigation (by default only the loading page is prefetched for dynamic pages, not the actual page content). But the downside to that is potential overfetching