@sonordev/site-kit 7.0.1 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/CHANGELOG.md +3606 -0
  2. package/README.md +12 -13
  3. package/agent-manifest.json +11 -5
  4. package/dist/{AnalyticsProvider-EMM2TKRE.js → AnalyticsProvider-ZMQUV33M.js} +4 -4
  5. package/dist/{ArticleViewTracker-RA64BGL6.js → ArticleViewTracker-V7NUXYBY.js} +3 -3
  6. package/dist/{BlocksPopup-D25RFNOV.js → BlocksPopup-52EU7OUY.js} +4 -4
  7. package/dist/{ChatWidget-RYI7BMJJ.js → ChatWidget-5BHMNR57.js} +5 -5
  8. package/dist/{EngageWidget-UKFCN33M.js → EngageWidget-PCGLX7SO.js} +4 -4
  9. package/dist/{FileField-MUHA7LZR.js → FileField-TSFGNAMY.js} +3 -3
  10. package/dist/{FormSpotlight-TCLPWPLL.js → FormSpotlight-XLBWEOTE.js} +1 -1
  11. package/dist/{FormStage-CNYLP6I6.js → FormStage-IJQ5Q2X6.js} +1 -1
  12. package/dist/{ManagedForm-7ZL5SKTO.js → ManagedForm-Z3PKOPIZ.js} +6 -6
  13. package/dist/{ManagedNewsletterForm-33B4JLX7.js → ManagedNewsletterForm-QAO3POLC.js} +4 -4
  14. package/dist/{SignalCore-L5FVDHFE.js → SignalCore-RBA3VDBL.js} +3 -3
  15. package/dist/{SiteDesignReporter-4JOFL4FP.js → SiteDesignReporter-C4LR5X2V.js} +5 -5
  16. package/dist/SitemapSync-XVMGKCF3.js +8 -0
  17. package/dist/_client/booking-widget.js +5 -5
  18. package/dist/affiliates/index.js +3 -3
  19. package/dist/analytics/index.js +4 -4
  20. package/dist/articles/index.js +1 -1
  21. package/dist/articles/server-ui.js +1 -1
  22. package/dist/chat/index.js +5 -5
  23. package/dist/{chunk-FYBZ5SNP.js → chunk-3G2SE2J4.js} +1 -1
  24. package/dist/{chunk-HGCK465A.js → chunk-3J2ERO3I.js} +1 -1
  25. package/dist/{chunk-KXPBMCFL.js → chunk-3QI26673.js} +3 -1
  26. package/dist/{chunk-BMO3VGMR.js → chunk-3XPJKZ6D.js} +30 -7
  27. package/dist/{chunk-QGHSMJKW.js → chunk-4JQQDCMO.js} +1 -1
  28. package/dist/{chunk-6HDT4G4A.js → chunk-4YTYGG2C.js} +2 -2
  29. package/dist/{chunk-N2UVOR3X.js → chunk-662ILEZ6.js} +2 -0
  30. package/dist/chunk-6G43IRWR.js +4 -0
  31. package/dist/{chunk-LVESVYCE.js → chunk-7MHHWZKC.js} +11 -117
  32. package/dist/{chunk-MV2MBTC3.js → chunk-7QTMMHUO.js} +1 -1
  33. package/dist/{chunk-KPAZG65P.js → chunk-CGWUXUYZ.js} +138 -46
  34. package/dist/{chunk-4IQ52CXL.js → chunk-DUAO4Q75.js} +2 -2
  35. package/dist/{chunk-4RMVXRBO.js → chunk-EIULXXUJ.js} +3 -3
  36. package/dist/{chunk-3KUUH2YP.js → chunk-EVFZ7KEW.js} +1 -1
  37. package/dist/{chunk-OFOAHPUV.js → chunk-F42R35NV.js} +1 -1
  38. package/dist/{chunk-QANVUXKH.js → chunk-FLR3EMK6.js} +1 -1
  39. package/dist/{chunk-P4GRY6QP.js → chunk-GIAOPEN6.js} +1 -1
  40. package/dist/{chunk-P5J7VMQ3.js → chunk-GWUKQ26F.js} +1 -1
  41. package/dist/{chunk-TT63HHIT.js → chunk-HAG4YIZY.js} +1 -1
  42. package/dist/{chunk-QZZIKMAT.js → chunk-HVH37YPX.js} +1 -1
  43. package/dist/{chunk-SSUQKA7L.js → chunk-J4D6ZXRW.js} +1 -1
  44. package/dist/{chunk-GYESATRY.js → chunk-L2DJD5Y4.js} +1 -1
  45. package/dist/{chunk-WATH55UY.js → chunk-LFXVE32I.js} +1 -1
  46. package/dist/chunk-LPH5FANE.js +169 -0
  47. package/dist/{chunk-V6LSQRTH.js → chunk-PLUP2KN5.js} +1 -1
  48. package/dist/chunk-RYVDGXC2.js +19 -0
  49. package/dist/{chunk-EGOD74PP.js → chunk-U35H2JIQ.js} +2 -2
  50. package/dist/chunk-VCJYLYJV.js +49 -0
  51. package/dist/{chunk-FL4EPUWA.js → chunk-W2CL2DB3.js} +2 -2
  52. package/dist/{chunk-UZN4ZYR2.js → chunk-XD3ZQET6.js} +1 -1
  53. package/dist/{chunk-CVTVNC2U.js → chunk-XNVSCQ2O.js} +2 -2
  54. package/dist/{chunk-T3MC4HOD.js → chunk-YLSEB32F.js} +1 -1
  55. package/dist/chunk-ZETJTCMV.js +118 -0
  56. package/dist/{chunk-5SEM2V4A.js → chunk-ZIMFQWGJ.js} +3 -3
  57. package/dist/client/index.js +3 -3
  58. package/dist/cms/CmsPage.d.ts +1 -0
  59. package/dist/cms/CmsPreview.d.ts +1 -0
  60. package/dist/cms/CmsSection.d.ts +1 -0
  61. package/dist/cms/index.d.ts +6 -0
  62. package/dist/cms/server-api.d.ts +3 -0
  63. package/dist/commerce/index.js +4 -4
  64. package/dist/config/index.js +1 -1
  65. package/dist/contracts/entries.d.ts +1 -1
  66. package/dist/contracts/site-cache.d.ts +55 -0
  67. package/dist/contracts/site-edit-param.d.ts +7 -0
  68. package/dist/contracts/site-edit.d.ts +77 -0
  69. package/dist/contracts/slot-content.d.ts +111 -0
  70. package/dist/contracts/slots.d.ts +39 -25
  71. package/dist/engage/index.js +6 -6
  72. package/dist/fleet/index.js +4 -4
  73. package/dist/forms/index.js +8 -8
  74. package/dist/forms/server.js +2 -2
  75. package/dist/forms/types.d.ts +3 -1
  76. package/dist/images/index.js +4 -4
  77. package/dist/index.js +1 -1
  78. package/dist/layout/client.js +8 -7
  79. package/dist/layout/index.js +9 -8
  80. package/dist/llms/index.js +4 -2
  81. package/dist/llms/seo-revalidate.d.ts +8 -1
  82. package/dist/maps/index.js +3 -3
  83. package/dist/mcp/sonor.js +6 -6
  84. package/dist/overlay-RXV6U6QC.js +353 -0
  85. package/dist/proxy/index.js +2 -2
  86. package/dist/proxy/securityHeaders.d.ts +4 -0
  87. package/dist/revalidate/index.d.ts +44 -0
  88. package/dist/revalidate/index.js +27 -0
  89. package/dist/seo/ManagedContent.d.ts +2 -0
  90. package/dist/seo/client.js +4 -4
  91. package/dist/seo/index.js +9 -8
  92. package/dist/seo/llms.js +4 -2
  93. package/dist/seo/register-sitemap-cli.js +1 -1
  94. package/dist/seo/server.js +3 -2
  95. package/dist/seo/sitemap.js +2 -2
  96. package/dist/server/index.js +2 -2
  97. package/dist/{server-api-GJJQZVG7.js → server-api-BVCBLJKL.js} +2 -1
  98. package/dist/shared/build-entries.d.ts +1 -0
  99. package/dist/shared/edit-bridge.d.ts +8 -0
  100. package/dist/shared/version.d.ts +1 -1
  101. package/dist/signal/index.js +2 -2
  102. package/dist/sitemap/index.js +2 -2
  103. package/dist/slots/ManagedLink.d.ts +31 -0
  104. package/dist/slots/ManagedList.d.ts +30 -0
  105. package/dist/slots/ManagedRichText.d.ts +31 -0
  106. package/dist/slots/contract.js +2 -1
  107. package/dist/slots/edit/locate.d.ts +30 -0
  108. package/dist/slots/edit/overlay.d.ts +18 -0
  109. package/dist/slots/index.d.ts +12 -4
  110. package/dist/slots/index.js +4 -2
  111. package/dist/slots/revalidate.d.ts +8 -3
  112. package/dist/slots/rich.d.ts +7 -0
  113. package/dist/slots/server-api.d.ts +6 -2
  114. package/dist/sync/index.js +5 -5
  115. package/dist/website/images.js +4 -4
  116. package/dist/website/index.js +5 -5
  117. package/dist/website/popups.js +4 -4
  118. package/dist/website/slots/contract.js +2 -1
  119. package/dist/website/slots.js +4 -2
  120. package/dist/{writeLLMsTxt-UMHKGNRR.js → writeLLMsTxt-QR23OQUE.js} +1 -1
  121. package/docs/MIGRATING-TO-7.md +146 -0
  122. package/docs.json +69 -0
  123. package/package.json +14 -4
  124. package/src/admin-auth/README.md +88 -0
  125. package/src/analytics/README.md +264 -0
  126. package/src/articles/README.md +325 -0
  127. package/src/commerce/README.md +109 -0
  128. package/src/cta-bar/README.md +154 -0
  129. package/src/engage/README.md +241 -0
  130. package/src/forms/README.md +219 -0
  131. package/src/images/README.md +74 -0
  132. package/src/layout/README.md +66 -0
  133. package/src/llms/README.md +723 -0
  134. package/src/mcp/README.md +376 -0
  135. package/src/motion/README.md +372 -0
  136. package/src/og/README.md +304 -0
  137. package/src/proxy/README.md +152 -0
  138. package/src/redirects/README.md +74 -0
  139. package/src/reputation/README.md +64 -0
  140. package/src/revalidate/README.md +82 -0
  141. package/src/seo/README.md +346 -0
  142. package/src/signal/README.md +115 -0
  143. package/src/sitemap/README.md +127 -0
  144. package/src/slots/README.md +168 -0
  145. package/src/sync/README.md +115 -0
  146. package/dist/SitemapSync-7WKY4HXI.js +0 -8
  147. package/dist/chunk-SS636UDN.js +0 -35
@@ -0,0 +1,152 @@
1
+ # Proxy — `@sonordev/site-kit/proxy`
2
+
3
+ Composable Next.js Proxy factory. Zero-config redirects + security headers. Opt-in AI discovery headers.
4
+
5
+ Next 16 renamed the `middleware` file convention to `proxy`. Through 7.x this
6
+ module is also served on `@sonordev/site-kit/middleware`, with `createMiddleware`
7
+ as an alias of `createProxy`; both go in 8.0. `npx sonor-setup codemod --write`
8
+ moves a site over (the file too).
9
+
10
+ ## Usage
11
+
12
+ ```ts
13
+ // proxy.ts — at the project root, or in src/
14
+ import { createProxy } from '@sonordev/site-kit/proxy'
15
+
16
+ export default createProxy()
17
+
18
+ // Inlined on purpose. See "The matcher must be inlined" below — importing
19
+ // siteKitMatcher here is a build error.
20
+ export const config = {
21
+ matcher: [
22
+ '/((?!_next/static|_next/image|favicon\\.ico|.*\\.(?:ico|png|jpg|jpeg|gif|webp|svg|woff2?)$).*)',
23
+ ],
24
+ }
25
+ ```
26
+
27
+ With options:
28
+
29
+ ```ts
30
+ export default createProxy({
31
+ securityHeaders: { frameAncestors: ["'self'", 'https://partner.example.com'] },
32
+ llmsDiscovery: { siteUrl: 'https://example.com' },
33
+ before: (req) => {
34
+ // Custom auth check, geolocation, etc.
35
+ },
36
+ })
37
+ ```
38
+
39
+ ## The matcher must be inlined
40
+
41
+ **`export const config = siteKitMatcher` is a build error.** Next statically
42
+ parses `config.matcher` at build time and Turbopack rejects any imported value,
43
+ so the array has to be a literal in your own file. Copy it from the example
44
+ above, or read it off `siteKitMatcher` — but paste the contents, don't export
45
+ the binding.
46
+
47
+ `siteKitMatcher` exists so the canonical pattern lives in one place that the
48
+ scaffold, the docs, and the tests all read. It is a **reference value**.
49
+
50
+ ## Migrating from `middleware.ts`
51
+
52
+ ```bash
53
+ npx sonor-setup next16
54
+ ```
55
+
56
+ Runs the codemod's `next-16-proxy` transform (deterministic and offline: it
57
+ moves the file, renames a named `middleware` export to `proxy`, and never
58
+ moves onto an existing `proxy.ts`) and re-runs the doctor check afterwards, so
59
+ the result is verified rather than assumed. Safe to run twice. A file that
60
+ sets `runtime` is flagged rather than moved (see below).
61
+
62
+ Two Next 16 traps to know:
63
+
64
+ - **No `runtime` export.** Proxy defaults to the Node.js runtime and the
65
+ `runtime` config option is unavailable in proxy files — setting it throws at
66
+ build time. (`middleware.ts` still accepts `runtime: 'nodejs'`, and it does
67
+ run on Netlify; remove the export before moving the file.)
68
+ - **The matcher**, as above.
69
+
70
+ ## Config
71
+
72
+ ```ts
73
+ interface SiteKitProxyConfig {
74
+ redirects?: boolean | RedirectConfig // Sonor-managed redirects (default: true)
75
+ securityHeaders?: boolean | SecurityHeadersConfig // Security headers (default: true)
76
+ llmsDiscovery?: LlmsDiscoveryConfig | false // AI crawler discovery header
77
+ identity?: boolean | IdentityConfig // Opt-in edge identity pass
78
+
79
+ before?: (req: NextRequest) => NextResponse | undefined | Promise<NextResponse | undefined>
80
+ after?: (req: NextRequest, res: NextResponse) => NextResponse | Promise<NextResponse>
81
+ }
82
+
83
+ interface LlmsDiscoveryConfig {
84
+ siteUrl: string // e.g. 'https://example.com'
85
+ llmsPath?: string // Default: '/llms.txt'
86
+ mcpServerCard?: boolean | string // Also link the MCP server card (rel="service-desc"); true = '/.well-known/mcp-server-card'
87
+ }
88
+ ```
89
+
90
+ `redirects: true` (the default) checks managed rules before each page render.
91
+ The rule list is cached in memory for five minutes per instance, and a fetch is
92
+ bounded at 300ms with a 30s cooldown after a failure, so page loads rarely wait
93
+ on it. Set `redirects: false` only on a site that uses no Sonor-managed
94
+ redirects.
95
+
96
+ Don't resolve redirects in `app/not-found.tsx` with `resolveManagedRedirect()`.
97
+ Next renders the root not-found boundary inside every page, so its `headers()`
98
+ call makes every route dynamic: on a Next 16 build every static route turned
99
+ into a dynamic one. The resolver is deprecated.
100
+
101
+ ## What It Handles (in order)
102
+
103
+ 1. **Before hook** — optional custom logic (return `NextResponse` to short-circuit)
104
+ 2. **Sonor-managed redirects** — 301/302/307/308 with query param preservation
105
+ 3. **Security headers** — see the table below
106
+ 4. **AI discovery header** — `Link: <url>; rel="describedby"; type="text/markdown"` on every request that
107
+ could be for a page: GET or HEAD, an Accept header that allows HTML (or no Accept header at all, which is
108
+ how curl and many crawlers ask), not a file-like path (`/llms.txt`, `/sitemap.xml`), not `/api/`, and not
109
+ a Next RSC navigation request. See `wantsLlmsDiscoveryLink` in `@sonordev/site-kit/llms`.
110
+ 5. **After hook** — mutate response headers
111
+
112
+ ## Security Headers (defaults)
113
+
114
+ | Header | Value |
115
+ |--------|-------|
116
+ | X-DNS-Prefetch-Control | `on` |
117
+ | Content-Security-Policy | `frame-ancestors 'self' https://upforge.io https://*.upforge.io` |
118
+ | X-Content-Type-Options | `nosniff` |
119
+ | X-XSS-Protection | `1; mode=block` |
120
+ | Referrer-Policy | `strict-origin-when-cross-origin` |
121
+ | Permissions-Policy | `camera=(), microphone=(), geolocation=()` |
122
+
123
+ Note there is no `X-Frame-Options` row — that is deliberate, see below.
124
+
125
+ ### Framing / `frame-ancestors`
126
+
127
+ Upforge showcases live client work in iframes on its portfolio and area
128
+ pages, so managed sites ship a CSP `frame-ancestors` allowlist instead of
129
+ `X-Frame-Options`. XFO has no allowlist form (`ALLOW-FROM` is dead), and
130
+ emitting both would let a stray `DENY` silently re-block the embed — so
131
+ `X-Frame-Options` is **omitted** whenever `frameAncestors` is active.
132
+ Every origin not on the list is still blocked.
133
+
134
+ ```ts
135
+ createProxy({
136
+ // extend the allowlist
137
+ securityHeaders: {
138
+ frameAncestors: [
139
+ "'self'",
140
+ 'https://upforge.io',
141
+ 'https://*.upforge.io',
142
+ 'https://partner.example.com',
143
+ ],
144
+ },
145
+ })
146
+
147
+ // opt out entirely → falls back to X-Frame-Options: DENY
148
+ createProxy({ securityHeaders: { frameAncestors: false } })
149
+ ```
150
+
151
+ `DEFAULT_FRAME_ANCESTORS` is the single source of truth — add origins there
152
+ rather than re-forking the list into individual sites.
@@ -0,0 +1,74 @@
1
+ # Redirects — `@sonordev/site-kit/redirects`
2
+
3
+ Sonor-managed 301/302/307/308 redirect rules. Used by `createProxy()` or standalone.
4
+
5
+ ## Usage
6
+
7
+ Automatically handled when using `createProxy()`:
8
+
9
+ ```ts
10
+ // proxy.ts
11
+ import { createProxy } from '@sonordev/site-kit/proxy'
12
+
13
+ export default createProxy() // redirects: true by default
14
+
15
+ // Inlined: an imported `config.matcher` is a build error. See the proxy README.
16
+ export const config = {
17
+ matcher: [
18
+ '/((?!_next/static|_next/image|favicon\\.ico|.*\\.(?:ico|png|jpg|jpeg|gif|webp|svg|woff2?)$).*)',
19
+ ],
20
+ }
21
+ ```
22
+
23
+ For standalone use:
24
+
25
+ ```ts
26
+ import { handleManagedRedirects } from '@sonordev/site-kit/redirects'
27
+
28
+ export async function middleware(request: NextRequest) {
29
+ const redirect = await handleManagedRedirects(request, {})
30
+ if (redirect) return redirect
31
+ return NextResponse.next()
32
+ }
33
+ ```
34
+
35
+ ## API
36
+
37
+ ```ts
38
+ handleManagedRedirects(request: NextRequest, config: RedirectConfig): Promise<NextResponse | undefined>
39
+ fetchRedirectRules(config: RedirectConfig): Promise<RedirectRule[]>
40
+ generateNextRedirects(config: RedirectConfig): Promise<Redirect[]> // For next.config.js
41
+ clearRedirectCache(): void // Dev helper
42
+ ```
43
+
44
+ ## Config
45
+
46
+ ```ts
47
+ interface RedirectConfig {
48
+ domain?: string // Resolved from Sonor when using apiKey
49
+ apiKey?: string // Project API key
50
+ site?: string // Multi-site host, sent as ?site= and cached per host (default: NEXT_PUBLIC_SITE_URL host)
51
+ portalApiUrl?: string // Default: https://api.sonor.io
52
+ cacheSeconds?: number // Default: 300 (5 minutes)
53
+ }
54
+ ```
55
+
56
+ ## Behavior
57
+
58
+ 1. Checks in-memory cache first
59
+ 2. Fetches rules from Sonor API if expired
60
+ 3. Matches pathname (exact or trailing-slash variant)
61
+ 4. Preserves query parameters on redirect
62
+ 5. Tracks redirect hit (fire-and-forget)
63
+ 6. Skips static assets and API routes
64
+
65
+ ## Types
66
+
67
+ ```ts
68
+ interface RedirectRule {
69
+ from_path: string
70
+ to_path: string
71
+ redirect_type: '301' | '302' | '307' | '308'
72
+ is_enabled: boolean
73
+ }
74
+ ```
@@ -0,0 +1,64 @@
1
+ # Reputation — `@sonordev/site-kit/reputation`
2
+
3
+ Display client reviews, testimonials, and rating statistics from Sonor.
4
+
5
+ ## Usage
6
+
7
+ ```tsx
8
+ import { TestimonialSection } from '@sonordev/site-kit/reputation'
9
+
10
+ export default function ReviewsPage() {
11
+ return (
12
+ <TestimonialSection
13
+ title="What Our Clients Say"
14
+ showRating
15
+ maxReviews={6}
16
+ />
17
+ )
18
+ }
19
+ ```
20
+
21
+ ## TestimonialSection Props
22
+
23
+ ```ts
24
+ interface TestimonialSectionProps {
25
+ title?: string
26
+ subtitle?: string
27
+ autoplay?: boolean // Auto-rotate reviews
28
+ autoplayInterval?: number // ms between rotations
29
+ showRating?: boolean // Display star ratings
30
+ maxReviews?: number // Limit displayed count
31
+ featuredOnly?: boolean // Show featured reviews only
32
+ service?: string // Filter by service tag
33
+ className?: string
34
+ }
35
+ ```
36
+
37
+ ## API Functions
38
+
39
+ ```ts
40
+ import { fetchReviews, fetchReviewStats } from '@sonordev/site-kit/reputation'
41
+
42
+ const reviews = await fetchReviews({ service: 'divorce', limit: 10, featured: true })
43
+ const stats = await fetchReviewStats() // { total_reviews, average_rating, distribution }
44
+ ```
45
+
46
+ ## Types
47
+
48
+ ```ts
49
+ interface Review {
50
+ id: string; quote: string; name: string; role?: string;
51
+ rating: number; image?: string; date?: string;
52
+ platform?: string; isFeatured?: boolean; serviceTags?: string[];
53
+ }
54
+
55
+ interface ReviewStats {
56
+ total_reviews: number
57
+ average_rating: number
58
+ distribution: { 1: number; 2: number; 3: number; 4: number; 5: number }
59
+ }
60
+ ```
61
+
62
+ ## Caching
63
+
64
+ Uses in-memory cache (60s TTL) with retry-on-429 backoff to prevent rate limiting during dev hot reload.
@@ -0,0 +1,82 @@
1
+ # Live updates
2
+
3
+ Your pages stay cached, and when someone changes content in Sonor, the pages
4
+ that use it refresh within seconds. It takes one route file:
5
+
6
+ ```ts
7
+ // app/api/seo-revalidate/route.ts
8
+ export { POST } from '@sonordev/site-kit/revalidate'
9
+ ```
10
+
11
+ `npx sonor-setup scaffold` writes it for you, and `npx sonor-setup doctor`
12
+ tells you when it's missing.
13
+
14
+ ## How it works
15
+
16
+ Everything site-kit fetches from Sonor is cached, and pages built from those
17
+ fetches are served from your host's CDN. When content changes in Sonor
18
+ (managed copy, a page's title or description, an article, a portfolio item),
19
+ Sonor sends this route the paths and cache tags that changed. The route checks
20
+ the call was signed with your project's API key, then expires only those
21
+ pages and tags. The next visitor gets the new version, and every other page
22
+ stays cached.
23
+
24
+ It's the same `SONOR_API_KEY` the rest of site-kit reads, so there's no
25
+ extra secret to set. Sonor finds the route on its own: the first sitemap sync
26
+ after a deploy registers `https://your-domain/api/seo-revalidate` and checks
27
+ that it answers.
28
+
29
+ Without the route, nothing breaks. Edits still show up, but only once each
30
+ cached fetch ages out: about five minutes for managed copy, up to a day for
31
+ metadata.
32
+
33
+ ## Sites with a publication
34
+
35
+ If your site publishes articles, tell the route where they live so the index
36
+ and feeds refresh with each article:
37
+
38
+ ```ts
39
+ // app/api/seo-revalidate/route.ts
40
+ import { createRevalidateRoute } from '@sonordev/site-kit/revalidate'
41
+
42
+ export const POST = createRevalidateRoute({ publicationBasePath: '/insights' })
43
+ ```
44
+
45
+ | Option | What it does |
46
+ |---|---|
47
+ | `publicationBasePath` | Where your articles live, e.g. `/insights`. The index and its RSS and Atom feeds refresh on every call. |
48
+ | `extraPaths` | Local paths to refresh on every call, e.g. a hub page like `/work`. |
49
+ | `extendPayload` | Map extra fields Sonor sends to more paths or tags. The result is validated again, so it can't widen what a caller may refresh. |
50
+ | `secret` | The key calls are signed with. Defaults to `SONOR_API_KEY`, read on every call. |
51
+
52
+ ## What Sonor sends
53
+
54
+ A POST with `Authorization: Bearer <SONOR_API_KEY>` and a JSON body:
55
+
56
+ ```json
57
+ { "paths": ["/services/roofing"], "tags": ["sonor-slots"] }
58
+ ```
59
+
60
+ | Field | Meaning |
61
+ |---|---|
62
+ | `paths` | Local paths to regenerate. `/sitemap.xml`, `/llms.txt` and `/llms-full.txt` are always refreshed too. |
63
+ | `tags` | Cache tags to expire. The ones Sonor uses are `SITE_CACHE_TAGS`: `sonor-slots` (managed copy), `seo` (metadata, schema, FAQs), `blog` (articles), `editorial-taxonomy` and `portfolio`. |
64
+ | `revalidateAll` | Regenerate every page. |
65
+ | `ping` | `{ "ping": true }` on its own regenerates nothing and answers `{ "ok": true, "ping": true, "version": "…" }`. Sonor uses it to confirm the route is installed. |
66
+
67
+ The route refuses a bad key (401), a body over 16 KB (413) and any path that
68
+ isn't a plain local path or any malformed tag (400). A refused call refreshes
69
+ nothing.
70
+
71
+ ## Checking it
72
+
73
+ ```bash
74
+ curl -s -X POST https://your-domain/api/seo-revalidate \
75
+ -H "Authorization: Bearer $SONOR_API_KEY" \
76
+ -H "Content-Type: application/json" \
77
+ -d '{"ping":true}'
78
+ ```
79
+
80
+ `{"ok":true,"ping":true,...}` means Sonor's edits will go live in seconds. A
81
+ 404 means the route isn't deployed. A 401 means the key on the site doesn't
82
+ match the project's key.