Skip to main content
ReactFrontendNext.js

React 19 Deep Dive: Server Components, Actions, and New Hooks

elango
elango

Full Stack Engineer

Updated
8 min read
React 19 Deep Dive: Server Components, Actions, and New Hooks

Upgrading elangodev.com to React 19 and Next.js 16

I run elangodev.com on Next.js 16.1 with React 19.2. The upgrade was not about chasing version numbers — I wanted clearer boundaries between server data fetching and client interactivity. My homepage, blog index, and blog post pages fetch from Supabase on the server. Dashboard, chat, elangodev.com, and Framer Motion animations stay client-side. React 19 formalizes patterns I was already approximating with the App Router.

The mental model I use daily: Server Components render once per request (or revalidation window) and never ship event handlers to the browser. Client Components — marked with "use client" — hydrate where state, effects, and browser APIs are required. Mixing them incorrectly causes hydration warnings or bloated JavaScript bundles.

Root layout: server shell, client providers

app/layout.jsx is a Server Component. It exports metadata with metadataBase https://elangodev.com, Open Graph images, and Google verification when configured. Structured data for Person and WebSite schemas lives in the head without client JavaScript.

Inside body, I nest ThemeProvider and ProfileProvider — both client contexts — then Suspense-wrap analytics and ads. That Suspense boundary matters: AnalyticsTracker and AdSenseScript read consent from localStorage, which only exists in the browser.

export default function RootLayout({ children }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <ThemeProvider>
          <ProfileProvider>
            <Suspense fallback={null}>
              <AnalyticsTracker />
              <GoogleAnalytics />
              <AdSenseScript />
            </Suspense>
            {children}
            <CookieConsent />
          </ProfileProvider>
        </ThemeProvider>
      </body>
    </html>
  );
}

Homepage: server fetch, client presentation

app/page.jsx is async. It calls createSimpleClient(), loads my users row with nested experience, education, and skills, and passes the result as props to HomeContent. export const revalidate = 3600 caches the page for an hour — a tradeoff I accept because profile edits are infrequent compared to anonymous visits.

HomeContent.jsx is "use client" because it uses Framer Motion scroll progress, section scrolling, and dynamic imports for below-the-fold sections. The server gives me fast HTML with real names and job titles; the client enhances with motion without blocking first contentful paint on a monolithic bundle.

const PublicNav = dynamic(() => import("../components/PublicNav"), { ssr: true });
const AboutSection = dynamic(() => import("./components/home/AboutSection"), { ssr: true });

const { scrollYProgress } = useScroll();
const scaleX = useSpring(scrollYProgress, {
  stiffness: 100,
  damping: 30,
  restDelta: 0.001,
});

I set ssr: true on homepage sections so crawlers and no-JS clients still see About and Experience markup. Heavier lab widgets are not on the homepage — that discipline keeps my React 19 server/client split intentional rather than defaulting everything to client.

Blog routes: generateStaticParams and metadata

app/blog/[slug]/page.jsx demonstrates server-only data access at scale. generateStaticParams queries published slugs from posts. generateMetadata calls getPostBySlug for unique titles and OG tags per article. The page component fetches post, profile, and recent posts in parallel with Promise.all — no client waterfall on first load.

PostDetailContent.jsx remains a client component for save-to-localStorage, share buttons, and fallback fetching when someone navigates client-side without SSR props. React 19 does not eliminate client components; it lets me choose them narrowly.

Server Actions versus Route Handlers on my site

I use Route Handlers — app/api/**/route.js — for chat, S3 uploads, and webhooks where I need explicit HTTP semantics, CORS, or third-party SDKs like web-push and AWS S3. Server Actions shine for form mutations colocated with UI; my dashboard still uses Supabase client calls from the browser for rapid iteration, but I am migrating write paths that touch secrets to server-only code.

When I adopt Server Actions for blog publishing, I will mark functions with "use server" inside server modules and call them from dashboard forms. React 19 serializes FormData automatically — less boilerplate than manual fetch with JSON bodies and CSRF considerations on every form.

Streaming, Suspense, and perceived performance

React 19 concurrent features pair naturally with Next.js loading.js files I use on slower lab routes. On the main portfolio, I prefer passing initial data from page.jsx so the hero renders in one server round trip. Suspense around analytics prevents consent-gated scripts from blocking children — a pattern I copied after measuring INP on mobile.

use() for promises is available in React 19, but I have not replaced every async server loader with client use() hooks. For elangodev.com, keeping async page components readable wins over demonstrating every new API in production.

What I tell other developers adopting React 19

Audit your tree from layout downward. Mark client boundaries at the lowest node that needs hooks. Push Supabase reads to server pages when rows are public — blog posts, profile highlights. Keep auth-gated dashboards client-first until Server Actions cover mutations.

Measure bundle size after removing accidental "use client" from leaf components that only render static markup. I cut kilobytes from elangodev.com entry points by splitting editors from marketing wrappers.

React 19 on elangodev.com is not a rewrite — it is a sharper split. Server Components deliver SEO and speed; client components deliver motion, chat, and the dashboard I use daily. That balance is the deep dive worth documenting: real URLs, real layout.jsx imports, and dynamic HomeContent.jsx imports I rely on every deploy.

When I onboard contributors to my repo, the first file I point them to is app/page.jsx paired with HomeContent.jsx — server fetch on top, client enhancement below. That pattern repeats on blog routes and will guide how I add Server Actions to dashboard forms next. React 19 did not change my product goals; it gave me clearer vocabulary for decisions I was already making in production.

What I ship with RSC on this domain

HomeContentLoader, blog listing, and post detail fetch Supabase on the server so Google and Mediapartners-Google see article HTML without waiting on client waterfalls. Client islands handle consent banners, theme, and chat. That split is the practical RSC story — not replacing every form with a Server Action overnight.

Server Actions power a subset of mutations where progressive enhancement matters. For high-frequency chat sends I still use route handlers because streaming and auth cookies are clearer in app/api/chat/* for my current tooling.

Caching mistakes that looked like “RSC is slow”

I once set revalidate too aggressively on the homepage while editing featured posts, then blamed React 19. The fix was explicit revalidate tags around post updates and keeping draft posts out of getFeaturedPosts. Measure TTFB in Vercel analytics before rewriting architecture.

export const revalidate = 3600; // homepage
// After publishing: revalidatePath('/') and revalidatePath('/blog')

Boundaries I refuse to cross

Secrets stay in Server Components and route handlers — never in Client Components via props that serialize to the browser. AdSense publisher IDs are public by nature; Supabase service role keys are not. RSC makes accidental leaks easier if you pass a fat user row into a client tree. I select only columns PublicNav needs.

Migration playbook from pages router habits

If you still think in getServerSideProps, map that mental model to async page.js plus cache controls. For elangodev.com the win was fewer useEffect data loaders on /blog and /handbook — content appears in view-source, which matters for both SEO and AdSense review of “low value” thin shells.

Concrete file map on this repo

app/page.jsx is a Server Component entry. HomeContentLoader fetches featured posts. FeaturedPosts is still a Client-friendly presentational module but receives server data as props. CookieConsent and AdSenseScript remain client islands in the root layout. That map is the “RSC in production” artifact readers cannot get from a generic React 19 announcement post.

When React Compiler guidance lands in our eslint config, I will revisit memo habits — until then I follow the repo rule: do not sprinkle useMemo by default. Server data flow already removes most unnecessary client work on editorial pages.

AdSense-relevant takeaway

Server Components help approval odds only when they actually put unique article HTML in the first response. A client-only blog that hydrates empty shells will look thin to reviewers regardless of React version. elangodev.com’s post pages render title, excerpt, and body on the server for that reason — RSC is a delivery mechanism for substantive writing, not a buzzword badge.

Pair RSC with honest editorial depth. Framework choice cannot rescue a 400-word paraphrase of the docs.

Reader outcomes

After reading this guide on elangodev.com you should be able to implement the pattern in a real Next.js codebase, explain the trade-offs to a teammate, and know which failure modes I already hit in production. That outcome-focused bar is how I decide whether an article is done — not a round word count alone, though we now target roughly 1,500 words of substantive detail for keepers in the AdSense recovery corpus.

elango

Written by

elango

Full Stack Engineer & Software Architect specializing in React, Next.js, and high-performance web applications.

Share on:

Discover More

View blog