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 purposeBuilt-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
})