YeetCode
Next.js App

Frontend Architecture

How Next.js and Convex work together in YeetCode's frontend.

YeetCode's frontend architecture follows a clear separation of concerns: Next.js is the shell (routing, images, middleware, deployment) and Convex owns the data layer on the client.

Core Principles

Server Components by Default

Pages (page.tsx) stay as Server Components. They are layout shells that compose Client Components:

// app/dashboard/page.tsx — Server Component (no "use client")
export default function DashboardPage() {
  return (
    <div>
      <h1>Dashboard</h1>
      <UserStats />      {/* Client Component */}
      <RecentActivity />  {/* Client Component */}
    </div>
  );
}

Never make entire pages into Client Components. Keep pages as Server Components stitching together client-side pieces.

Client Components Own Their Data

Individual Client Components declare their own Convex useQuery subscriptions:

"use client";

import { useQuery } from "convex/react";
import { api } from "@yeetcode/convex-backend/_generated/api";

export function UserStats() {
  const stats = useQuery(api.stats.getUserStats);
  if (!stats) return <Skeleton />;
  return <StatsDisplay data={stats} />;
}

Each component is self-contained — it fetches exactly the data it needs and handles its own loading states.

No SSR for Authenticated Convex Data

Authenticated pages have no SEO benefit from server-side rendering. The data behind auth doesn't need to be in the initial HTML — it loads in real-time via Convex subscriptions on the client.

SSR for Public Content

Public content served from Convex (articles, changelogs) should use preloadQuery for SSR:

// app/changelog/[slug]/page.tsx — Server Component with SSR data
import { preloadQuery } from "convex/nextjs";

export default async function ChangelogEntry({ params }) {
  const preloaded = await preloadQuery(api.changelog.getEntry, { slug: params.slug });
  return <ChangelogContent preloadedEntry={preloaded} />;
}

These pages need SEO and fast first paint, so server-rendering the Convex data makes sense.

Marketing Pages Are Standard Next.js

Marketing and static pages (pricing, landing, about) use standard Next.js SSR/ISR without Convex. They don't need real-time data.

Summary

Page TypeRenderingData Source
Authenticated app pagesClient-sideConvex useQuery subscriptions
Public dynamic contentSSR via preloadQueryConvex
Marketing/static pagesSSR/ISRNext.js (no Convex)

On this page