Skip to content

Adding a feature

Walk-through using a concrete example: adding the /about page.

1. Create the route

App Router convention — a folder matching the URL segment, containing page.tsx.

mkdir -p apps/web/app/about

apps/web/app/about/page.tsx:

import { Header } from "@/components/layout/Header";
import { Footer } from "@/components/layout/Footer";

export const metadata = {
  title: "About",
  description: "Who Fruit Plug is and why we source exotic fruit.",
};

export default function AboutPage() {
  return (
    <>
      <Header />
      <main className="mx-auto max-w-3xl px-4 py-16 sm:px-6">
        <h1 className="fp-display text-5xl">About Fruit Plug</h1>
        <p className="mt-6 text-muted-foreground">
          London's #1 exotic and tropical fruit supplier...
        </p>
      </main>
      <Footer />
    </>
  );
}

Save the file. The browser at https://dev.fruitplug.co.uk/about works immediately — no rebuild.

2. Reuse existing components

Before writing new UI:

  • components/brand/Logo.tsx — the wordmark
  • components/layout/Header.tsx — sticky top bar with nav + cart
  • components/layout/Footer.tsx — four-column footer

Import via @/ alias (configured in tsconfig.json):

import { Header } from "@/components/layout/Header";

3. Follow the brand

  • Use tokens from globals.css (see brand/guidelines)
  • Use Lucide outline icons with strokeWidth={1.75}
  • Script font only for the display heading (fp-display class)

4. Fetch data from Woo

For product/order/customer data, hit the Woo Store API from a Server Component:

async function getProducts() {
  const res = await fetch(`${process.env.WP_STORE_API_URL}/products`, {
    next: { revalidate: 300 }, // 5-min ISR
  });
  return res.json();
}

export default async function ShopPage() {
  const products = await getProducts();
  return <ProductGrid products={products} />;
}

For loyalty / custom-box / push endpoints, hit fruitplug-api:

const res = await fetch(`${process.env.WP_REST_URL}/fruitplug/v1/loyalty/me`, {
  headers: { Cookie: cookies().toString() },
  cache: "no-store",
});

5. Client interactivity

Only opt into "use client" when you need state, effects, or event handlers:

"use client";
import { useState } from "react";
export function AddToCartButton({ id }: { id: number }) {
  const [adding, setAdding] = useState(false);
  // ...
}

Keep the parent Server Component and pass down the client island as a child.

6. Test

# TypeScript
pnpm typecheck

# Lint
pnpm lint

# Production build (smoke-test standalone output)
pnpm build && pnpm start

7. Commit + deploy

git add apps/web/app/about/
git commit -m "feat: /about page"
git push origin main

The GitHub Action builds a new image, pushes it to GHCR, and SSH-deploys it to pwa-host. dev.fruitplug.co.uk will show the update in ~2 min.