How does Next.js decide between server and client execution, especially during routing?

I’ve been reading through Next.js documentation on rendering modes, including Server Components, Server Actions, and data fetching best practices. My understanding is that Next.js code can run in three contexts:

  1. Build time.
  2. Server side.
  3. Client side.

I’m confused about how Next.js decides whether code executes on the client or the server, especially during client-side routing (e.g., when clicking a link). Here’s what I understand so far:

Build Time Execution

Code is prevented from running at build time if:

  • export const dynamic = 'force-dynamic' is set.
  • "use client" is present at the top of the file.
  • Any of the functions cookies(), headers(), unstable_noStore(), unstable_after(), or searchParams are used (source).

Server-Side vs. Client-Side Execution

  • If the file starts with "use client", it runs client-side only.
  • Without "use client", the initial render runs server-side by default.

My Confusion: Preventing Client-Side Execution

Consider the following example from the official docs:

import { unstable_cache } from 'next/cache'
import { db, posts } from '@/lib/db'
 
const getPosts = unstable_cache(
  async () => {
    return await db.select().from(posts)
  },
  ['posts'],
  { revalidate: 3600, tags: ['posts'] }
)
 
export default async function Page() {
  const allPosts = await getPosts()
 
  return (
    <ul>
      {allPosts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

Clearly, getPosts() can only run server-side (it fetches from a database), but what happens when the page is rendered client-side during navigation?

  • Does Next.js always force server-side rendering when navigating between pages, even with client-side routing? How? Does it do something akin to RoR’s Hotwire?

  • Or, does it somehow translate the database call in some RPC style execution over network?

This part of Next.js isn’t very clear to me, and I’d appreciate any insight or clarifications on how Next.js handles this.