Capítulo 81 de 456

SPAs

Core Idea

Next.js suporta construir Single-Page Applications (navegação client-side, sem full-page reload) usando padrões próprios — evitando o JS pesado e os data waterfalls de uma "strict SPA" tradicional, com caminho de migração progressivo para features de servidor.

Key Concepts

  • Strict SPA: definida aqui como CSR total (um único index.html) + zero full-page reloads; costuma exigir muito JS antes de ficar interativa e sofre com data waterfalls no cliente.
  • Code splitting automático + múltiplos entry points HTML: Next.js evita carregar JS desnecessário no cliente, reduzindo bundle size.
  • next/link com prefetch automático: dá transições rápidas como uma strict SPA, mas preserva o estado de rota na URL (compartilhável/linkável).
  • use() + Context Provider: padrão para streamar dado do servidor a um Client Component — inicia o fetch num Server Component (sem await), passa a Promise via provider/context, e o Client Component desembrulha com use(), suspendendo até resolver.
  • React.cache: usado para compartilhar uma única chamada de dado (ex. getUser) entre múltiplos componentes que leem o mesmo dado numa mesma requisição.
  • next/dynamic com ssr: false: desabilita prerender de um Client Component, carregando-o só no browser — útil para libs de terceiros que dependem de window/document.
  • Shallow routing: usar window.history.pushState/replaceState diretamente para atualizar a URL sem reload; essas chamadas se integram ao router do Next.js e sincronizam com usePathname/useSearchParams.
  • Server Actions + useTransition/useOptimistic/useActionState/useFormStatus: ferramentas React para manter a UI responsiva durante uma mutação de servidor, aproximando a sensação de uma SPA client-rendered.
  • Static export (opcional): Next.js pode gerar uma strict SPA totalmente estática, sem servidor.

Code Examples

import { UserProvider } from './user-provider'
import { getUser } from './user'

export default function RootLayout({ children }: LayoutProps<'/'>) {
  let userPromise = getUser() // do NOT await
  return (
    <html lang="en">
      <body>
        <UserProvider userPromise={userPromise}>{children}</UserProvider>
      </body>
    </html>
  )
}
'use client'
import { use } from 'react'
import { useUser } from './user-provider'

export function Profile() {
  const userPromise = useUser()
  const user = use(userPromise)
  return <p>{user.name}</p>
}
  • O que demonstra: Server Component inicia o fetch sem await e passa a Promise adiante; Client Component desembrulha com use() e suspende até resolver, evitando waterfall client-side.
'use client'
import { useTransition } from 'react'
import { deletePost } from './actions'

export function DeletePost({ id }: { id: string }) {
  const [isPending, startTransition] = useTransition()
  return (
    <button disabled={isPending} onClick={() => startTransition(() => deletePost(id))}>
      {isPending ? 'Deleting…' : 'Delete'}
    </button>
  )
}
  • O que demonstra: chamar uma Server Action dentro de startTransition e usar isPending para feedback imediato de UI.
import dynamic from 'next/dynamic'

const ClientOnlyComponent = dynamic(() => import('./component'), {
  ssr: false,
})
  • O que demonstra: desabilitar prerender/SSR para um componente que depende de APIs exclusivas do browser.

Anti-patterns

  • Colocar o provider da Promise no root layout quando só parte do app precisa do dado: refetch da Promise alta na árvore re-executa o Server Component que a definiu — posicione o provider mais perto da subárvore que consome.
  • Reduzir tudo a strict SPA por padrão: perde os ganhos de code splitting automático e prerender que o Next.js já dá de graça.

Key Takeaways

  1. O padrão recomendado para streaming servidor→cliente sem waterfall é: fetch sem await no Server Component + Promise via context + use() no Client Component, dentro de <Suspense>.
  2. React.cache evita chamadas duplicadas quando múltiplos componentes leem o mesmo dado numa mesma requisição.
  3. Shallow routing com window.history.pushState/replaceState é o caminho de migração pra código legado de SPA que já manipulava a URL manualmente (ex. vindo de CRA/Vite).
  4. Para mutações que precisam parecer instantâneas, combine Server Actions com useTransition, useOptimistic, useActionState e useFormStatus.

Connects To

  • Server and Client Boundary (ch080): base teórica de como use() e Promise-as-prop cruzam a fronteira servidor/cliente.
  • Server Actions (ch079): mecanismo de mutação usado nos padrões de SPA aqui descritos.
  • Static Exports (ch082): caminho para gerar uma strict SPA totalmente estática com Next.js.