npm.io
0.21.3 • Published 11h agoCLI

@soloworks/smking-next

Licence
MIT
Version
0.21.3
Deps
0
Size
136 kB
Vulns
0
Weekly
0

@soloworks/smking-next

AI-native SEO (AEO) for Next.js. One server component injects JSON-LD, OG tags, AI summary, and FAQ on every page so AI crawlers (ChatGPT, Perplexity, Google AI) can cite your content.

  • One server component in your root layout — every URL gets its own AEO content automatically (/products/nike-air, /products/adidas/red, anything dynamic, no codemod needed).
  • Fail-fast, fail-open. 2-second timeout + Next.js ISR — if smking is down, your page renders without injection. Never blocks.
  • Push updates — webhook handler invalidates only the changed paths via revalidateTag.

Install

Don't follow this README to install. Your smking dashboard generates a per-site install prompt with the real SMKING_API_KEY, SMKING_BASE_URL, and (if you use CMS) SMKING_WEBHOOK_SECRET baked in, plus copy-pasteable layout / route shims. The prompt is the source of truth and stays in sync with the SDK version.

Two ways to get it:

# Option 1 — one-shot wizard (installs deps + writes env + runs doctor)
npx @soloworks/smking-wizard

# Option 2 — copy the prompt manually from your smking dashboard's
# install panel into your editor / coding agent.

The wizard owns: pnpm add @soloworks/smking-next, <SmkingAEO /> mount in app/layout.tsx, env writes, app/api/smking/webhook/route.ts shim, and doctor verification.

How metadata wins / loses

Both <SmkingAEO /> and your own generateMetadata emit head tags. Next.js + React 19 head dedup applies last-write-wins:

  • No generateMetadata → smking's <title> / og:* are used.
  • You write generateMetadata in a layout / page → your tags override smking's for that route segment.

This is the pattern: smking provides AEO/SEO baseline, you override per-page when you want. No HOF, no codemod.

For client pages ('use client') that need dynamic metadata, write a sibling layout.tsx with generateMetadata — standard Next.js workflow, unrelated to smking.

How outage tolerance works

getAeoContent wraps fetch with AbortSignal.timeout(2000) and Next.js ISR (next: { revalidate: 3600, tags: ['smking:path:<path>'] }):

  • Cache hit (the common path): zero network. Tags allow webhook-driven invalidation.
  • Cache miss + smking healthy: one network roundtrip, response cached for 1h.
  • Cache miss + smking down / hung: returns null after at most 2s, page renders without injection. Next.js ISR retries on the next request after revalidate.
  • 5xx / 4xx / parse error: same fail-open path.

No circuit breaker, no retry, no status command — Next.js infrastructure already covers what those would do.

API

<SmkingAEO /> props
interface SmkingAEOProps {
  apiKey: string;        // required
  baseUrl?: string;      // override SMKING_BASE_URL env
  path?: string;         // explicit path; auto-resolved from headers() otherwise
  revalidate?: number;   // ISR seconds; default 3600 (1h)
}

Path auto-detection works for any URL schema (/products/[slug], /shop/[cat]/[id]/[variant]). Pass path explicitly only for static-export contexts where headers() is unavailable.

getAeoContent(params)

Lower-level helper if you want to fetch the AEO response and render yourself. Same params, returns Promise<AeoResponse | null>.

import { getAeoContent } from '@soloworks/smking-next';

const aeo = await getAeoContent({ apiKey: ..., path: '/products/abc' });
if (aeo?.status === 'ready') {
  // aeo.jsonLd, aeo.faq, aeo.summary, aeo.seo, aeo.chatLinks, ...
}
Webhook payload
POST /api/smking-revalidate
Authorization: Bearer <SMKING_WEBHOOK_TOKEN>
Content-Type: application/json

{ "paths": ["/products/abc", "/products/xyz"] }

Response: { revalidated: number, errors: number }. errors > 0 means some tags couldn't be revalidated (others still succeeded — partial-success delivery).

Mount the runtime once (v0.16.1+)

Add <SmkingRuntime /> once in your root layout — it emits a <link> to the saas-served CSS and a <script async> to the bundled Web Component runtime IIFE. Browser caches both per saas-controlled stale-while-revalidate headers, so the cost amortises across every <SmkingCms> instance on the page.

// app/layout.tsx
import { SmkingRuntime } from "@soloworks/smking-next";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <SmkingRuntime />
        {children}
      </body>
    </html>
  );
}

Optional baseUrl prop overrides process.env.SMKING_BASE_URL. Default: https://smking.app.

Without <SmkingRuntime />, <SmkingCms> content still renders but the Tailwind utility classes from the dashboard's cva variants resolve to dead strings — the page reaches the browser unstyled. The wizard installer auto-adds this mount; if you're upgrading manually from < v0.16.1, add the one line above.

CMS rendering (optional, v0.11.0+)

The base install only wires AEO. If you author content in the smking dashboard's CMS and want to render it on your Next.js site, use the <SmkingCms slug="…" /> Server Component.

The SDK does not have a "CMS root" config — you choose any URL prefix (/blog, /knowledge, /shop/articles) and wire your own route. The component takes a slug prop, fetches the published page from ${SMKING_BASE_URL}/api/v1/public/page?slug=…, and renders the Tiptap ProseMirror JSON as <article class="smk-cms">…</article> via @tiptap/static-renderer/pm/react. SEO <title> / <meta> / og:* / canonical hoist into <head> automatically via React 19.

Catch-all route (handles flat + nested slugs)

smking CMS slugs can be nested — e.g. blog/123, blog/seo/intro. Use Next.js catch-all [...slug] (three dots, not single [slug]) so one route handles every depth:

// app/blog/[...slug]/page.tsx
import { SmkingCms } from "@soloworks/smking-next/cms";

export default async function Page({
  params,
}: {
  params: Promise<{ slug: string[] }>;
}) {
  const { slug } = await params;
  return (
    <SmkingCms
      apiKey={process.env.SMKING_API_KEY!}
      slug={slug.join("/")}  // ← array → "blog/seo/intro" matches dashboard slug format
    />
  );
}

[...slug] accepts both flat (/blog/hello["hello"]) and nested (/blog/seo/intro["seo", "intro"]). The .join("/") reconstructs the dashboard slug string.

Single-bracket [slug] (without the three dots) only matches one segment — pick this if you know your slugs are always flat.

Markup contract for CSS
<article class="smk-cms" data-smking="cms">
  <h1 class="smk-cms__title"></h1>
  <p>standard prose</p>
  <h2>headings</h2>
  <ul><li>lists</li></ul>
  <blockquote></blockquote>
  <pre><code>code blocks</code></pre>
  <a href="…">links</a>
  <img src="…" alt="…">
  <div data-type="gallery"
       data-layout="grid"
       data-columns="3"
       class="smk-gallery smk-gallery--grid">
    <figure class="smk-gallery__item">
      <img src="…" alt="…" loading="lazy">
      <figcaption>optional</figcaption>
    </figure>
  </div>
</article>

Standard nodes inherit your site's prose styling. Gallery is the only opinionated structure — provide CSS for .smk-gallery (typically a grid with grid-template-columns: repeat(var(--smk-gallery-cols, 3), 1fr) since the SDK injects --smk-gallery-cols inline).

Cache invalidation

CMS responses cache for 5 minutes by default via Next.js data cache (tags: ["smking:cms_page:<slug>"]). When you publish, rename, or archive a page in the dashboard, smking SaaS POSTs a signed webhook to https://<your-site>/api/smking/webhook (mount with one-line re-export — see Install). The handler verifies HMAC against SMKING_WEBHOOK_SECRET and calls revalidateTag so the next visitor reads fresh content.

If SMKING_WEBHOOK_SECRET is unset, the webhook route returns 503 and cache invalidation falls back to TTL-based expiry.

Versions

See CHANGELOG.md. Aligned with smking/laravel for SaaS-side parity.

License

MIT

Keywords