Skip to main content
The vibestrap home page is a stack of self-contained “block” components in src/components/blocks/. Each block owns its layout, pulls its copy from messages/{en,zh}.json, and is independently re-orderable. Composition lives in a single file: src/app/[locale]/(marketing)/page.tsx.

Prerequisites

None. The blocks are already wired in.

The block lineup

Current order in page.tsx:
<Hero />            // Headline + primary CTA + sparkle badge
<LogoCloud />       // "As featured in" / proof bar
<Features />        // Grid of feature cards
<UseCases />        // Tool-station example use cases
<WhyVibestrap />    // Differentiators vs. building from scratch
<Quickstart />      // 3-4 step "ship it today" flow
<Pricing />         // Single-product pricing card
<FAQ />             // Accordion of common questions
<NewsletterCard />  // Email capture
<CTA />             // Final conversion push
Each component is a single file in src/components/blocks/. None of them take props — all variation is via i18n keys.

How a block reads its copy

Every block calls useTranslations with a Home.<block> namespace:
import { useTranslations } from 'next-intl';

export function Hero() {
  const t = useTranslations('Home.hero');
  return <h1>{t('title')}</h1>;
}
Find the matching keys in messages/en.json under Home.hero, and the same keys in messages/zh.json. Editing copy is editing JSON — never touch component source for words.

Re-order or remove blocks

Open src/app/[locale]/(marketing)/page.tsx and rearrange the JSX. To drop a block, delete the line. To move pricing above features, swap the order. There is no nav data structure — visual order is JSX order.

Add a new block

  1. Create src/components/blocks/<my-block>.tsx. Default-export a server component that calls useTranslations('Home.myBlock').
  2. Add Home.myBlock to both messages/en.json and messages/zh.json.
  3. Import and render in page.tsx at the desired position.
  4. Run node scripts/check-i18n.mjs to confirm key parity.
A minimal block:
import { useTranslations } from 'next-intl';

export function MyBlock() {
  const t = useTranslations('Home.myBlock');
  return (
    <section className="border-b py-24">
      <h2 className="text-3xl font-bold">{t('title')}</h2>
    </section>
  );
}

Theme tokens

Brand colors and radii are CSS variables defined in the @theme block of src/app/globals.css (Tailwind v4 directive). Change --primary once and every block re-skins. Dark mode variables live under .dark further down.

Verify it works

pnpm dev                              # eyeball /, /zh, and resize to 360px
node scripts/check-i18n.mjs           # confirm key parity
pnpm typecheck && pnpm lint           # types + style
pnpm build                            # production check
Open Chrome DevTools, toggle device mode to iPhone SE (360px), and scroll the home page top to bottom. Every block must look right.

Common pitfalls

  1. Hardcoded English in components — always use useTranslations. If you write a literal string, the Chinese site shows English. The reviewer will catch it but you’ll already have wasted commits.
  2. Block order matters for visual rhythm — alternate dense and breathing sections (e.g. Features then Quickstart, not two grids in a row).
  3. Mobile responsiveness at 360px — the iPhone SE width is the floor. Test it. Hero subtitles love to overflow on narrow screens.
  4. Adding keys to en.json only — the validator blocks the build. Always add to both locale files.
  5. Importing next/link directly — use Link from @/i18n/navigation so locale prefixes are preserved on click.

Official docs