Build a landing page in Next.js with shadcn-compatible blocks
Build a Next.js landing page from free shadcn-compatible blocks: section order, copy that converts, performance, and metadata and structured data for SEO.
By MiniDev13 min read
To build a landing page in Next.js quickly, keep app/page.tsx a server component that exports metadata, and compose it from eight client blocks in a fixed order: navigation, hero, product, how it works, social proof, pricing, FAQ and a final call to action. MiniDev UI ships each of those as a free shadcn-compatible block with real props, so the work left is your copy, your product visuals and your numbers. This guide covers the order, the props that matter, copy, performance and the SEO basics.
The section order and why
Visitors arrive with one question ("is this for me?") and leave with another ("is it worth it?"). The order answers them in sequence:
| Section | Block | Its one job |
|---|---|---|
| Navigation | floating-nav | Orient, and keep the primary CTA one click away |
| Hero | hero-spotlight | Say what it is, who it is for and what to do next |
| Product | bento-live | Show the product doing the three to six things people buy it for |
| How it works | scroll-story | Remove the fear of setup with three concrete steps |
| Social proof | social-proof-wall | Show that people like the visitor already use it |
| Pricing | pricing-plans | Make the cost and the right plan obvious |
| FAQ | faq-section | Answer the objections that stop the click |
| Final CTA | cta-band | Ask once more, with the lowest friction form possible |
Social proof can move up directly under the hero when you have recognizable logos. Pricing can move down or out entirely for sales-led products; keep the FAQ either way.
Install the blocks and tokens
npx shadcn@latest add https://ui.minidev.pro/r/floating-nav.json https://ui.minidev.pro/r/hero-spotlight.json https://ui.minidev.pro/r/bento-live.json https://ui.minidev.pro/r/scroll-story.json
npx shadcn@latest add https://ui.minidev.pro/r/social-proof-wall.json https://ui.minidev.pro/r/pricing-plans.json https://ui.minidev.pro/r/faq-section.json https://ui.minidev.pro/r/cta-band.json https://ui.minidev.pro/r/light-provider.json
The blocks use MiniDev's semantic tokens (bg-surface, text-fg-muted, shadow-raised) and a few utilities such as light-spot and text-lit. Add the token stylesheet once, either from the package (npm i minidev-ui-kit) or by copying styles.css into your project:
@import "tailwindcss";
@import "minidev-ui-kit/styles.css";
@source "../node_modules/minidev-ui-kit";
Render LightProvider once in your root layout. It writes the light position to CSS variables from the pointer, which is what makes the hero headline and the CTA panel catch the light. Its idle prop, on by default, lets the light drift slowly on touch devices and when the pointer is still; pass idle={false} to keep it pinned to the pointer.
Compose the page
Every block is a client component, but your page does not need to be. Keep app/page.tsx a server component so it can export metadata, and pass only serializable props: strings, arrays, hrefs and React elements. Function props such as onSubmit go through a small client wrapper.
import Image from "next/image"
import { FloatingNav } from "@/components/ui/floating-nav"
import { HeroSpotlight } from "@/components/ui/hero-spotlight"
import { BentoLive } from "@/components/ui/bento-live"
import { ScrollStory } from "@/components/ui/scroll-story"
import { SocialProofWall } from "@/components/ui/social-proof-wall"
import { PricingPlans } from "@/components/ui/pricing-plans"
import { FaqSection } from "@/components/ui/faq-section"
import { Signup } from "./signup"
import { FEATURES, STEPS, QUOTES, PLANS, FAQS } from "./content"
export default function Home() {
return (
<>
<FloatingNav
brand={<span className="font-semibold">Acme</span>}
links={[
{ label: "Product", href: "#product" },
{ label: "How it works", href: "#how" },
{ label: "Pricing", href: "#pricing" },
{ label: "FAQ", href: "#faq" },
]}
cta={{ label: "Start free", href: "/signup" }}
secondary={{ label: "Sign in", href: "/login" }}
/>
<main>
<HeroSpotlight
eyebrow="New: Slack alerts"
title="Revenue answers,"
highlight="before Monday."
description="Acme watches Stripe and your CRM and tells finance what changed and why."
primary={{ label: "Start free", href: "/signup" }}
secondary={{ label: "See a 2 minute demo", href: "#how" }}
proof="Free for 14 days. No card required."
visual={<Image src="/app.png" alt="Acme dashboard showing MRR and alerts" width={2400} height={1500} preload sizes="(min-width: 1280px) 1152px, 100vw" className="rounded-2xl border border-border shadow-overlay" />}
/>
<div id="product" className="scroll-mt-24"><BentoLive title="Everything finance checks, checked for you." items={FEATURES} /></div>
<div id="how" className="scroll-mt-24"><ScrollStory title="Live in an afternoon." steps={STEPS} /></div>
<div id="customers" className="scroll-mt-24"><SocialProofWall logos={["Northwind", "Halcyon"]} quotes={QUOTES} /></div>
<div id="pricing" className="scroll-mt-24"><PricingPlans plans={PLANS} /></div>
<div id="faq" className="scroll-mt-24"><FaqSection items={FAQS} /></div>
<Signup />
</main>
</>
)
}
scroll-mt-24 on each anchor target keeps section headings clear of the sticky nav when a link jumps to them. The same structure, with the default content, is the Lumen template, which is a good reference for spacing and rhythm between the blocks.
Notes on each block
FloatingNav
FloatingNav starts as a full-width bar and, after 40px of scroll, tucks into a floating pill with a raised surface. The hover highlight glides between links, and on mobile a menu sheet drops from the pill. Props: brand, links ({ label, href }[]), cta ({ label, href?, onClick? }) and secondary (pass null to hide it). Keep it to four or five links.
HeroSpotlight
HeroSpotlight renders the page h1 from title plus a second, lit line from highlight. It also takes eyebrow, description, primary and secondary actions, a small proof line, an optional visual that rises in last, and align ("center" or "left"). Put your strongest real product visual in visual. The entrance animation is skipped when the visitor prefers reduced motion.
BentoLive
BentoLive is a six-column grid on large screens. Each item has title, body, an optional visual and a span of 2, 3, 4 or 6 columns, plus tall to span two rows. Make each row add up to six. The idea is that tiles show a small working piece of the product (a live number, a streaming answer, a list of alerts) instead of static screenshots.
ScrollStory
ScrollStory lists steps ({ kicker, title, body, visual }) on the left and pins the active step's visual on the right, switching as each step crosses the middle of the viewport. A rail fills with progress. On small screens each step shows its visual inline, so nothing is lost on mobile. Three steps is the sweet spot.
SocialProofWall
SocialProofWall combines a logo marquee that pauses on hover with a three-column wall of quotes. Quotes are { quote, name, role, metric? }; the optional metric renders as a pill above the quote and is the most persuasive part, so use it. The sample content is fictional. Replace it with real customers before you ship.
PricingPlans and FaqSection
Both are covered in depth in the React pricing page guide: plan data, the yearly toggle with rolling prices, the featured plan, and an accessible accordion FAQ where one answer is open at a time.
CtaBand
CtaBand is a lit panel with a headline and an email form. onSubmit(email) may return a promise: the button shows a busy state while it runs, morphs into the success message when it resolves, and returns to idle if it throws, so errors are handled by throwing.
"use client"
import { CtaBand } from "@/components/ui/cta-band"
export function Signup() {
return (
<CtaBand
button="Get early access"
success="Check your inbox"
note="No card. Unsubscribe in one click."
onSubmit={async (email) => {
const res = await fetch("/api/waitlist", { method: "POST", body: JSON.stringify({ email }) })
if (!res.ok) throw new Error("Signup failed")
}}
/>
)
}
Copy that does the work
- Hero title: the outcome, not the category. "Revenue answers before Monday" beats "AI-powered analytics platform".
- Hero description: who it is for and how it works, in one sentence under 25 words.
- One primary action, repeated with the same label in the nav, hero, pricing and final CTA.
- Feature tiles: a verb and a result in the title, a specific detail in the body. Numbers beat adjectives.
- Steps: start each with a verb ("Connect", "Learn", "Tell") and state the time each takes.
- Quotes: pick ones that name a before and after. Put the measurable part in
metric. - FAQ: answer the questions sales hears, especially security, data, contracts and switching cost.
Performance
Client components still render to HTML on the server, so all copy is in the first response and crawlable. What you pay for on the client is JavaScript for the interactive parts and the Motion library that drives the animations. Keep that cost in check:
- Your hero visual is usually the Largest Contentful Paint element. Serve it with
next/image, give itsizes, and mark itpreload(prioritybefore Next.js 16). - The hero headline animates in with a short fade. If LCP is your headline and you measure a delay, shorten the delay or drop the
initialanimation on theh1in your copy of the block. - Use
next/fontfor fonts so they are self-hosted and do not shift layout. MiniDev UI uses Geist via thegeistpackage. - Keep below-the-fold visuals light. Live tiles built from HTML and CSS are cheaper than autoplaying video.
- Every animated block respects
prefers-reduced-motion, and marquees stop entirely withmotion-reduce:animate-none.
Test on a mid-range phone with network throttling, not only on your laptop. Landing page traffic from ads and social is mostly mobile.
Metadata and SEO basics
Set metadataBase in the root layout, then give the home page a specific title, a description of 140 to 160 characters, a canonical URL and Open Graph data. Add an opengraph-image.tsx next to the page to generate the social image with ImageResponse from next/og.
import type { Metadata } from "next"
export const metadata: Metadata = {
title: "Acme: revenue alerts for finance teams",
description: "Acme watches Stripe, your CRM and your warehouse, and tells finance what changed, why, and what to do next. Free for 14 days.",
alternates: { canonical: "/" },
openGraph: { type: "website", url: "/", siteName: "Acme", title: "Acme: revenue alerts for finance teams" },
twitter: { card: "summary_large_image" },
}
Add structured data for the product as JSON-LD in the page. Escape < so the JSON can never close the script tag:
const jsonLd = {
"@context": "https://schema.org",
"@type": "SoftwareApplication",
name: "Acme",
applicationCategory: "BusinessApplication",
operatingSystem: "Web",
offers: { "@type": "Offer", price: "0", priceCurrency: "USD" },
}
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd).replace(/</g, "\\u003c") }}
/>
- One
h1per page.HeroSpotlightrenders it; every other block renders its title as anh2, which gives the page a clean outline. - Add
app/sitemap.tsandapp/robots.tsso crawlers find the page and its siblings. - Give the hero image real
alttext that describes the product screen. - Link to deeper pages (docs, pricing, use cases) with descriptive anchor text, not "learn more".
Templates to start from
The templates page has complete landing pages built from these blocks. Lumen uses exactly the stack in this guide on the glass material. Open one next to your page and compare section spacing and headline length. If you want the page, the product behind it and the launch handled end to end, the MiniDev studio builds complete products with this kit.
Components used
All free and MIT licensed. For a matching dark theme without a flash on load, see dark mode in Next.js.
Floating NavblockA sticky marketing navbar that tucks into a floating pill after you scroll, with a hover highlight that glides between links and an animated mobile menu.npx shadcn@latest add ui.minidev.pro/r/floating-nav.jsonScroll StoryblockA scrollytelling section: steps scroll on one side while a pinned visual swaps as each reaches mid screen and a progress rail fills. Inline on mobile.npx shadcn@latest add ui.minidev.pro/r/scroll-story.jsonSocial Proof WallblockA testimonial wall with a logo marquee that pauses on hover and three columns of quote cards drifting at different speeds, with metric chips and edge fades.npx shadcn@latest add ui.minidev.pro/r/social-proof-wall.jsonCta BandblockA closing CTA section with a large lit headline and an inline email form whose button animates from idle to sending to a success check. Async onSubmit.npx shadcn@latest add ui.minidev.pro/r/cta-band.jsonnpx shadcn@latest add https://ui.minidev.pro/r/<name>.json
Components used in this guide
Frequently asked questions
What sections should a SaaS landing page have?
Navigation, a hero with one clear action, a product section, how it works, social proof, pricing, an FAQ and a final call to action. Move social proof up if you have well-known customer logos.
Can a Next.js page export metadata if it uses client components?
Yes, as long as page.tsx itself is not marked "use client". A server component page can render client blocks and still export metadata or generateMetadata.
Are shadcn landing page blocks good for SEO?
They can be. Client components still render to HTML on the server, so the copy is crawlable. What matters is one h1, a sensible heading outline, real alt text, fast LCP and proper metadata.
Is there a free Next.js landing page template?
Yes. MiniDev UI's templates, such as Lumen, are built entirely from free MIT blocks you can install with the shadcn CLI and edit in your own repo.