Next.js Discord

Discord Forum

App feels unresponsive when navigating between pages (App Router)

Unanswered
Western Screech-Owl posted this in #help-forum
Open in Discord
Western Screech-OwlOP
Using Next.js App Router, I’m noticing that any kind of navigation — whether via <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>;
}
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 await Promise.all([])
4. Add loading.tsx to routes
5. Fetch non relevant data to initial page load and SEO client side
American black bear
Also try to avoid fetching data using the 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 rsc request sent every time you hit the link
1. don't disable link prefetch. with link prefetch enabled (by default), static pages will trigger the rsc request before the user actually clicks on the link, leading to instant navigations
2. 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 link

Yeah 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