Skip to content
Drivn
5 min read

Next.js Alert — Server-Rendered Component, Zero JS

Add a Next.js Alert that renders entirely on the server. Drivn ships a hook-free, zero-dep Alert component that slots into any App Router page or layout.

Most alert components in the React ecosystem open with a 'use client' directive. That single line is enough to pull the banner — and often the route rendering it — out of Next.js 16's server tree, paying hydration cost for a component that never handles a click. Alerts are display surfaces: a failed payment notice, a trial countdown, a deploy status, an empty-state nudge. They read data, they never collect it.

Drivn's Alert matches that shape. The source is one function that merges three Tailwind class strings — a base, a variant, and your className — onto a div, then renders an optional icon slot beside a title and children. No useState, no effect hook, no 'use client', no browser-only API in the render path. Drop it into an App Router page or layout and Next.js streams it as part of the initial HTML, shipping zero JavaScript for the alert itself.

What follows is the whole path: installing through the CLI, rendering inside a server component, driving the variant from an awaited fetch, pairing it with lucide-react icons, and retuning the styles object against your own tokens. Every snippet is copied from the file npx drivn add alert writes into your repo — no fork, no extra config. The Alert docs list the complete prop surface, and Drivn vs shadcn/ui Alert covers the Radix-based alternative.

Install in a Next.js 16 project

Drivn installs through a tiny CLI that writes component source files directly into your repository — there is no runtime npm package and no version lock to upgrade. Open a terminal in the root of your Next.js 16 project and run npx drivn add alert. The CLI prompts for your install directory (defaulting to src/components/ui/) and copies the Alert source file. If you want to ship icons, it also flags lucide-react as a peer — install it once and every Drivn component that renders an icon will share it. The CLI reference documents every flag, including how to target a custom path or add several components at once. After install you own the file; future Drivn releases will not overwrite it, and you can edit the internals without forking a package. Commit the change so you have a clean baseline before customizing.

1# from the root of your Next.js 16 project
2npx drivn add alert
3
4# add lucide-react for icon props (once per project)
5npm install lucide-react

Render inside a Server Component without 'use client'

App Router pages are server components by default, which means any client-side hook inside them triggers an error at build time. Most toast and alert libraries sidestep this by marking their root as a client component, forcing the host page to also become client or to wrap the component in a client boundary. Drivn's Alert has no such requirement. It contains no useState, no useEffect, and no 'use client' directive — the component is a pure function that maps its props to a div with Tailwind classes. Drop it anywhere in a server tree and Next.js streams it as part of the HTML response, with no JavaScript shipped for the alert itself. The installation guide covers project bootstrapping; the Alert docs list every prop.

1// app/billing/page.tsx — server component
2import { Alert } from '@/components/ui/alert'
3
4export default function BillingPage() {
5 return (
6 <main className="mx-auto max-w-2xl py-12">
7 <h1 className="text-3xl font-bold mb-6">Billing</h1>
8 <Alert variant="info" title="Heads up">
9 Your invoice for April is ready to view.
10 </Alert>
11 </main>
12 )
13}

Drive the variant from server data

A static alert is rarely what production pages ship. You usually want the variant, title, and body to reflect something real — a trial countdown read from the database, a deployment status fetched from an API, a form-submission flash message passed through a cookie. Because the Alert runs as a server component, you can await any data source directly in the page and pass the result into the component. No useEffect, no client-side fetch, no loading spinner. The HTML that ships to the browser already carries the correct variant and copy. Combine this with Next.js fetch caching or revalidateTag and your alerts can stay accurate without a single line of client code.

1// app/dashboard/page.tsx
2import { Alert } from '@/components/ui/alert'
3import { AlertTriangle, CheckCircle } from 'lucide-react'
4
5async function getTrialStatus() {
6 const res = await fetch('https://api.example.com/billing', {
7 next: { revalidate: 60 },
8 })
9 return res.json() as Promise<{ daysLeft: number }>
10}
11
12export default async function Dashboard() {
13 const { daysLeft } = await getTrialStatus()
14 const expiring = daysLeft <= 3
15
16 return (
17 <Alert
18 variant={expiring ? 'destructive' : 'success'}
19 icon={expiring ? AlertTriangle : CheckCircle}
20 title={expiring ? 'Trial ending soon' : 'Trial active'}
21 >
22 {expiring
23 ? `Your trial expires in ${daysLeft} day${daysLeft === 1 ? '' : 's'}.`
24 : `You have ${daysLeft} days left on your trial.`}
25 </Alert>
26 )
27}

Pair with lucide-react icons

The Alert exposes an icon prop typed as React.ComponentType<{ className?: string }> | React.ReactElement — pass either a component reference or a ready-made element. Passing the component (icon={Info}) is cleaner and lets the Alert decide when to render and size it. Because lucide-react ships tree-shakable named exports, each imported icon adds only its own SVG to your bundle, typically one to two kilobytes. The icon appears in a flex-shrink-0 mt-0.5 wrapper next to the title and children, so vertical rhythm stays consistent whether the alert is a single line or a three-line paragraph. For the full icon catalog, browse the lucide-react site and import only what you use. The Alert examples page shows seven icon pairings side by side.

1import { Alert } from '@/components/ui/alert'
2import { Info, CheckCircle, XCircle } from 'lucide-react'
3
4export default function Page() {
5 return (
6 <div className="space-y-4">
7 <Alert variant="info" icon={Info} title="Did you know?">
8 You can customize alerts with any icon.
9 </Alert>
10 <Alert variant="success" icon={CheckCircle} title="Deployed">
11 Build finished in 38 seconds.
12 </Alert>
13 <Alert variant="destructive" icon={XCircle} title="Error">
14 Something went wrong. Please try again.
15 </Alert>
16 </div>
17 )
18}

Theme the styles object for your tokens

Every visual decision in the Alert lives in a co-located styles object at the top of src/components/ui/alert.tsx, above the component itself. Four variant strings map to HSL tokens — bg-accent/50 for default, bg-info/10 for info, bg-success/10 for success, bg-destructive/10 for destructive — each paired with a matching border and text color. Two further keys handle the inner layout: title for the bold heading and description for the body's opacity-90. Retuning colors, widening the padding, or adding a fifth variant all happen inside this one object, and because the variant prop is typed as keyof typeof styles.variants, a new key shows up in autocomplete the moment you add it. Projects scaffolded with npx drivn@latest create already declare these tokens in Tailwind v4's inline @theme block, so light and dark mode track without extra work. Bringing Alert into an existing codebase that names its tokens differently? This object is the only place you need to touch. The theming page lists every Drivn token and its default HSL value.

1// src/components/ui/alert.tsx — styles object
2const styles = {
3 base: 'flex gap-3 p-4 rounded-[10px] border text-sm',
4 variants: {
5 default: 'bg-accent/50 border-border text-foreground',
6 info: 'bg-info/10 border-info/20 text-info',
7 success: 'bg-success/10 border-success/20 text-success',
8 destructive: 'bg-destructive/10 border-destructive/20 text-destructive',
9 },
10 title: 'font-semibold mb-1',
11 description: 'text-sm opacity-90',
12}
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 Alert source contains no useState, useEffect, or other client-only hook and is not marked 'use client'. It renders as a pure server component inside any App Router page or layout, which means zero JavaScript shipped to the browser for the alert itself. The HTML that reaches the client already contains the correct variant, icon, and copy — there is no hydration step and no client boundary crossed.

Make the page an async server component, await your data source at the top, and pass the result into the Alert props directly. Because the Alert runs on the server, variant, title, icon, and children can all come from a database query, a fetch call, or a cookie read — no client-side effect, no loading spinner, and no mismatch between server and client HTML. Combine with Next.js fetch caching or revalidateTag to keep the alert content fresh without extra code.

Not out of the box — Drivn's Alert is a pure display component with no internal dismiss state, which is what keeps it server-renderable. For dismissible banners, either wrap the Alert in a small client component that holds a boolean open state, or reach for Toast, which Drivn ships separately for transient feedback. For persistent but dismissible messages (a cookie notice, for example), store the dismiss state in a cookie on the server so the banner stays hidden across pages without client JavaScript.

The Alert reads HSL custom properties declared in your Tailwind v4 @theme block: --accent, --border, and --foreground for the default variant; --info for info; --success for success; --destructive for destructive. The /10 and /20 suffixes are Tailwind v4 opacity modifiers, which work because each token is stored as a bare HSL triplet rather than a finished color function. If your project names its tokens differently, rename them inside the styles object — nothing else in the file references color.