Skip to content
Drivn
5 min read

Next.js Aspect Ratio — Server-Rendered, Zero JS

Add a Next.js Aspect Ratio box that renders on the server with zero JavaScript. Drivn ships a hook-free, zero-dependency component for images and video.

Next.js renders most of your UI on the server and ships only the interactive parts to the browser. Media containers — the boxes that hold a hero image, an embedded video, or a third-party iframe — belong firmly on the server side. They reserve space and clip their child; they collect nothing and react to nothing. A media box that holds its proportions before the asset loads is also the simplest fix for layout shift, the Core Web Vital that punishes pages whose content jumps as images arrive.

Drivn's Aspect Ratio component was built for exactly this. It is a plain function that renders a single div with relative w-full overflow-hidden and an inline aspect-ratio style derived from a ratio prop. There is no 'use client' directive, no useState, no effect, and no browser-only API anywhere in the render path. Drop it into any App Router page, layout, or server component and Next.js streams it as part of the initial HTML, with zero JavaScript shipped for the box itself.

This guide installs Drivn in a Next.js 16 project, renders an Aspect Ratio container in a server component, fills it with a responsive next/image, embeds a video without layout shift, and tunes the container for custom ratios. Every snippet comes from the component source the CLI installs. For the full API see the Aspect Ratio docs; for the head-to-head see Drivn vs shadcn/ui Aspect Ratio.

Install in a Next.js 16 project

Drivn installs through a small CLI that writes component source files directly into your repository — there is no runtime npm package and no version to keep upgrading. Open a terminal at the root of your Next.js 16 project and run npx drivn add aspect-ratio. The CLI prompts for an install directory, defaulting to src/components/ui/, then copies aspect-ratio.tsx into place. Unlike most aspect-ratio helpers, this one pulls in no peer dependency at all — it is pure React and Tailwind, so there is nothing else to install. The CLI reference documents every flag, including targeting a custom path or adding several components in one command. After install you own the file; future Drivn releases never overwrite it, and you can edit the internals without forking a package.

1# from the root of your Next.js 16 project
2npx drivn add aspect-ratio

Render inside a Server Component without 'use client'

App Router pages are server components by default, so any client-side hook inside one fails at build time. A surprising number of media-ratio packages still mark their root 'use client' because they measure the container with a resize observer. Drivn's Aspect Ratio measures nothing. It sets the proportion with the native CSS aspect-ratio property and lets the browser do the math, so the component is a pure function with no hooks and no directive. Render it directly inside a server page and Next.js streams the box as static HTML. The installation guide covers project bootstrapping; the Aspect Ratio docs list every prop the component accepts.

1// app/page.tsx — server component
2import { AspectRatio } from '@/components/ui/aspect-ratio'
3
4export default function Page() {
5 return (
6 <main className="mx-auto max-w-2xl py-12">
7 <AspectRatio ratio="16/9">
8 <img
9 src="/hero.jpg"
10 alt="Product hero"
11 className="w-full h-full object-cover"
12 />
13 </AspectRatio>
14 </main>
15 )
16}

Hold a responsive next/image

In production you usually want Next.js to optimize the image rather than serving a raw <img>. The next/image component does this, and it pairs naturally with Aspect Ratio: set fill on the image so it absolutely positions to the nearest positioned ancestor, and let the Aspect Ratio div — which already carries relative and overflow-hidden — be that ancestor. The box reserves the correct height from first paint, the optimized image fills it once decoded, and there is no layout shift in between. Add a sizes attribute so Next.js serves an appropriately scaled file for each breakpoint. Because the container clips overflow, object-cover crops cleanly to the ratio you chose.

1import Image from 'next/image'
2import { AspectRatio } from '@/components/ui/aspect-ratio'
3
4export default function Cover() {
5 return (
6 <AspectRatio ratio="16/9">
7 <Image
8 src="/hero.jpg"
9 alt="Landscape"
10 fill
11 sizes="(max-width: 768px) 100vw, 768px"
12 className="object-cover"
13 />
14 </AspectRatio>
15 )
16}

Embed video and iframes without layout shift

Embedded video and third-party iframes are the most common cause of cumulative layout shift, because their intrinsic size is unknown until the frame loads. Wrapping the embed in an Aspect Ratio container fixes the height before the iframe arrives, so surrounding content never jumps. Pass the ratio that matches the source — 16/9 for most modern video, 4/3 for older footage — and stretch the iframe to fill with w-full h-full. The container's overflow-hidden trims any letterboxing the provider adds. This works identically for a YouTube embed, a Vimeo player, a map iframe, or a self-hosted <video> element, and all of it still renders on the server because the box itself ships no JavaScript. See the Aspect Ratio examples for portrait and square variants.

1import { AspectRatio } from '@/components/ui/aspect-ratio'
2
3export default function Trailer() {
4 return (
5 <AspectRatio ratio="16/9">
6 <iframe
7 src="https://www.youtube.com/embed/dQw4w9WgXcQ"
8 title="Trailer"
9 allowFullScreen
10 className="w-full h-full"
11 />
12 </AspectRatio>
13 )
14}

Set custom ratios and theme the container

The ratio prop accepts three string presets — 16/9, 4/3, 1/1 — or any raw number for a custom proportion. Pass ratio={2.35} for a cinematic widescreen crop, ratio={3 / 4} for a portrait card, or compute the value from real dimensions with ratio={width / height}. Internally the component is deliberately small: a single div that merges your className over relative w-full overflow-hidden and sets the CSS aspectRatio property from the prop. There is nothing to theme in the usual sense, but because className is forwarded last you can add rounded corners, a border, or a background placeholder color without touching the source. The theming page covers the design tokens you would reference for that placeholder.

1// src/components/ui/aspect-ratio.tsx
2export function AspectRatio({
3 ratio = '16/9',
4 className,
5 children,
6}: AspectRatioProps) {
7 return (
8 <div
9 className={cn('relative w-full overflow-hidden', className)}
10 style={{ aspectRatio: ratio }}
11 >
12 {children}
13 </div>
14 )
15}
Get started

Install Drivn in one command

Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.

npx drivn@latest create

Requires Node 18+. Works with npm, pnpm, and yarn.

Enjoying Drivn?
Star the repo on GitHub to follow new component releases.
Star →

Frequently asked questions

No. The component sets its proportion with the native CSS aspect-ratio property rather than measuring the DOM, so it contains no useState, no useEffect, no resize observer, and no 'use client' directive. It renders as a pure server component inside any App Router page or layout, shipping zero JavaScript for the box itself. The HTML that reaches the browser already reserves the correct height.

Wrap the image in an Aspect Ratio container with the ratio that matches the asset, then use next/image with the fill prop. The container reserves the final height from first paint because the CSS aspect-ratio resolves before the image loads, so nothing reflows when the optimized file arrives. This removes the cumulative layout shift that an unsized <img> would otherwise cause as it decodes.

Yes. The ratio prop accepts any number in addition to the 16/9, 4/3, and 1/1 string presets. Pass ratio={2.35} for a cinematic crop, ratio={3 / 4} for portrait, or derive it from known dimensions with ratio={width / height}. The value is written straight into the inline aspectRatio style, so any valid CSS aspect ratio works without changing the component source.

None. Unlike most aspect-ratio utilities, the Drivn component imports only React and the local cn class-merge helper — there is no Radix primitive, no resize-observer polyfill, and no lucide-react requirement because it renders no icon. The CLI writes a single self-contained .tsx file into your project, so your bundle grows by the size of that file and nothing else.