More Next.js Features

A grab-bag of the other things Next.js gives you beyond routing/rendering/API routes/auth β€” each is a small addition to what you already know from React.

Optimized images β€” next/image

Automatically serves correctly-sized, modern-format (WebP/AVIF) images, lazy-loads offscreen images, and prevents layout shift by requiring width/height (or fill). Plain React/Vite has no equivalent built in β€” you'd reach for a library.
import Image from 'next/image'

<Image src="/logo.png" alt="Logo" width={120} height={40} priority />

Optimized fonts β€” next/font

Self-hosts Google Fonts (or local fonts) at build time β€” no request to Google at runtime, no layout shift while the font loads. This app's sidebar/body text uses this already, in app/layout.tsx.
app/layout.tsx
import { Geist } from 'next/font/google'
const geistSans = Geist({ variable: '--font-geist-sans', subsets: ['latin'] })

Metadata API β€” SEO tags without a <head> manually

Export a `metadata` object (static) or a `generateMetadata()` function (dynamic, e.g. per blog post) and Next.js injects the right <title>/<meta> tags β€” including nested metadata that merges down through layouts.
app/blog/[slug]/page.tsx
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params
  const post = getPostBySlug(slug)
  return { title: post?.title ?? 'Not found' }
}

Streaming with Suspense

Wrap a slow-loading part of a Server Component tree in <Suspense> and Next.js streams the rest of the page to the browser immediately, filling in the slow part when it's ready β€” instead of the whole page waiting on the slowest piece of data.
import { Suspense } from 'react'

export default function Page() {
  return (
    <div>
      <FastHeader />
      <Suspense fallback={<p>Loading comments…</p>}>
        <SlowComments />  {/* can take its time without blocking the rest */}
      </Suspense>
    </div>
  )
}

Environment variables & server-only secrets

Anything in .env.local is available server-side via process.env. Only variables prefixed NEXT_PUBLIC_ are ever sent to the browser bundle β€” a built-in guardrail against leaking secrets, with no equivalent convention in a plain Vite app (Vite uses import.meta.env and its own VITE_ prefix instead).
# .env.local
DATABASE_URL=postgres://...        # server-only, never bundled
NEXT_PUBLIC_ANALYTICS_ID=abc123    # bundled into client JS on purpose

Built-in caching & revalidation

fetch() calls inside Server Components are automatically deduplicated and can be told how long to stay fresh β€” no separate data-fetching library (React Query, SWR) is strictly required, though they're still fine to use for client-side needs.
fetch('https://api.example.com/data', {
  next: { revalidate: 60 },   // reuse the response for up to 60s
})