ReactReact Frameworks (Next.js, Remix)

React Frameworks (Next.js, Remix)

React is intentionally a library, not a framework. It handles the view layer brilliantly but ships without routing, server rendering, build optimisation, or data fetching conventions. For production apps you almost always layer a framework on top. This page surveys the major options and explains when each one wins.

Why Use a Framework at All?
  • Routing — frameworks provide file-system or convention-based routing so you don't configure react-router from scratch

  • Server-side rendering (SSR) / Static generation (SSG) — critical for SEO and initial page-load performance

  • Build optimisation — automatic code splitting, image and font optimisation, tree shaking

  • Full-stack capability — API routes, server actions, and middleware colocated with your UI code

  • Deployment integration — one-command deploys to Vercel, Netlify, Cloudflare, or your own server

  • Developer experience — fast refresh, TypeScript preset, sensible defaults out of the box

Next.js — The Dominant Choice

Next.js (maintained by Vercel) is by far the most widely adopted React framework. It supports two routing systems: the App Router (Next.js 13+, the future) and the Pages Router (stable, battle-tested). New projects should use the App Router.

App Router highlights:

  • File-based routing under app/ — every folder is a route segment

  • React Server Components by default — fetch data directly in components

  • Nested layouts with layout.tsx — shell UI shared across child routes without re-rendering

  • Server Actions — async functions that run on the server, callable from forms and event handlers

  • Streaming with Suspense — stream HTML chunks as data resolves

  • Route handlers (route.ts) replace API routes

TSX
// app/blog/[slug]/page.tsx — App Router page
interface Props {
  params: { slug: string }
}

// generateStaticParams → pre-render at build time (SSG)
export async function generateStaticParams() {
  const posts = await fetch('https://api.example.com/posts').then((r) => r.json())
  return posts.map((p: { slug: string }) => ({ slug: p.slug }))
}

export default async function BlogPost({ params }: Props) {
  const post = await fetch(`https://api.example.com/posts/${params.slug}`).then(
    (r) => r.json()
  )
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.body}</p>
    </article>
  )
}

Pages Router (legacy but still fully supported):

TSX
// pages/blog/[slug].tsx — Pages Router page
import type { GetStaticProps, GetStaticPaths } from 'next'

interface Props {
  post: { title: string; body: string }
}

export const getStaticPaths: GetStaticPaths = async () => {
  const posts = await fetch('https://api.example.com/posts').then((r) => r.json())
  return {
    paths: posts.map((p: { slug: string }) => ({ params: { slug: p.slug } })),
    fallback: false,
  }
}

export const getStaticProps: GetStaticProps<Props> = async ({ params }) => {
  const post = await fetch(`https://api.example.com/posts/${params!.slug}`).then(
    (r) => r.json()
  )
  return { props: { post }, revalidate: 60 }
}

export default function BlogPost({ post }: Props) {
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.body}</p>
    </article>
  )
}

Next.js also provides first-class optimisation components:

TSX
import Image from 'next/image'
import Link from 'next/link'
import { Inter } from 'next/font/google'

const inter = Inter({ subsets: ['latin'] }) // font loaded zero-layout-shift

export function Header() {
  return (
    <header className={inter.className}>
      <Link href="/">Home</Link>
      <Image
        src="/logo.png"
        alt="Company logo"
        width={120}
        height={40}
        priority // preload as LCP candidate
      />
    </header>
  )
}
Remix — Web Standards First

Remix (now part of the React Router ecosystem under Shopify) takes a different philosophy: embrace web platform fundamentals — HTML forms, HTTP semantics, browser navigation — rather than recreate them in JavaScript. The result is apps that degrade gracefully and often perform better with less code.

The core pattern is loaders (data for GET) and actions (data mutations for POST/PUT/DELETE), colocated with their route:

TSX
// app/routes/contacts.$contactId.tsx — Remix route
import { json, redirect } from '@remix-run/node'
import { useLoaderData, Form } from '@remix-run/react'
import type { ActionFunctionArgs, LoaderFunctionArgs } from '@remix-run/node'
import { getContact, updateContact } from '~/data'

// loader runs on the server for GET requests
export async function loader({ params }: LoaderFunctionArgs) {
  const contact = await getContact(params.contactId)
  if (!contact) throw new Response('Not Found', { status: 404 })
  return json({ contact })
}

// action runs on the server for form submissions
export async function action({ params, request }: ActionFunctionArgs) {
  const formData = await request.formData()
  await updateContact(params.contactId, Object.fromEntries(formData))
  return redirect(`/contacts/${params.contactId}`)
}

export default function ContactDetail() {
  const { contact } = useLoaderData<typeof loader>()

  return (
    <Form method="post">
      <input name="name" defaultValue={contact.name} />
      <button type="submit">Save</button>
    </Form>
  )
}

Key Remix differentiators:

  • Nested routes — each route segment can have its own loader, action, and error boundary. Errors are isolated to the broken segment

  • Progressive enhancement — forms work without JavaScript enabled; JS adds optimistic UI on top

  • No client-side data cache — data is always fresh from the server (loaders re-run on navigation)

  • Edge-first — runs natively on Cloudflare Workers, Deno Deploy, and other edge runtimes

Gatsby — Static Site Specialist

Gatsby excels at content-heavy static sites: marketing pages, blogs, documentation, e-commerce catalogues. It builds every page at compile time and ships a highly optimised static bundle. Its GraphQL data layer lets you pull from CMSes (Contentful, Sanity, WordPress), markdown files, and APIs into a unified query interface.

TSX
// src/pages/blog/{markdownRemark.frontmatter__slug}.tsx
import { graphql, type PageProps } from 'gatsby'

export const query = graphql`
  query BlogPost($id: String!) {
    markdownRemark(id: { eq: $id }) {
      html
      frontmatter {
        title
        date(formatString: "MMMM DD, YYYY")
      }
    }
  }
`

export default function BlogPost({ data }: PageProps<Queries.BlogPostQuery>) {
  const post = data.markdownRemark!
  return (
    <main>
      <h1>{post.frontmatter?.title}</h1>
      <time>{post.frontmatter?.date}</time>
      <div dangerouslySetInnerHTML={{ __html: post.html! }} />
    </main>
  )
}

Gatsby has lost significant mindshare to Next.js for general-purpose projects but remains the best choice when GraphQL-driven static generation and Gatsby's plugin ecosystem (image processing, CMS connectors) are priorities.

Expo / React Native — Mobile

React Native lets you write React components that compile to native iOS and Android views — not a WebView. Expo wraps React Native with a managed workflow, OTA updates, and a massive library of native modules. With Expo Router, you get file-based routing that works across iOS, Android, and the web from a single codebase.

TSX
// app/(tabs)/profile.tsx — Expo Router screen
import { View, Text, StyleSheet } from 'react-native'
import { useLocalSearchParams } from 'expo-router'

export default function ProfileScreen() {
  const { userId } = useLocalSearchParams<{ userId: string }>()

  return (
    <View style={styles.container}>
      <Text style={styles.title}>User {userId}</Text>
    </View>
  )
}

const styles = StyleSheet.create({
  container: { flex: 1, alignItems: 'center', justifyContent: 'center' },
  title: { fontSize: 24, fontWeight: 'bold' },
})
Framework Comparison

Framework

Best for

Rendering

Key strength

Next.js (App Router)

Full-stack web apps

SSR, SSG, RSC, ISR

RSC, server actions, Vercel platform

Next.js (Pages Router)

Existing Next.js apps

SSR, SSG, ISR

Stability, battle-tested

Remix / React Router v7

Data-heavy, form-driven apps

SSR

Web standards, nested routes, edge

Gatsby

Marketing sites, blogs

SSG

GraphQL data layer, plugin ecosystem

Expo

iOS / Android / web

Native + web

Shared codebase across platforms

Vite SPA (no framework)

SPAs, dashboards, internal tools

CSR

Simplicity, no server needed

When to Use Bare React (Vite SPA)

Not every project needs a framework. A Vite-powered SPA is the right choice when:

  • The app lives behind authentication (SEO is irrelevant — Google doesn't index your dashboard)

  • You're building an internal tool, admin panel, or data visualisation

  • The backend is owned by another team and React is purely a UI layer

  • You want maximum flexibility with no framework opinions on routing or data

  • Build-time complexity needs to be minimal (no server to manage)

The Next.js default
For greenfield public-facing web apps, Next.js App Router is the community default in 2025. It covers every rendering strategy, has the largest ecosystem, and is the reference implementation for React Server Components.
Note
Remix merged with React Router in late 2024 (as React Router v7). The loader/action mental model is unchanged — only the package names differ. Legacy Remix v2 projects migrate with a compatibility layer.