How I build a Next.js Tailwind marketing site in hours, not days

By Nayan Kyada · · 5 min read

Part of Next.js Performance

Getting a marketing page from blank canvas to production-ready in a single working day is repeatable once you have the right setup. This is the exact Next.js Tailwind workflow I use across client projects — design tokens as CSS custom properties, a small component library with firm conventions, and a page-building sequence that keeps decisions cheap.

Why Tailwind v4 + CSS variables is the right foundation

Tailwind CSS v4 ditched tailwind.config.js in favour of a plain CSS file. That sounds like less control, but it's actually better for marketing sites because your design tokens live in one place that every tool — the browser, Figma tokens plugins, your CMS preview — can read.

Here's the token file I start every project with:

/* app/globals.css */
@import "tailwindcss";
 
@theme {
  --color-brand-500: oklch(55% 0.22 250);
  --color-brand-600: oklch(48% 0.22 250);
  --color-surface: oklch(98% 0 0);
  --color-surface-muted: oklch(95% 0 0);
 
  --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
  --font-display: "Cal Sans", var(--font-sans);
 
  --spacing-section: 5rem;   /* 80px */
  --spacing-container: 80rem; /* 1280px */
 
  --radius-card: 0.75rem;
  --shadow-card: 0 1px 3px oklch(0% 0 0 / 0.08), 0 4px 12px oklch(0% 0 0 / 0.05);
}

Anything inside @theme becomes a Tailwind utility automatically (bg-brand-500, text-brand-600, rounded-card, shadow-card). No config file. No plugin. The same variables are available to arbitrary CSS via var(--color-brand-500) for the edge cases where a utility class won't reach.

Setting up the Next.js project correctly from the start

I use create-next-app with --turbopack and skip the default boilerplate pages immediately:

npx create-next-app@latest acme-site \
  --typescript \
  --tailwind \
  --app \
  --turbopack \
  --import-alias "@/*"

Then I delete app/page.tsx content, strip globals.css back to just the @import "tailwindcss" line, and add my @theme block. Turbopack gives sub-200 ms HMR on component edits — meaningful when you're iterating on section layouts.

Font loading comes next because it affects CLS from the first render. I load Inter via next/font/google in the root layout and expose it as a CSS variable so Tailwind can pick it up:

// app/layout.tsx
import { Inter } from "next/font/google";
import "./globals.css";
 
const inter = Inter({
  subsets: ["latin"],
  variable: "--font-sans",
  display: "swap",
});
 
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" className={inter.variable}>
      <body className="bg-surface font-sans text-gray-900 antialiased">
        {children}
      </body>
    </html>
  );
}

Component conventions that keep the build moving

Marketing sites live or die by how fast you can assemble a new page from existing blocks. My rule: every section component accepts a className prop for layout overrides and owns its own padding via a Section wrapper. That wrapper handles the section spacing token so individual blocks never hardcode vertical rhythm.

The wrapper is dead simple:

// components/ui/section.tsx
import { cn } from "@/lib/utils";
 
interface SectionProps {
  children: React.ReactNode;
  className?: string;
  as?: React.ElementType;
}
 
export function Section({ children, className, as: Tag = "section" }: SectionProps) {
  return (
    <Tag
      className={cn(
        "mx-auto w-full max-w-[var(--spacing-container)] px-4 py-[var(--spacing-section)] sm:px-6 lg:px-8",
        className
      )}
    >
      {children}
    </Tag>
  );
}

I keep a cn util (clsx + tailwind-merge) in lib/utils.ts — it prevents class conflicts when callers override spacing or background on a per-page basis.

For section-level components I follow a consistent file shape: components/sections/hero.tsx, components/sections/feature-grid.tsx, components/sections/testimonials.tsx. Each exports one named component, accepts a typed props interface, and uses the Section wrapper. That's the entire convention. No special registry, no magic config.

The build sequence for a new page

When a new marketing page lands in my queue — say, a product landing page — I work in this exact order:

1. Rough layout in one file. I drop everything into app/(marketing)/product/page.tsx with inline placeholder text. No components yet. This gets the section order agreed on quickly before any abstraction.

2. Extract real content into section components. Once the order is locked, each section gets its own file under components/sections/. This is usually three or four components for a standard landing page (hero, feature grid, social proof, CTA).

3. Wire up images with next/image. Every image gets explicit width and height to prevent CLS, sizes tuned to the actual layout breakpoint, and priority on the above-the-fold hero image. I do not use fill for images that have a known aspect ratio — it adds layout complexity and makes CLS worse if you get the container sizing wrong.

4. Add metadata. The page exports a generateMetadata function with title, description, openGraph, and twitter keys. Takes five minutes and covers the basics.

5. Check Core Web Vitals before handing off. I run next build && next start locally, open Chrome DevTools Performance panel on a throttled 4G profile, and look at LCP element and any layout shift. Tailwind's utility approach mostly eliminates render-blocking CSS issues, but a missed priority prop or a wrong sizes string will still tank LCP.

What this gets you in practice

A five-section marketing page — hero, feature overview, social proof, FAQ, footer — takes roughly four hours with this setup, including responsive behaviour and basic SEO metadata. The token system means a client colour change is one variable edit, not a grep-and-replace across fifty files. The section convention means a junior dev or second contractor can add a new block without breaking the layout rhythm.

The things that eat time on marketing builds are usually decisions, not code: copy changes, section reordering, brand adjustments. This stack makes the code side of those changes cheap enough that design iteration doesn't turn into a rework spiral.