docs
    agency-site-kit: Quickstart
    v0.11.1.md

    Quickstart

    The setup CLI adds a working portfolio to a Next.js App Router site in one command: the routes, a starter renderer you own, the webhook Sonor calls when content changes, and the route behind the dashboard's Preview button. This page covers its flags, the files it writes and the three things to finish by hand.

    Before you start

    You need a Next.js App Router project, the two packages, and your project's API key:

    pnpm add @sonordev/agency-site-kit @sonordev/site-kit
    # .env.local
    SONOR_API_KEY=sonor_xxxxxxxx_xxxxxxxxxxxx

    Run the CLI

    From your project root:

    npx agency-site-kit-setup --site-url https://youragency.com --agency-name "Your Agency"

    It looks for app/ first, then src/app/, and writes everything under the one it finds. If there's neither, it stops with an error and writes nothing.

    FlagDefaultWhat it sets
    --base-path/workWhere case studies live. --base-path /projects puts the index at /projects and each case study at /projects/<slug>. A trailing slash is dropped.
    --site-urlhttps://YOUR-SITE.comYour site's origin, written into the index's canonical URLs and each case study's JSON-LD. A trailing slash is dropped.
    --agency-nameYour AgencyThe publisher named in each case study's JSON-LD.

    Give each flag its value as the next argument (--base-path /projects, not --base-path=/projects). If you leave out --site-url, search the generated files for https://YOUR-SITE.com and replace it.

    What it writes

    With the default base path:

    FileWhat it is
    app/work/page.tsxThe index, with a stable title and canonical URL per page
    app/work/category/[category]/page.tsxOne page per category. They're real links, so crawlers find every case study
    app/work/[slug]/page.tsxEach case study, statically generated, with Article JSON-LD
    app/work/error.tsxWhat a visitor sees if Sonor can't be reached on a page's first render
    app/work/_components/CaseStudy.tsxYour case study renderer: hero, challenges, strategy, results, testimonial, gallery and call to action
    app/work/_components/WorkGrid.tsxYour index: category links, cards and pagination
    app/work/_components/work.module.cssStarter styles, built on the --sk-* brand variables
    app/api/seo-revalidate/route.tsThe webhook Sonor calls when content changes (see Live updates and preview)
    app/api/portfolio-preview/route.tsThe dashboard's Preview button (same page)

    The CLI never overwrites a file. Anything that already exists is reported as skipped (exists) and left alone, so running it again only fills in what's missing. The leading underscore on _components keeps that folder out of routing.

    Finish up

    1. Wrap your layout

    AgencySiteKitLayout fetches your brand from Sonor and publishes it as CSS variables the starter styles read. It also gives site-kit's client modules (analytics, forms, chat) the short-lived token they need.

    // app/layout.tsx
    import { AgencySiteKitLayout } from '@sonordev/agency-site-kit';
    
    export default function RootLayout({ children }: { children: React.ReactNode }) {
      return (
        <html lang="en">
          <body>
            <AgencySiteKitLayout>{children}</AgencySiteKitLayout>
          </body>
        </html>
      );
    }

    Already using site-kit's own SiteKitLayout? See Brand layout for how the two fit together.

    2. Add case studies to your sitemap

    // app/sitemap.ts
    import type { MetadataRoute } from 'next';
    import { generatePortfolioSitemapEntries } from '@sonordev/agency-site-kit/portfolio/server';
    
    export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
      const work = await generatePortfolioSitemapEntries({ baseUrl: 'https://youragency.com' });
      return [
        { url: 'https://youragency.com', changeFrequency: 'weekly', priority: 1 },
        ...work,
      ];
    }

    Pass the same basePath you gave the CLI if it isn't /work.

    3. Make it yours

    CaseStudy.tsx and WorkGrid.tsx are plain server components with no client JavaScript, and nothing in the kit depends on their class names or markup. Restyle them, reorder them, or replace them. The kit keeps supplying the data and the rules, whatever the page looks like.

    The starter renders the seven sections most case studies carry. Every other section's data is typed too (see Types), so adding one is a new case in CaseStudy.tsx's switch. For example, a Lighthouse block for the Performance section:

    case 'portfolioPerformance': {
      const data = section.data as Data<'portfolioPerformance'>;
      if (!data.after) return null;
      return (
        <section className={styles.section}>
          <h2>Performance</h2>
          <p>
            Performance {data.after.performance ?? '–'}, SEO {data.after.seo ?? '–'}, accessibility{' '}
            {data.after.accessibility ?? '–'}
          </p>
        </section>
      );
    }

    If you add motion, keep it below the fold. The hero is the page's largest paint and has to be visible in the server-rendered HTML. @sonordev/site-kit has motion components built for that.

    How the starter behaves

    • Every page is a server component. The one client component is error.tsx, because Next requires error boundaries to be.
    • The index shows 12 case studies a page, with ?page=2 and so on for the rest. Category links appear once there's more than one category. The first two card images load eagerly and the rest lazily.
    • A category route carries a slug (web-development). WorkGrid looks the slug up in getPortfolioCategories() to get the stored name the API filters on.
    • Numbers carry their credit. Hero figures and result cards run through curatePortfolioProof and print metricNote under anything you didn't measure, and each card leads with headlineKpi. See Proof and provenance.
    • A missing case study is a 404, an outage isn't. getPortfolioItem returns null for a slug that doesn't exist and throws when Sonor can't be reached, so an already-built page keeps serving its last good copy. See Fetching.

    Check it

    Build, start the server, then fetch the index and look for real content in the HTML:

    pnpm build && pnpm start
    curl -s http://localhost:3000/work | grep -c '<h1'

    A count of zero means the page didn't render on the server. Once the site is deployed, open a draft case study in the Sonor dashboard and press Preview: it should open on your site with the draft showing.