Skip to content
Drivn logoDrivn
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.

Next.js 16 pushes interactivity to the edges of your app and keeps the rest rendering on the server. Alerts — banners for success messages, API errors, trial countdowns, empty-state prompts — fit that model exactly. They display content, they do not collect it. A well-built Alert should render on the server, read server-side data directly, and stream into the initial HTML response with no hydration cost and no client JavaScript.

Drivn's Alert was built this way. The component is a plain function that renders a div with a variant-keyed Tailwind class string and an optional lucide-react icon. There is no 'use client' directive, no useState, no effect hook, and no browser-only API anywhere in the render path. Import it inside any App Router page, layout, or server action response — it renders to HTML identically on the server and the client runtime.

This guide walks through installing Drivn in a Next.js 16 project, dropping an Alert into a server-rendered route, driving its variant and content from an async fetch call, pairing it with lucide-react icons, and tailoring the styles object to match your design tokens. Every snippet is drawn from the Alert source file the CLI installs — no forks, no extra config. For the full API surface see the Alert docs; for a head-to-head with the Radix-based alternative, see Drivn vs shadcn/ui Alert.

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. The component reads four variant strings that reference HSL tokens — bg-accent/50, bg-primary/10, bg-success/10, bg-destructive/10 — plus a title class for the bold heading and a description class for the body opacity. Editing this object is how you retune colors, adjust padding, or add a new variant. In a Next.js 16 project scaffolded with npx drivn@latest create, the tokens are wired up in globals.css through Tailwind v4's inline @theme block, so dark mode and light mode already track correctly. If you bring Drivn into an existing project with different token names, this object is the only file you need to adjust. The theming page maps every Drivn token to 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-primary/10 border-primary/20 text-primary-light',
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 defined in your Tailwind v4 @theme block: --accent, --border, and --foreground for the default variant; --primary and --primary-light for info; --success for success; --destructive for destructive. The /10 and /20 opacity suffixes are Tailwind v4 opacity modifiers that work because the tokens are stored as HSL triplets. Swap the token names in the styles object if your project uses different names.