@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
generateMetadatain 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