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

    @sonordev/agency-site-kit

    Case studies for agency websites built on Sonor. The kit fetches your portfolio from Sonor and gives your site the rules every case study follows: which numbers it may claim and the credit a client's number wears, the order its sections read in, JSON-LD, image sizes, the device-stage decision, and what happens when the API can't be reached. How your case studies look is up to you: the kit doesn't render any markup, and the setup CLI scaffolds a starter renderer that's yours to restyle.

    Built for the Next.js App Router. Full docs live at sonor.dev/agency-site-kit.

    Install

    pnpm add @sonordev/agency-site-kit @sonordev/site-kit

    @sonordev/site-kit (>= 6.5.0) is a peer dependency. It carries the shared Sonor data plane and the portfolio contract the kit's proof rules build on. Next.js >= 14 and React >= 18 are peers too.

    You need one environment variable, in .env.local:

    SONOR_API_KEY=sonor_xxxxxxxx_xxxxxxxxxxxx

    It's read on the server only. There's no NEXT_PUBLIC_ variant and no project ID: Sonor works out the project from the key.

    Quickstart

    From your Next.js project root:

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

    That writes the /work index, category pages, case study pages, a starter renderer, the webhook Sonor calls when content changes and the dashboard's draft preview route. It never overwrites a file, so it's safe to run again. Then wrap your root layout in AgencySiteKitLayout and add your case studies to the sitemap. The Quickstart walks through every flag and file.

    A case study page

    // app/work/[slug]/page.tsx
    import { notFound } from 'next/navigation';
    import { curatePortfolioProof, formatMetricValue, metricNote, sectionData } from '@sonordev/agency-site-kit/portfolio';
    import { getPortfolioItem } from '@sonordev/agency-site-kit/portfolio/server';
    
    export default async function CaseStudyPage({ params }: { params: Promise<{ slug: string }> }) {
      const { slug } = await params;
      const item = await getPortfolioItem(slug);
      if (!item) notFound(); // null means it doesn't exist; an outage throws instead
    
      const hero = sectionData(curatePortfolioProof(item), 'portfolioHero');
    
      return (
        <article>
          <h1>{hero?.headline ?? item.title}</h1>
          <dl>
            {hero?.kpis.map((kpi) => {
              const note = metricNote(kpi, 'short');
              return (
                <div key={kpi.label}>
                  <dt>{kpi.label}</dt>
                  <dd>{formatMetricValue(kpi)}</dd>
                  {note ? <dd>{note}</dd> : null}
                </div>
              );
            })}
          </dl>
        </article>
      );
    }

    metricNote is the line a number wears when you didn't measure it: per Northwind Engineering on a client-reported number, Estimated on an estimate, nothing on a measured one.

    What's in the box

    AreaWhat you getDocs
    ProofcuratePortfolioProof, metricNote, headlineKpi, gateMetricsDeltas, formatMetricValue and the measured / reported / estimated modelProof and provenance
    SectionsorderedSections, sectionData, projectBrandColor, and the one-laptop-or-three-devices decisionSections and devices
    Images and labelsresolvePortfolioImage, category, service and date formatting, path sanitizingImages, labels and paths
    Structured databuildPortfolioJsonLd and jsonLdStringJSON-LD
    DataThe server fetchers, metadata, static params and sitemap helpers, and the error contractFetching, Types
    BrandAgencySiteKitLayout, which publishes your brand as --sk-* CSS variablesBrand layout
    Live updatescreateRevalidateHandler and createPortfolioPreviewHandlerLive updates and preview
    Device framesShowing a client's live site inside a device frameLive device frames

    Where to import from

    • @sonordev/agency-site-kit/portfolio: the rules and the types. Pure, so it imports from a server component, a client component, a plain module or a test.
    • @sonordev/agency-site-kit/portfolio/server: the fetchers and Next.js helpers, plus the same rules. Server only.
    • @sonordev/agency-site-kit: everything, including AgencySiteKitLayout. Server only.

    The Subpath map lists every entry and what it exports.

    Keeping up to date

    The kit is pre-1.0, so breaking changes can land in a minor release. The changelog says when one does.

    A new release doesn't reach your site on its own. Your lockfile pins the version and your host installs from it, so run pnpm update @sonordev/agency-site-kit and commit the new lockfile. The build succeeds either way and the site looks the same, so an update that never landed is easy to miss. Install from the npm registry, never as a link: dependency: a link works locally and fails on a fresh clone.

    License

    MIT