@sonordev/site-kit 2.8.2 → 3.0.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 (117) hide show
  1. package/dist/EngageWidget-LRE4VD7O.js +14 -0
  2. package/dist/{EngageWidget-JIXUBG47.js.map → EngageWidget-LRE4VD7O.js.map} +1 -1
  3. package/dist/EngageWidget-OAOLB6ED.mjs +5 -0
  4. package/dist/{EngageWidget-4W5EM4UO.mjs.map → EngageWidget-OAOLB6ED.mjs.map} +1 -1
  5. package/dist/blog/index.js +2 -1
  6. package/dist/blog/index.js.map +1 -1
  7. package/dist/blog/index.mjs +2 -1
  8. package/dist/blog/index.mjs.map +1 -1
  9. package/dist/chunk-2FEQ6TYD.mjs +37 -0
  10. package/dist/chunk-2FEQ6TYD.mjs.map +1 -0
  11. package/dist/{chunk-5SQ4NRPH.mjs → chunk-3AEOGV6V.mjs} +6 -4
  12. package/dist/chunk-3AEOGV6V.mjs.map +1 -0
  13. package/dist/chunk-662YHKWW.mjs +10 -0
  14. package/dist/chunk-662YHKWW.mjs.map +1 -0
  15. package/dist/{chunk-MNOVPHL6.mjs → chunk-72MRAYAS.mjs} +19 -14
  16. package/dist/chunk-72MRAYAS.mjs.map +1 -0
  17. package/dist/{chunk-5QLM6LZ2.mjs → chunk-APRAGA5G.mjs} +3 -3
  18. package/dist/{chunk-5QLM6LZ2.mjs.map → chunk-APRAGA5G.mjs.map} +1 -1
  19. package/dist/{chunk-MWE2HRPU.js → chunk-D5XTX5EB.js} +19 -14
  20. package/dist/chunk-D5XTX5EB.js.map +1 -0
  21. package/dist/{chunk-HANRPNKG.js → chunk-FMI65E5Y.js} +3 -3
  22. package/dist/{chunk-HANRPNKG.js.map → chunk-FMI65E5Y.js.map} +1 -1
  23. package/dist/{chunk-SLJJJWMV.js → chunk-FPU2QPO4.js} +338 -33
  24. package/dist/chunk-FPU2QPO4.js.map +1 -0
  25. package/dist/{chunk-PKUTYMN7.js → chunk-J25JPU77.js} +2 -2
  26. package/dist/chunk-J25JPU77.js.map +1 -0
  27. package/dist/chunk-J7QI3GDX.js +12 -0
  28. package/dist/chunk-J7QI3GDX.js.map +1 -0
  29. package/dist/chunk-MLJXELPA.js +693 -0
  30. package/dist/chunk-MLJXELPA.js.map +1 -0
  31. package/dist/{chunk-L7XPPFSS.mjs → chunk-QDKQZ5XH.mjs} +2 -2
  32. package/dist/chunk-QDKQZ5XH.mjs.map +1 -0
  33. package/dist/{chunk-IAV32YHL.mjs → chunk-RV45YMBJ.mjs} +338 -33
  34. package/dist/chunk-RV45YMBJ.mjs.map +1 -0
  35. package/dist/chunk-TCVHI3JP.mjs +686 -0
  36. package/dist/chunk-TCVHI3JP.mjs.map +1 -0
  37. package/dist/{chunk-PAF5IGGF.js → chunk-XNUPJC6R.js} +6 -4
  38. package/dist/chunk-XNUPJC6R.js.map +1 -0
  39. package/dist/chunk-YBKALLBH.js +46 -0
  40. package/dist/chunk-YBKALLBH.js.map +1 -0
  41. package/dist/engage/index.d.mts +2 -2
  42. package/dist/engage/index.d.ts +2 -2
  43. package/dist/engage/index.js +5 -4
  44. package/dist/engage/index.js.map +1 -1
  45. package/dist/engage/index.mjs +2 -1
  46. package/dist/engage/index.mjs.map +1 -1
  47. package/dist/forms/index.js +19 -683
  48. package/dist/forms/index.js.map +1 -1
  49. package/dist/forms/index.mjs +4 -676
  50. package/dist/forms/index.mjs.map +1 -1
  51. package/dist/index.d.mts +2 -2
  52. package/dist/index.d.ts +2 -2
  53. package/dist/layout/client.js +2 -2
  54. package/dist/layout/client.mjs +1 -1
  55. package/dist/layout/index.js +5 -5
  56. package/dist/layout/index.mjs +2 -2
  57. package/dist/llms/index.d.mts +18 -4
  58. package/dist/llms/index.d.ts +18 -4
  59. package/dist/llms/index.js +26 -51
  60. package/dist/llms/index.js.map +1 -1
  61. package/dist/llms/index.mjs +16 -41
  62. package/dist/llms/index.mjs.map +1 -1
  63. package/dist/middleware/index.d.mts +62 -1
  64. package/dist/middleware/index.d.ts +62 -1
  65. package/dist/middleware/index.js +105 -0
  66. package/dist/middleware/index.js.map +1 -1
  67. package/dist/middleware/index.mjs +103 -1
  68. package/dist/middleware/index.mjs.map +1 -1
  69. package/dist/seo/index.d.mts +10 -205
  70. package/dist/seo/index.d.ts +10 -205
  71. package/dist/seo/index.js +16 -15
  72. package/dist/seo/index.js.map +1 -1
  73. package/dist/seo/index.mjs +7 -6
  74. package/dist/seo/index.mjs.map +1 -1
  75. package/dist/seo/server.d.mts +2 -106
  76. package/dist/seo/server.d.ts +2 -106
  77. package/dist/seo/server.js +56 -197
  78. package/dist/seo/server.js.map +1 -1
  79. package/dist/seo/server.mjs +2 -183
  80. package/dist/seo/server.mjs.map +1 -1
  81. package/dist/server-l6LMNvF1.d.ts +360 -0
  82. package/dist/server-mX5At_6x.d.mts +360 -0
  83. package/dist/sitemap/index.d.mts +1 -1
  84. package/dist/sitemap/index.d.ts +1 -1
  85. package/dist/sitemap/index.js +2 -2
  86. package/dist/sitemap/index.mjs +1 -1
  87. package/dist/slots/contract.d.mts +83 -0
  88. package/dist/slots/contract.d.ts +83 -0
  89. package/dist/slots/contract.js +41 -0
  90. package/dist/slots/contract.js.map +1 -0
  91. package/dist/slots/contract.mjs +4 -0
  92. package/dist/slots/contract.mjs.map +1 -0
  93. package/dist/slots/index.d.mts +104 -0
  94. package/dist/slots/index.d.ts +104 -0
  95. package/dist/slots/index.js +186 -0
  96. package/dist/slots/index.js.map +1 -0
  97. package/dist/slots/index.mjs +146 -0
  98. package/dist/slots/index.mjs.map +1 -0
  99. package/dist/{types-DYyIAgQg.d.ts → types-DPsbBKiu.d.mts} +1 -1
  100. package/dist/{types-DYyIAgQg.d.mts → types-DPsbBKiu.d.ts} +1 -1
  101. package/dist/{types-BnCwwUX3.d.mts → types-D_1iUxOQ.d.mts} +18 -1
  102. package/dist/{types-BnCwwUX3.d.ts → types-D_1iUxOQ.d.ts} +18 -1
  103. package/dist/{types-J7Z_FqmV.d.mts → types-DhCAWUmd.d.mts} +8 -0
  104. package/dist/{types-0NuBL1Gg.d.ts → types-xcP_owwn.d.ts} +8 -0
  105. package/package.json +11 -1
  106. package/dist/EngageWidget-4W5EM4UO.mjs +0 -4
  107. package/dist/EngageWidget-JIXUBG47.js +0 -13
  108. package/dist/chunk-5SQ4NRPH.mjs.map +0 -1
  109. package/dist/chunk-IAV32YHL.mjs.map +0 -1
  110. package/dist/chunk-L7XPPFSS.mjs.map +0 -1
  111. package/dist/chunk-MNOVPHL6.mjs.map +0 -1
  112. package/dist/chunk-MWE2HRPU.js.map +0 -1
  113. package/dist/chunk-PAF5IGGF.js.map +0 -1
  114. package/dist/chunk-PKUTYMN7.js.map +0 -1
  115. package/dist/chunk-SLJJJWMV.js.map +0 -1
  116. package/dist/routing-C7gmHWm9.d.ts +0 -163
  117. package/dist/routing-trNzR1Pz.d.mts +0 -163
@@ -7,6 +7,76 @@ require('../chunk-JP5WGL75.js');
7
7
  require('../chunk-ZSMWDLMK.js');
8
8
  var server = require('next/server');
9
9
 
10
+ // src/middleware/segment.ts
11
+ var PAID_MEDIUMS = ["cpc", "ppc", "paid", "paidsearch", "paid-search", "display", "cpm", "banner"];
12
+ var SOCIAL_MEDIUMS = ["social", "social-media", "social_media", "sm"];
13
+ var SOCIAL_HOSTS = [
14
+ "facebook.",
15
+ "instagram.",
16
+ "linkedin.",
17
+ "t.co",
18
+ "twitter.",
19
+ "x.com",
20
+ "pinterest.",
21
+ "tiktok.",
22
+ "youtube.",
23
+ "reddit."
24
+ ];
25
+ var SEARCH_HOSTS = ["google.", "bing.", "duckduckgo.", "yahoo.", "ecosia.", "brave.", "baidu.", "yandex."];
26
+ function resolveVisitorSegment(request, opts) {
27
+ const params = request.nextUrl.searchParams;
28
+ const medium = (params.get("utm_medium") || "").toLowerCase().trim();
29
+ const campaign = params.get("utm_campaign");
30
+ let source = "direct";
31
+ if (PAID_MEDIUMS.includes(medium)) {
32
+ source = "paid";
33
+ } else if (SOCIAL_MEDIUMS.includes(medium)) {
34
+ source = "social";
35
+ } else if (medium === "organic") {
36
+ source = "organic";
37
+ } else if (medium === "referral") {
38
+ source = "referral";
39
+ } else if (params.get("utm_source") || params.get("gclid") || params.get("fbclid")) {
40
+ if (params.get("gclid")) source = "paid";
41
+ else if (params.get("fbclid")) source = "social";
42
+ else source = "referral";
43
+ } else {
44
+ const ref = request.headers.get("referer") || "";
45
+ if (ref) {
46
+ try {
47
+ const host = new URL(ref).host.toLowerCase();
48
+ const selfHost = request.nextUrl.host.toLowerCase();
49
+ if (host && host !== selfHost) {
50
+ if (SEARCH_HOSTS.some((h) => host.includes(h))) source = "organic";
51
+ else if (SOCIAL_HOSTS.some((h) => host.includes(h))) source = "social";
52
+ else source = "referral";
53
+ }
54
+ } catch {
55
+ }
56
+ }
57
+ }
58
+ return {
59
+ visitorType: opts.hadVisitorCookie ? "returning" : "new",
60
+ source,
61
+ campaign: campaign || null
62
+ };
63
+ }
64
+ function encodeSegment(seg) {
65
+ const campaign = (seg.campaign || "").replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 40);
66
+ return `${seg.visitorType}|${seg.source}|${campaign}`;
67
+ }
68
+ function decodeSegment(value) {
69
+ if (!value) return null;
70
+ const [visitorType, source, campaign] = value.split("|");
71
+ if (visitorType !== "new" && visitorType !== "returning") return null;
72
+ return {
73
+ visitorType,
74
+ source: ["paid", "organic", "social", "referral", "direct"].includes(source) ? source : "direct",
75
+ campaign: campaign || null
76
+ };
77
+ }
78
+
79
+ // src/middleware/index.ts
10
80
  function createMiddleware(config) {
11
81
  const securityHeaders = chunk3MYZS6PD_js.buildSecurityHeaders(
12
82
  config?.securityHeaders
@@ -42,12 +112,44 @@ function createMiddleware(config) {
42
112
  applyHeaders(response, discoveryHeaders);
43
113
  }
44
114
  }
115
+ if (config?.identity) {
116
+ const idCfg = typeof config.identity === "object" ? config.identity : {};
117
+ const hadVisitorCookie = !!request.cookies.get("_sk_vid")?.value;
118
+ if (!hadVisitorCookie) {
119
+ const maxAgeDays = idCfg.cookieMaxAgeDays ?? 730;
120
+ response.cookies.set("_sk_vid", generateVisitorId(), {
121
+ maxAge: maxAgeDays * 24 * 60 * 60,
122
+ path: "/",
123
+ sameSite: "lax",
124
+ secure: true
125
+ });
126
+ }
127
+ if (idCfg.segment !== false) {
128
+ const segment = encodeSegment(
129
+ resolveVisitorSegment(request, { hadVisitorCookie })
130
+ );
131
+ response.cookies.set("_sk_seg", segment, {
132
+ // Session-ish: a fresh campaign / source on a later visit re-segments.
133
+ maxAge: 30 * 60,
134
+ path: "/",
135
+ sameSite: "lax",
136
+ secure: true
137
+ });
138
+ }
139
+ }
45
140
  if (config?.after) {
46
141
  response = await config.after(request, response);
47
142
  }
48
143
  return response;
49
144
  };
50
145
  }
146
+ function generateVisitorId() {
147
+ if (typeof crypto !== "undefined" && crypto.randomUUID) return crypto.randomUUID();
148
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
149
+ const r = Math.random() * 16 | 0;
150
+ return (c === "x" ? r : r & 3 | 8).toString(16);
151
+ });
152
+ }
51
153
  function applyHeaders(response, headers) {
52
154
  for (const [key, value] of Object.entries(headers)) {
53
155
  response.headers.set(key, value);
@@ -62,6 +164,9 @@ var createProxy = createMiddleware;
62
164
 
63
165
  exports.createMiddleware = createMiddleware;
64
166
  exports.createProxy = createProxy;
167
+ exports.decodeSegment = decodeSegment;
168
+ exports.encodeSegment = encodeSegment;
169
+ exports.resolveVisitorSegment = resolveVisitorSegment;
65
170
  exports.siteKitMatcher = siteKitMatcher;
66
171
  //# sourceMappingURL=index.js.map
67
172
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/middleware/index.ts"],"names":["buildSecurityHeaders","buildAiDiscoveryHeaders","handleManagedRedirects","NextResponse"],"mappings":";;;;;;;;;AAiFO,SAAS,iBAAiB,MAAA,EAAkC;AACjE,EAAA,MAAM,eAAA,GAAkBA,qCAAA;AAAA,IACtB,MAAA,EAAQ;AAAA,GACV;AAGA,EAAA,MAAM,gBAAA,GACJ,MAAA,EAAQ,aAAA,GACJC,wCAAA,CAAwB;AAAA,IACtB,OAAA,EAAS,OAAO,aAAA,CAAc,OAAA;AAAA,IAC9B,QAAA,EAAU,OAAO,aAAA,CAAc;AAAA,GAChC,IACD,EAAC;AAEP,EAAA,OAAO,eAAe,MACpB,OAAA,EACuB;AAEvB,IAAA,IAAI,QAAQ,MAAA,EAAQ;AAClB,MAAA,MAAM,aAAA,GAAgB,MAAM,MAAA,CAAO,MAAA,CAAO,OAAO,CAAA;AACjD,MAAA,IAAI,aAAA,EAAe;AAEjB,QAAA,YAAA,CAAa,eAAe,eAAe,CAAA;AAC3C,QAAA,OAAO,aAAA;AAAA,MACT;AAAA,IACF;AAGA,IAAA,IAAI,MAAA,EAAQ,cAAc,KAAA,EAAO;AAC/B,MAAA,MAAM,iBACJ,OAAO,MAAA,EAAQ,cAAc,QAAA,GACzB,MAAA,CAAO,YACP,EAAC;AAEP,MAAA,MAAM,WAAW,MAAMC,uCAAA;AAAA,QACrB,OAAA;AAAA,QACA;AAAA,OACF;AACA,MAAA,IAAI,QAAA,EAAU;AAEZ,QAAA,YAAA,CAAa,UAAU,eAAe,CAAA;AACtC,QAAA,OAAO,QAAA;AAAA,MACT;AAAA,IACF;AAGA,IAAA,IAAI,QAAA,GAAWC,oBAAa,IAAA,EAAK;AAGjC,IAAA,YAAA,CAAa,UAAU,eAAe,CAAA;AAGtC,IAAA,IAAI,MAAA,CAAO,IAAA,CAAK,gBAAgB,CAAA,CAAE,SAAS,CAAA,EAAG;AAC5C,MAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,QAAQ,CAAA,IAAK,EAAA;AAChD,MAAA,IAAI,OAAO,QAAA,CAAS,WAAW,KAAK,MAAA,CAAO,QAAA,CAAS,KAAK,CAAA,EAAG;AAC1D,QAAA,YAAA,CAAa,UAAU,gBAAgB,CAAA;AAAA,MACzC;AAAA,IACF;AAGA,IAAA,IAAI,QAAQ,KAAA,EAAO;AACjB,MAAA,QAAA,GAAW,MAAM,MAAA,CAAO,KAAA,CAAM,OAAA,EAAS,QAAQ,CAAA;AAAA,IACjD;AAEA,IAAA,OAAO,QAAA;AAAA,EACT,CAAA;AACF;AAEA,SAAS,YAAA,CACP,UACA,OAAA,EACM;AACN,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,OAAO,CAAA,EAAG;AAClD,IAAA,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,GAAA,EAAK,KAAK,CAAA;AAAA,EACjC;AACF;AAMO,IAAM,cAAA,GAAiB;AAAA,EAC5B,OAAA,EAAS;AAAA,IACP;AAAA;AAEJ;AAGO,IAAM,WAAA,GAAc","file":"index.js","sourcesContent":["/**\n * @sonordev/site-kit/middleware\n *\n * Composable Next.js middleware factory. Handles:\n * - Portal-managed redirects (via existing handleManagedRedirects)\n * - Security headers (X-Frame-Options, CSP, etc.)\n * - Optional before/after hooks for custom logic\n *\n * @example\n * ```ts\n * // middleware.ts — entire file\n * import { createMiddleware, siteKitMatcher } from '@sonordev/site-kit/middleware'\n *\n * export default createMiddleware()\n * export const config = siteKitMatcher\n * ```\n *\n * @example With custom hooks:\n * ```ts\n * export default createMiddleware({\n * securityHeaders: { frameOptions: 'SAMEORIGIN' },\n * before: (req) => {\n * // Custom auth check, etc.\n * },\n * })\n * ```\n */\n\nimport { NextRequest, NextResponse } from 'next/server'\nimport {\n handleManagedRedirects,\n type RedirectConfig,\n} from '../redirects'\nimport {\n buildSecurityHeaders,\n type SecurityHeadersConfig,\n} from './securityHeaders'\nimport { buildAiDiscoveryHeaders } from '../llms/discovery-headers'\n\nexport type { SecurityHeadersConfig } from './securityHeaders'\n\nexport interface LlmsDiscoveryConfig {\n /** Canonical site origin, e.g. `https://example.com` */\n siteUrl: string\n /** Path to llms.txt (default `/llms.txt`) */\n llmsPath?: string\n}\n\nexport interface SiteKitMiddlewareConfig {\n /** Enable Portal-managed redirects. Default: true */\n redirects?: boolean | RedirectConfig\n /** Enable security headers. Default: true */\n securityHeaders?: boolean | SecurityHeadersConfig\n /**\n * Opt-in: inject `Link: <...>; rel=\"describedby\"` header on HTML responses\n * pointing to /llms.txt for AI crawler discovery (RFC 8288).\n * Preferred over visible footer links.\n */\n llmsDiscovery?: LlmsDiscoveryConfig | false\n /**\n * Hook that runs BEFORE redirect check.\n * Return a NextResponse to short-circuit, or undefined to continue.\n */\n before?: (\n request: NextRequest,\n ) => NextResponse | undefined | Promise<NextResponse | undefined>\n /**\n * Hook that runs AFTER all processing.\n * Receives the response that will be sent — mutate headers, etc.\n */\n after?: (\n request: NextRequest,\n response: NextResponse,\n ) => NextResponse | Promise<NextResponse>\n}\n\n/**\n * Create a fully-featured Next.js middleware function.\n *\n * With zero config this handles redirects + security headers automatically.\n */\nexport function createMiddleware(config?: SiteKitMiddlewareConfig) {\n const securityHeaders = buildSecurityHeaders(\n config?.securityHeaders,\n )\n\n // Pre-compute discovery headers (static per middleware instance)\n const discoveryHeaders: Record<string, string> =\n config?.llmsDiscovery\n ? buildAiDiscoveryHeaders({\n siteUrl: config.llmsDiscovery.siteUrl,\n llmsPath: config.llmsDiscovery.llmsPath,\n })\n : {}\n\n return async function proxy(\n request: NextRequest,\n ): Promise<NextResponse> {\n // 1. Run user's 'before' hook\n if (config?.before) {\n const earlyResponse = await config.before(request)\n if (earlyResponse) {\n // Apply security headers even to early responses\n applyHeaders(earlyResponse, securityHeaders)\n return earlyResponse\n }\n }\n\n // 2. Check Portal-managed redirects (unless disabled)\n if (config?.redirects !== false) {\n const redirectConfig: RedirectConfig =\n typeof config?.redirects === 'object'\n ? config.redirects\n : {}\n\n const redirect = await handleManagedRedirects(\n request,\n redirectConfig,\n )\n if (redirect) {\n // Redirects get security headers too\n applyHeaders(redirect, securityHeaders)\n return redirect\n }\n }\n\n // 3. Build the pass-through response\n let response = NextResponse.next()\n\n // 4. Apply security headers\n applyHeaders(response, securityHeaders)\n\n // 5. Apply AI discovery Link header (only on HTML-like requests, not static assets)\n if (Object.keys(discoveryHeaders).length > 0) {\n const accept = request.headers.get('accept') || ''\n if (accept.includes('text/html') || accept.includes('*/*')) {\n applyHeaders(response, discoveryHeaders)\n }\n }\n\n // 6. Run user's 'after' hook\n if (config?.after) {\n response = await config.after(request, response)\n }\n\n return response\n }\n}\n\nfunction applyHeaders(\n response: NextResponse,\n headers: Record<string, string>,\n): void {\n for (const [key, value] of Object.entries(headers)) {\n response.headers.set(key, value)\n }\n}\n\n/**\n * Standard matcher config that excludes static assets and images.\n * Export alongside createProxy/createMiddleware in your proxy.ts.\n */\nexport const siteKitMatcher = {\n matcher: [\n '/((?!_next/static|_next/image|favicon\\\\.ico|.*\\\\.(?:ico|png|jpg|jpeg|gif|webp|svg|woff2?)$).*)',\n ],\n}\n\n/** Next.js 16+ alias — same as createMiddleware, named for the proxy convention. */\nexport const createProxy = createMiddleware\n"]}
1
+ {"version":3,"sources":["../../src/middleware/segment.ts","../../src/middleware/index.ts"],"names":["buildSecurityHeaders","buildAiDiscoveryHeaders","handleManagedRedirects","NextResponse"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @sonordev/site-kit/middleware — visitor segment resolution (the \"one edge pass\").\n *\n * Pure, edge-runtime-safe helpers that classify a request into a coarse visitor\n * segment (new-vs-returning + marketing source + campaign) from the URL's UTM\n * params and the referrer. The middleware writes the result to the `_sk_seg`\n * cookie so server-rendered slots / personalization can vary by segment WITHOUT\n * each one re-parsing the request. Resolved once, at the edge.\n *\n * Kept side-effect-free (no NextResponse, no cookie writes) so it is trivially\n * unit-testable and reusable.\n */\nimport type { NextRequest } from 'next/server'\n\nexport type SegmentSource = 'paid' | 'organic' | 'social' | 'referral' | 'direct'\n\nexport interface VisitorSegment {\n /** `new` on the first visit (no prior `_sk_vid` cookie), else `returning`. */\n visitorType: 'new' | 'returning'\n /** Marketing source bucket derived from UTM medium/source, then referrer. */\n source: SegmentSource\n /** `utm_campaign`, if present (sanitized when encoded). */\n campaign: string | null\n}\n\nconst PAID_MEDIUMS = ['cpc', 'ppc', 'paid', 'paidsearch', 'paid-search', 'display', 'cpm', 'banner']\nconst SOCIAL_MEDIUMS = ['social', 'social-media', 'social_media', 'sm']\nconst SOCIAL_HOSTS = [\n 'facebook.',\n 'instagram.',\n 'linkedin.',\n 't.co',\n 'twitter.',\n 'x.com',\n 'pinterest.',\n 'tiktok.',\n 'youtube.',\n 'reddit.',\n]\nconst SEARCH_HOSTS = ['google.', 'bing.', 'duckduckgo.', 'yahoo.', 'ecosia.', 'brave.', 'baidu.', 'yandex.']\n\n/**\n * Classify `request` into a {@link VisitorSegment}. UTM params win over the\n * referrer; a same-host referrer is ignored (internal navigation, not a source).\n */\nexport function resolveVisitorSegment(\n request: NextRequest,\n opts: { hadVisitorCookie: boolean },\n): VisitorSegment {\n const params = request.nextUrl.searchParams\n const medium = (params.get('utm_medium') || '').toLowerCase().trim()\n const campaign = params.get('utm_campaign')\n\n let source: SegmentSource = 'direct'\n\n if (PAID_MEDIUMS.includes(medium)) {\n source = 'paid'\n } else if (SOCIAL_MEDIUMS.includes(medium)) {\n source = 'social'\n } else if (medium === 'organic') {\n source = 'organic'\n } else if (medium === 'referral') {\n source = 'referral'\n } else if (params.get('utm_source') || params.get('gclid') || params.get('fbclid')) {\n // A tagged link with a non-standard medium: gclid => paid Google, fbclid => social.\n if (params.get('gclid')) source = 'paid'\n else if (params.get('fbclid')) source = 'social'\n else source = 'referral'\n } else {\n // No campaign tagging — fall back to the referrer host.\n const ref = request.headers.get('referer') || ''\n if (ref) {\n try {\n const host = new URL(ref).host.toLowerCase()\n const selfHost = request.nextUrl.host.toLowerCase()\n if (host && host !== selfHost) {\n if (SEARCH_HOSTS.some((h) => host.includes(h))) source = 'organic'\n else if (SOCIAL_HOSTS.some((h) => host.includes(h))) source = 'social'\n else source = 'referral'\n }\n } catch {\n /* malformed referer — stays 'direct' */\n }\n }\n }\n\n return {\n visitorType: opts.hadVisitorCookie ? 'returning' : 'new',\n source,\n campaign: campaign || null,\n }\n}\n\n/**\n * Compact, cookie-safe encoding: `\"<visitorType>|<source>|<campaign>\"`, e.g.\n * `\"new|paid|spring_sale\"`. Campaign is sanitized to `[A-Za-z0-9_-]` and capped.\n * Decode with {@link decodeSegment}.\n */\nexport function encodeSegment(seg: VisitorSegment): string {\n const campaign = (seg.campaign || '').replace(/[^a-zA-Z0-9_-]/g, '').slice(0, 40)\n return `${seg.visitorType}|${seg.source}|${campaign}`\n}\n\n/** Parse a `_sk_seg` cookie value produced by {@link encodeSegment}. */\nexport function decodeSegment(value: string | undefined | null): VisitorSegment | null {\n if (!value) return null\n const [visitorType, source, campaign] = value.split('|')\n if (visitorType !== 'new' && visitorType !== 'returning') return null\n return {\n visitorType,\n source: (['paid', 'organic', 'social', 'referral', 'direct'].includes(source) ? source : 'direct') as SegmentSource,\n campaign: campaign || null,\n }\n}\n","/**\n * @sonordev/site-kit/middleware\n *\n * Composable Next.js middleware factory. Handles:\n * - Portal-managed redirects (via existing handleManagedRedirects)\n * - Security headers (X-Frame-Options, CSP, etc.)\n * - Optional before/after hooks for custom logic\n *\n * @example\n * ```ts\n * // middleware.ts — entire file\n * import { createMiddleware, siteKitMatcher } from '@sonordev/site-kit/middleware'\n *\n * export default createMiddleware()\n * export const config = siteKitMatcher\n * ```\n *\n * @example With custom hooks:\n * ```ts\n * export default createMiddleware({\n * securityHeaders: { frameOptions: 'SAMEORIGIN' },\n * before: (req) => {\n * // Custom auth check, etc.\n * },\n * })\n * ```\n */\n\nimport { NextRequest, NextResponse } from 'next/server'\nimport {\n handleManagedRedirects,\n type RedirectConfig,\n} from '../redirects'\nimport {\n buildSecurityHeaders,\n type SecurityHeadersConfig,\n} from './securityHeaders'\nimport { buildAiDiscoveryHeaders } from '../llms/discovery-headers'\nimport { resolveVisitorSegment, encodeSegment } from './segment'\n\nexport type { SecurityHeadersConfig } from './securityHeaders'\nexport {\n resolveVisitorSegment,\n encodeSegment,\n decodeSegment,\n type VisitorSegment,\n type SegmentSource,\n} from './segment'\n\nexport interface IdentityConfig {\n /**\n * Also resolve + persist a coarse visitor segment (new-vs-returning + source\n * + campaign) into the `_sk_seg` cookie, for segment-aware slots /\n * personalization. Default: true (when identity is enabled).\n */\n segment?: boolean\n /** Days the `_sk_vid` visitor cookie persists. Default: 730 (~2 years). */\n cookieMaxAgeDays?: number\n}\n\nexport interface LlmsDiscoveryConfig {\n /** Canonical site origin, e.g. `https://example.com` */\n siteUrl: string\n /** Path to llms.txt (default `/llms.txt`) */\n llmsPath?: string\n}\n\nexport interface SiteKitMiddlewareConfig {\n /** Enable Portal-managed redirects. Default: true */\n redirects?: boolean | RedirectConfig\n /** Enable security headers. Default: true */\n securityHeaders?: boolean | SecurityHeadersConfig\n /**\n * Opt-in: inject `Link: <...>; rel=\"describedby\"` header on HTML responses\n * pointing to /llms.txt for AI crawler discovery (RFC 8288).\n * Preferred over visible footer links.\n */\n llmsDiscovery?: LlmsDiscoveryConfig | false\n /**\n * Opt-in EDGE IDENTITY pass. When enabled, the middleware ensures a\n * first-party `_sk_vid` visitor cookie exists (server-authoritative id) and\n * resolves the visitor segment into `_sk_seg`, so server-rendered slots and\n * personalization can read identity + segment without re-parsing the request.\n * Purely additive — it only writes cookies, never changes the rendered HTML,\n * so it is LCP/static-safe. Default: OFF (sites opt in for 3.0 personalization).\n *\n * NOTE: to make these the SAME id the client uses, the shared identity helper\n * must read `_sk_vid` cookie-first (a follow-on change validated with a live\n * preview); until then the cookie is the server-side mirror.\n */\n identity?: boolean | IdentityConfig\n /**\n * Hook that runs BEFORE redirect check.\n * Return a NextResponse to short-circuit, or undefined to continue.\n */\n before?: (\n request: NextRequest,\n ) => NextResponse | undefined | Promise<NextResponse | undefined>\n /**\n * Hook that runs AFTER all processing.\n * Receives the response that will be sent — mutate headers, etc.\n */\n after?: (\n request: NextRequest,\n response: NextResponse,\n ) => NextResponse | Promise<NextResponse>\n}\n\n/**\n * Create a fully-featured Next.js middleware function.\n *\n * With zero config this handles redirects + security headers automatically.\n */\nexport function createMiddleware(config?: SiteKitMiddlewareConfig) {\n const securityHeaders = buildSecurityHeaders(\n config?.securityHeaders,\n )\n\n // Pre-compute discovery headers (static per middleware instance)\n const discoveryHeaders: Record<string, string> =\n config?.llmsDiscovery\n ? buildAiDiscoveryHeaders({\n siteUrl: config.llmsDiscovery.siteUrl,\n llmsPath: config.llmsDiscovery.llmsPath,\n })\n : {}\n\n return async function proxy(\n request: NextRequest,\n ): Promise<NextResponse> {\n // 1. Run user's 'before' hook\n if (config?.before) {\n const earlyResponse = await config.before(request)\n if (earlyResponse) {\n // Apply security headers even to early responses\n applyHeaders(earlyResponse, securityHeaders)\n return earlyResponse\n }\n }\n\n // 2. Check Portal-managed redirects (unless disabled)\n if (config?.redirects !== false) {\n const redirectConfig: RedirectConfig =\n typeof config?.redirects === 'object'\n ? config.redirects\n : {}\n\n const redirect = await handleManagedRedirects(\n request,\n redirectConfig,\n )\n if (redirect) {\n // Redirects get security headers too\n applyHeaders(redirect, securityHeaders)\n return redirect\n }\n }\n\n // 3. Build the pass-through response\n let response = NextResponse.next()\n\n // 4. Apply security headers\n applyHeaders(response, securityHeaders)\n\n // 5. Apply AI discovery Link header (only on HTML-like requests, not static assets)\n if (Object.keys(discoveryHeaders).length > 0) {\n const accept = request.headers.get('accept') || ''\n if (accept.includes('text/html') || accept.includes('*/*')) {\n applyHeaders(response, discoveryHeaders)\n }\n }\n\n // 5b. Edge identity pass (opt-in). Ensure a first-party `_sk_vid` visitor\n // cookie exists and resolve the visitor segment into `_sk_seg`. Additive:\n // only writes cookies, never changes the rendered HTML (LCP/static-safe).\n if (config?.identity) {\n const idCfg: IdentityConfig =\n typeof config.identity === 'object' ? config.identity : {}\n const hadVisitorCookie = !!request.cookies.get('_sk_vid')?.value\n if (!hadVisitorCookie) {\n const maxAgeDays = idCfg.cookieMaxAgeDays ?? 730\n response.cookies.set('_sk_vid', generateVisitorId(), {\n maxAge: maxAgeDays * 24 * 60 * 60,\n path: '/',\n sameSite: 'lax',\n secure: true,\n })\n }\n if (idCfg.segment !== false) {\n const segment = encodeSegment(\n resolveVisitorSegment(request, { hadVisitorCookie }),\n )\n response.cookies.set('_sk_seg', segment, {\n // Session-ish: a fresh campaign / source on a later visit re-segments.\n maxAge: 30 * 60,\n path: '/',\n sameSite: 'lax',\n secure: true,\n })\n }\n }\n\n // 6. Run user's 'after' hook\n if (config?.after) {\n response = await config.after(request, response)\n }\n\n return response\n }\n}\n\n/**\n * Generate a visitor id at the edge. Prefers Web Crypto (available in the\n * Next.js edge runtime); falls back to a v4-shaped string elsewhere.\n */\nfunction generateVisitorId(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) return crypto.randomUUID()\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0\n return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16)\n })\n}\n\nfunction applyHeaders(\n response: NextResponse,\n headers: Record<string, string>,\n): void {\n for (const [key, value] of Object.entries(headers)) {\n response.headers.set(key, value)\n }\n}\n\n/**\n * Standard matcher config that excludes static assets and images.\n * Export alongside createProxy/createMiddleware in your proxy.ts.\n */\nexport const siteKitMatcher = {\n matcher: [\n '/((?!_next/static|_next/image|favicon\\\\.ico|.*\\\\.(?:ico|png|jpg|jpeg|gif|webp|svg|woff2?)$).*)',\n ],\n}\n\n/** Next.js 16+ alias — same as createMiddleware, named for the proxy convention. */\nexport const createProxy = createMiddleware\n"]}
@@ -5,6 +5,76 @@ import '../chunk-B22WSN4L.mjs';
5
5
  import '../chunk-4XPGGLVP.mjs';
6
6
  import { NextResponse } from 'next/server';
7
7
 
8
+ // src/middleware/segment.ts
9
+ var PAID_MEDIUMS = ["cpc", "ppc", "paid", "paidsearch", "paid-search", "display", "cpm", "banner"];
10
+ var SOCIAL_MEDIUMS = ["social", "social-media", "social_media", "sm"];
11
+ var SOCIAL_HOSTS = [
12
+ "facebook.",
13
+ "instagram.",
14
+ "linkedin.",
15
+ "t.co",
16
+ "twitter.",
17
+ "x.com",
18
+ "pinterest.",
19
+ "tiktok.",
20
+ "youtube.",
21
+ "reddit."
22
+ ];
23
+ var SEARCH_HOSTS = ["google.", "bing.", "duckduckgo.", "yahoo.", "ecosia.", "brave.", "baidu.", "yandex."];
24
+ function resolveVisitorSegment(request, opts) {
25
+ const params = request.nextUrl.searchParams;
26
+ const medium = (params.get("utm_medium") || "").toLowerCase().trim();
27
+ const campaign = params.get("utm_campaign");
28
+ let source = "direct";
29
+ if (PAID_MEDIUMS.includes(medium)) {
30
+ source = "paid";
31
+ } else if (SOCIAL_MEDIUMS.includes(medium)) {
32
+ source = "social";
33
+ } else if (medium === "organic") {
34
+ source = "organic";
35
+ } else if (medium === "referral") {
36
+ source = "referral";
37
+ } else if (params.get("utm_source") || params.get("gclid") || params.get("fbclid")) {
38
+ if (params.get("gclid")) source = "paid";
39
+ else if (params.get("fbclid")) source = "social";
40
+ else source = "referral";
41
+ } else {
42
+ const ref = request.headers.get("referer") || "";
43
+ if (ref) {
44
+ try {
45
+ const host = new URL(ref).host.toLowerCase();
46
+ const selfHost = request.nextUrl.host.toLowerCase();
47
+ if (host && host !== selfHost) {
48
+ if (SEARCH_HOSTS.some((h) => host.includes(h))) source = "organic";
49
+ else if (SOCIAL_HOSTS.some((h) => host.includes(h))) source = "social";
50
+ else source = "referral";
51
+ }
52
+ } catch {
53
+ }
54
+ }
55
+ }
56
+ return {
57
+ visitorType: opts.hadVisitorCookie ? "returning" : "new",
58
+ source,
59
+ campaign: campaign || null
60
+ };
61
+ }
62
+ function encodeSegment(seg) {
63
+ const campaign = (seg.campaign || "").replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 40);
64
+ return `${seg.visitorType}|${seg.source}|${campaign}`;
65
+ }
66
+ function decodeSegment(value) {
67
+ if (!value) return null;
68
+ const [visitorType, source, campaign] = value.split("|");
69
+ if (visitorType !== "new" && visitorType !== "returning") return null;
70
+ return {
71
+ visitorType,
72
+ source: ["paid", "organic", "social", "referral", "direct"].includes(source) ? source : "direct",
73
+ campaign: campaign || null
74
+ };
75
+ }
76
+
77
+ // src/middleware/index.ts
8
78
  function createMiddleware(config) {
9
79
  const securityHeaders = buildSecurityHeaders(
10
80
  config?.securityHeaders
@@ -40,12 +110,44 @@ function createMiddleware(config) {
40
110
  applyHeaders(response, discoveryHeaders);
41
111
  }
42
112
  }
113
+ if (config?.identity) {
114
+ const idCfg = typeof config.identity === "object" ? config.identity : {};
115
+ const hadVisitorCookie = !!request.cookies.get("_sk_vid")?.value;
116
+ if (!hadVisitorCookie) {
117
+ const maxAgeDays = idCfg.cookieMaxAgeDays ?? 730;
118
+ response.cookies.set("_sk_vid", generateVisitorId(), {
119
+ maxAge: maxAgeDays * 24 * 60 * 60,
120
+ path: "/",
121
+ sameSite: "lax",
122
+ secure: true
123
+ });
124
+ }
125
+ if (idCfg.segment !== false) {
126
+ const segment = encodeSegment(
127
+ resolveVisitorSegment(request, { hadVisitorCookie })
128
+ );
129
+ response.cookies.set("_sk_seg", segment, {
130
+ // Session-ish: a fresh campaign / source on a later visit re-segments.
131
+ maxAge: 30 * 60,
132
+ path: "/",
133
+ sameSite: "lax",
134
+ secure: true
135
+ });
136
+ }
137
+ }
43
138
  if (config?.after) {
44
139
  response = await config.after(request, response);
45
140
  }
46
141
  return response;
47
142
  };
48
143
  }
144
+ function generateVisitorId() {
145
+ if (typeof crypto !== "undefined" && crypto.randomUUID) return crypto.randomUUID();
146
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
147
+ const r = Math.random() * 16 | 0;
148
+ return (c === "x" ? r : r & 3 | 8).toString(16);
149
+ });
150
+ }
49
151
  function applyHeaders(response, headers) {
50
152
  for (const [key, value] of Object.entries(headers)) {
51
153
  response.headers.set(key, value);
@@ -58,6 +160,6 @@ var siteKitMatcher = {
58
160
  };
59
161
  var createProxy = createMiddleware;
60
162
 
61
- export { createMiddleware, createProxy, siteKitMatcher };
163
+ export { createMiddleware, createProxy, decodeSegment, encodeSegment, resolveVisitorSegment, siteKitMatcher };
62
164
  //# sourceMappingURL=index.mjs.map
63
165
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/middleware/index.ts"],"names":[],"mappings":";;;;;;;AAiFO,SAAS,iBAAiB,MAAA,EAAkC;AACjE,EAAA,MAAM,eAAA,GAAkB,oBAAA;AAAA,IACtB,MAAA,EAAQ;AAAA,GACV;AAGA,EAAA,MAAM,gBAAA,GACJ,MAAA,EAAQ,aAAA,GACJ,uBAAA,CAAwB;AAAA,IACtB,OAAA,EAAS,OAAO,aAAA,CAAc,OAAA;AAAA,IAC9B,QAAA,EAAU,OAAO,aAAA,CAAc;AAAA,GAChC,IACD,EAAC;AAEP,EAAA,OAAO,eAAe,MACpB,OAAA,EACuB;AAEvB,IAAA,IAAI,QAAQ,MAAA,EAAQ;AAClB,MAAA,MAAM,aAAA,GAAgB,MAAM,MAAA,CAAO,MAAA,CAAO,OAAO,CAAA;AACjD,MAAA,IAAI,aAAA,EAAe;AAEjB,QAAA,YAAA,CAAa,eAAe,eAAe,CAAA;AAC3C,QAAA,OAAO,aAAA;AAAA,MACT;AAAA,IACF;AAGA,IAAA,IAAI,MAAA,EAAQ,cAAc,KAAA,EAAO;AAC/B,MAAA,MAAM,iBACJ,OAAO,MAAA,EAAQ,cAAc,QAAA,GACzB,MAAA,CAAO,YACP,EAAC;AAEP,MAAA,MAAM,WAAW,MAAM,sBAAA;AAAA,QACrB,OAAA;AAAA,QACA;AAAA,OACF;AACA,MAAA,IAAI,QAAA,EAAU;AAEZ,QAAA,YAAA,CAAa,UAAU,eAAe,CAAA;AACtC,QAAA,OAAO,QAAA;AAAA,MACT;AAAA,IACF;AAGA,IAAA,IAAI,QAAA,GAAW,aAAa,IAAA,EAAK;AAGjC,IAAA,YAAA,CAAa,UAAU,eAAe,CAAA;AAGtC,IAAA,IAAI,MAAA,CAAO,IAAA,CAAK,gBAAgB,CAAA,CAAE,SAAS,CAAA,EAAG;AAC5C,MAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,QAAQ,CAAA,IAAK,EAAA;AAChD,MAAA,IAAI,OAAO,QAAA,CAAS,WAAW,KAAK,MAAA,CAAO,QAAA,CAAS,KAAK,CAAA,EAAG;AAC1D,QAAA,YAAA,CAAa,UAAU,gBAAgB,CAAA;AAAA,MACzC;AAAA,IACF;AAGA,IAAA,IAAI,QAAQ,KAAA,EAAO;AACjB,MAAA,QAAA,GAAW,MAAM,MAAA,CAAO,KAAA,CAAM,OAAA,EAAS,QAAQ,CAAA;AAAA,IACjD;AAEA,IAAA,OAAO,QAAA;AAAA,EACT,CAAA;AACF;AAEA,SAAS,YAAA,CACP,UACA,OAAA,EACM;AACN,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,OAAO,CAAA,EAAG;AAClD,IAAA,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,GAAA,EAAK,KAAK,CAAA;AAAA,EACjC;AACF;AAMO,IAAM,cAAA,GAAiB;AAAA,EAC5B,OAAA,EAAS;AAAA,IACP;AAAA;AAEJ;AAGO,IAAM,WAAA,GAAc","file":"index.mjs","sourcesContent":["/**\n * @sonordev/site-kit/middleware\n *\n * Composable Next.js middleware factory. Handles:\n * - Portal-managed redirects (via existing handleManagedRedirects)\n * - Security headers (X-Frame-Options, CSP, etc.)\n * - Optional before/after hooks for custom logic\n *\n * @example\n * ```ts\n * // middleware.ts — entire file\n * import { createMiddleware, siteKitMatcher } from '@sonordev/site-kit/middleware'\n *\n * export default createMiddleware()\n * export const config = siteKitMatcher\n * ```\n *\n * @example With custom hooks:\n * ```ts\n * export default createMiddleware({\n * securityHeaders: { frameOptions: 'SAMEORIGIN' },\n * before: (req) => {\n * // Custom auth check, etc.\n * },\n * })\n * ```\n */\n\nimport { NextRequest, NextResponse } from 'next/server'\nimport {\n handleManagedRedirects,\n type RedirectConfig,\n} from '../redirects'\nimport {\n buildSecurityHeaders,\n type SecurityHeadersConfig,\n} from './securityHeaders'\nimport { buildAiDiscoveryHeaders } from '../llms/discovery-headers'\n\nexport type { SecurityHeadersConfig } from './securityHeaders'\n\nexport interface LlmsDiscoveryConfig {\n /** Canonical site origin, e.g. `https://example.com` */\n siteUrl: string\n /** Path to llms.txt (default `/llms.txt`) */\n llmsPath?: string\n}\n\nexport interface SiteKitMiddlewareConfig {\n /** Enable Portal-managed redirects. Default: true */\n redirects?: boolean | RedirectConfig\n /** Enable security headers. Default: true */\n securityHeaders?: boolean | SecurityHeadersConfig\n /**\n * Opt-in: inject `Link: <...>; rel=\"describedby\"` header on HTML responses\n * pointing to /llms.txt for AI crawler discovery (RFC 8288).\n * Preferred over visible footer links.\n */\n llmsDiscovery?: LlmsDiscoveryConfig | false\n /**\n * Hook that runs BEFORE redirect check.\n * Return a NextResponse to short-circuit, or undefined to continue.\n */\n before?: (\n request: NextRequest,\n ) => NextResponse | undefined | Promise<NextResponse | undefined>\n /**\n * Hook that runs AFTER all processing.\n * Receives the response that will be sent — mutate headers, etc.\n */\n after?: (\n request: NextRequest,\n response: NextResponse,\n ) => NextResponse | Promise<NextResponse>\n}\n\n/**\n * Create a fully-featured Next.js middleware function.\n *\n * With zero config this handles redirects + security headers automatically.\n */\nexport function createMiddleware(config?: SiteKitMiddlewareConfig) {\n const securityHeaders = buildSecurityHeaders(\n config?.securityHeaders,\n )\n\n // Pre-compute discovery headers (static per middleware instance)\n const discoveryHeaders: Record<string, string> =\n config?.llmsDiscovery\n ? buildAiDiscoveryHeaders({\n siteUrl: config.llmsDiscovery.siteUrl,\n llmsPath: config.llmsDiscovery.llmsPath,\n })\n : {}\n\n return async function proxy(\n request: NextRequest,\n ): Promise<NextResponse> {\n // 1. Run user's 'before' hook\n if (config?.before) {\n const earlyResponse = await config.before(request)\n if (earlyResponse) {\n // Apply security headers even to early responses\n applyHeaders(earlyResponse, securityHeaders)\n return earlyResponse\n }\n }\n\n // 2. Check Portal-managed redirects (unless disabled)\n if (config?.redirects !== false) {\n const redirectConfig: RedirectConfig =\n typeof config?.redirects === 'object'\n ? config.redirects\n : {}\n\n const redirect = await handleManagedRedirects(\n request,\n redirectConfig,\n )\n if (redirect) {\n // Redirects get security headers too\n applyHeaders(redirect, securityHeaders)\n return redirect\n }\n }\n\n // 3. Build the pass-through response\n let response = NextResponse.next()\n\n // 4. Apply security headers\n applyHeaders(response, securityHeaders)\n\n // 5. Apply AI discovery Link header (only on HTML-like requests, not static assets)\n if (Object.keys(discoveryHeaders).length > 0) {\n const accept = request.headers.get('accept') || ''\n if (accept.includes('text/html') || accept.includes('*/*')) {\n applyHeaders(response, discoveryHeaders)\n }\n }\n\n // 6. Run user's 'after' hook\n if (config?.after) {\n response = await config.after(request, response)\n }\n\n return response\n }\n}\n\nfunction applyHeaders(\n response: NextResponse,\n headers: Record<string, string>,\n): void {\n for (const [key, value] of Object.entries(headers)) {\n response.headers.set(key, value)\n }\n}\n\n/**\n * Standard matcher config that excludes static assets and images.\n * Export alongside createProxy/createMiddleware in your proxy.ts.\n */\nexport const siteKitMatcher = {\n matcher: [\n '/((?!_next/static|_next/image|favicon\\\\.ico|.*\\\\.(?:ico|png|jpg|jpeg|gif|webp|svg|woff2?)$).*)',\n ],\n}\n\n/** Next.js 16+ alias — same as createMiddleware, named for the proxy convention. */\nexport const createProxy = createMiddleware\n"]}
1
+ {"version":3,"sources":["../../src/middleware/segment.ts","../../src/middleware/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * @sonordev/site-kit/middleware — visitor segment resolution (the \"one edge pass\").\n *\n * Pure, edge-runtime-safe helpers that classify a request into a coarse visitor\n * segment (new-vs-returning + marketing source + campaign) from the URL's UTM\n * params and the referrer. The middleware writes the result to the `_sk_seg`\n * cookie so server-rendered slots / personalization can vary by segment WITHOUT\n * each one re-parsing the request. Resolved once, at the edge.\n *\n * Kept side-effect-free (no NextResponse, no cookie writes) so it is trivially\n * unit-testable and reusable.\n */\nimport type { NextRequest } from 'next/server'\n\nexport type SegmentSource = 'paid' | 'organic' | 'social' | 'referral' | 'direct'\n\nexport interface VisitorSegment {\n /** `new` on the first visit (no prior `_sk_vid` cookie), else `returning`. */\n visitorType: 'new' | 'returning'\n /** Marketing source bucket derived from UTM medium/source, then referrer. */\n source: SegmentSource\n /** `utm_campaign`, if present (sanitized when encoded). */\n campaign: string | null\n}\n\nconst PAID_MEDIUMS = ['cpc', 'ppc', 'paid', 'paidsearch', 'paid-search', 'display', 'cpm', 'banner']\nconst SOCIAL_MEDIUMS = ['social', 'social-media', 'social_media', 'sm']\nconst SOCIAL_HOSTS = [\n 'facebook.',\n 'instagram.',\n 'linkedin.',\n 't.co',\n 'twitter.',\n 'x.com',\n 'pinterest.',\n 'tiktok.',\n 'youtube.',\n 'reddit.',\n]\nconst SEARCH_HOSTS = ['google.', 'bing.', 'duckduckgo.', 'yahoo.', 'ecosia.', 'brave.', 'baidu.', 'yandex.']\n\n/**\n * Classify `request` into a {@link VisitorSegment}. UTM params win over the\n * referrer; a same-host referrer is ignored (internal navigation, not a source).\n */\nexport function resolveVisitorSegment(\n request: NextRequest,\n opts: { hadVisitorCookie: boolean },\n): VisitorSegment {\n const params = request.nextUrl.searchParams\n const medium = (params.get('utm_medium') || '').toLowerCase().trim()\n const campaign = params.get('utm_campaign')\n\n let source: SegmentSource = 'direct'\n\n if (PAID_MEDIUMS.includes(medium)) {\n source = 'paid'\n } else if (SOCIAL_MEDIUMS.includes(medium)) {\n source = 'social'\n } else if (medium === 'organic') {\n source = 'organic'\n } else if (medium === 'referral') {\n source = 'referral'\n } else if (params.get('utm_source') || params.get('gclid') || params.get('fbclid')) {\n // A tagged link with a non-standard medium: gclid => paid Google, fbclid => social.\n if (params.get('gclid')) source = 'paid'\n else if (params.get('fbclid')) source = 'social'\n else source = 'referral'\n } else {\n // No campaign tagging — fall back to the referrer host.\n const ref = request.headers.get('referer') || ''\n if (ref) {\n try {\n const host = new URL(ref).host.toLowerCase()\n const selfHost = request.nextUrl.host.toLowerCase()\n if (host && host !== selfHost) {\n if (SEARCH_HOSTS.some((h) => host.includes(h))) source = 'organic'\n else if (SOCIAL_HOSTS.some((h) => host.includes(h))) source = 'social'\n else source = 'referral'\n }\n } catch {\n /* malformed referer — stays 'direct' */\n }\n }\n }\n\n return {\n visitorType: opts.hadVisitorCookie ? 'returning' : 'new',\n source,\n campaign: campaign || null,\n }\n}\n\n/**\n * Compact, cookie-safe encoding: `\"<visitorType>|<source>|<campaign>\"`, e.g.\n * `\"new|paid|spring_sale\"`. Campaign is sanitized to `[A-Za-z0-9_-]` and capped.\n * Decode with {@link decodeSegment}.\n */\nexport function encodeSegment(seg: VisitorSegment): string {\n const campaign = (seg.campaign || '').replace(/[^a-zA-Z0-9_-]/g, '').slice(0, 40)\n return `${seg.visitorType}|${seg.source}|${campaign}`\n}\n\n/** Parse a `_sk_seg` cookie value produced by {@link encodeSegment}. */\nexport function decodeSegment(value: string | undefined | null): VisitorSegment | null {\n if (!value) return null\n const [visitorType, source, campaign] = value.split('|')\n if (visitorType !== 'new' && visitorType !== 'returning') return null\n return {\n visitorType,\n source: (['paid', 'organic', 'social', 'referral', 'direct'].includes(source) ? source : 'direct') as SegmentSource,\n campaign: campaign || null,\n }\n}\n","/**\n * @sonordev/site-kit/middleware\n *\n * Composable Next.js middleware factory. Handles:\n * - Portal-managed redirects (via existing handleManagedRedirects)\n * - Security headers (X-Frame-Options, CSP, etc.)\n * - Optional before/after hooks for custom logic\n *\n * @example\n * ```ts\n * // middleware.ts — entire file\n * import { createMiddleware, siteKitMatcher } from '@sonordev/site-kit/middleware'\n *\n * export default createMiddleware()\n * export const config = siteKitMatcher\n * ```\n *\n * @example With custom hooks:\n * ```ts\n * export default createMiddleware({\n * securityHeaders: { frameOptions: 'SAMEORIGIN' },\n * before: (req) => {\n * // Custom auth check, etc.\n * },\n * })\n * ```\n */\n\nimport { NextRequest, NextResponse } from 'next/server'\nimport {\n handleManagedRedirects,\n type RedirectConfig,\n} from '../redirects'\nimport {\n buildSecurityHeaders,\n type SecurityHeadersConfig,\n} from './securityHeaders'\nimport { buildAiDiscoveryHeaders } from '../llms/discovery-headers'\nimport { resolveVisitorSegment, encodeSegment } from './segment'\n\nexport type { SecurityHeadersConfig } from './securityHeaders'\nexport {\n resolveVisitorSegment,\n encodeSegment,\n decodeSegment,\n type VisitorSegment,\n type SegmentSource,\n} from './segment'\n\nexport interface IdentityConfig {\n /**\n * Also resolve + persist a coarse visitor segment (new-vs-returning + source\n * + campaign) into the `_sk_seg` cookie, for segment-aware slots /\n * personalization. Default: true (when identity is enabled).\n */\n segment?: boolean\n /** Days the `_sk_vid` visitor cookie persists. Default: 730 (~2 years). */\n cookieMaxAgeDays?: number\n}\n\nexport interface LlmsDiscoveryConfig {\n /** Canonical site origin, e.g. `https://example.com` */\n siteUrl: string\n /** Path to llms.txt (default `/llms.txt`) */\n llmsPath?: string\n}\n\nexport interface SiteKitMiddlewareConfig {\n /** Enable Portal-managed redirects. Default: true */\n redirects?: boolean | RedirectConfig\n /** Enable security headers. Default: true */\n securityHeaders?: boolean | SecurityHeadersConfig\n /**\n * Opt-in: inject `Link: <...>; rel=\"describedby\"` header on HTML responses\n * pointing to /llms.txt for AI crawler discovery (RFC 8288).\n * Preferred over visible footer links.\n */\n llmsDiscovery?: LlmsDiscoveryConfig | false\n /**\n * Opt-in EDGE IDENTITY pass. When enabled, the middleware ensures a\n * first-party `_sk_vid` visitor cookie exists (server-authoritative id) and\n * resolves the visitor segment into `_sk_seg`, so server-rendered slots and\n * personalization can read identity + segment without re-parsing the request.\n * Purely additive — it only writes cookies, never changes the rendered HTML,\n * so it is LCP/static-safe. Default: OFF (sites opt in for 3.0 personalization).\n *\n * NOTE: to make these the SAME id the client uses, the shared identity helper\n * must read `_sk_vid` cookie-first (a follow-on change validated with a live\n * preview); until then the cookie is the server-side mirror.\n */\n identity?: boolean | IdentityConfig\n /**\n * Hook that runs BEFORE redirect check.\n * Return a NextResponse to short-circuit, or undefined to continue.\n */\n before?: (\n request: NextRequest,\n ) => NextResponse | undefined | Promise<NextResponse | undefined>\n /**\n * Hook that runs AFTER all processing.\n * Receives the response that will be sent — mutate headers, etc.\n */\n after?: (\n request: NextRequest,\n response: NextResponse,\n ) => NextResponse | Promise<NextResponse>\n}\n\n/**\n * Create a fully-featured Next.js middleware function.\n *\n * With zero config this handles redirects + security headers automatically.\n */\nexport function createMiddleware(config?: SiteKitMiddlewareConfig) {\n const securityHeaders = buildSecurityHeaders(\n config?.securityHeaders,\n )\n\n // Pre-compute discovery headers (static per middleware instance)\n const discoveryHeaders: Record<string, string> =\n config?.llmsDiscovery\n ? buildAiDiscoveryHeaders({\n siteUrl: config.llmsDiscovery.siteUrl,\n llmsPath: config.llmsDiscovery.llmsPath,\n })\n : {}\n\n return async function proxy(\n request: NextRequest,\n ): Promise<NextResponse> {\n // 1. Run user's 'before' hook\n if (config?.before) {\n const earlyResponse = await config.before(request)\n if (earlyResponse) {\n // Apply security headers even to early responses\n applyHeaders(earlyResponse, securityHeaders)\n return earlyResponse\n }\n }\n\n // 2. Check Portal-managed redirects (unless disabled)\n if (config?.redirects !== false) {\n const redirectConfig: RedirectConfig =\n typeof config?.redirects === 'object'\n ? config.redirects\n : {}\n\n const redirect = await handleManagedRedirects(\n request,\n redirectConfig,\n )\n if (redirect) {\n // Redirects get security headers too\n applyHeaders(redirect, securityHeaders)\n return redirect\n }\n }\n\n // 3. Build the pass-through response\n let response = NextResponse.next()\n\n // 4. Apply security headers\n applyHeaders(response, securityHeaders)\n\n // 5. Apply AI discovery Link header (only on HTML-like requests, not static assets)\n if (Object.keys(discoveryHeaders).length > 0) {\n const accept = request.headers.get('accept') || ''\n if (accept.includes('text/html') || accept.includes('*/*')) {\n applyHeaders(response, discoveryHeaders)\n }\n }\n\n // 5b. Edge identity pass (opt-in). Ensure a first-party `_sk_vid` visitor\n // cookie exists and resolve the visitor segment into `_sk_seg`. Additive:\n // only writes cookies, never changes the rendered HTML (LCP/static-safe).\n if (config?.identity) {\n const idCfg: IdentityConfig =\n typeof config.identity === 'object' ? config.identity : {}\n const hadVisitorCookie = !!request.cookies.get('_sk_vid')?.value\n if (!hadVisitorCookie) {\n const maxAgeDays = idCfg.cookieMaxAgeDays ?? 730\n response.cookies.set('_sk_vid', generateVisitorId(), {\n maxAge: maxAgeDays * 24 * 60 * 60,\n path: '/',\n sameSite: 'lax',\n secure: true,\n })\n }\n if (idCfg.segment !== false) {\n const segment = encodeSegment(\n resolveVisitorSegment(request, { hadVisitorCookie }),\n )\n response.cookies.set('_sk_seg', segment, {\n // Session-ish: a fresh campaign / source on a later visit re-segments.\n maxAge: 30 * 60,\n path: '/',\n sameSite: 'lax',\n secure: true,\n })\n }\n }\n\n // 6. Run user's 'after' hook\n if (config?.after) {\n response = await config.after(request, response)\n }\n\n return response\n }\n}\n\n/**\n * Generate a visitor id at the edge. Prefers Web Crypto (available in the\n * Next.js edge runtime); falls back to a v4-shaped string elsewhere.\n */\nfunction generateVisitorId(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) return crypto.randomUUID()\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0\n return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16)\n })\n}\n\nfunction applyHeaders(\n response: NextResponse,\n headers: Record<string, string>,\n): void {\n for (const [key, value] of Object.entries(headers)) {\n response.headers.set(key, value)\n }\n}\n\n/**\n * Standard matcher config that excludes static assets and images.\n * Export alongside createProxy/createMiddleware in your proxy.ts.\n */\nexport const siteKitMatcher = {\n matcher: [\n '/((?!_next/static|_next/image|favicon\\\\.ico|.*\\\\.(?:ico|png|jpg|jpeg|gif|webp|svg|woff2?)$).*)',\n ],\n}\n\n/** Next.js 16+ alias — same as createMiddleware, named for the proxy convention. */\nexport const createProxy = createMiddleware\n"]}
@@ -1,201 +1,10 @@
1
- import { c as ManagedSchemaProps, e as ManagedFAQProps, g as ManagedInternalLinksProps, i as ManagedContentProps, h as ManagedContentBlock, p as ManagedScriptsProps } from '../types-DYyIAgQg.mjs';
2
- export { A as ABTest, k as ABTestResult, F as FAQItem, l as GetABVariantOptions, G as GetManagedMetadataOptions, n as GetRedirectOptions, r as GetRobotsOptions, t as GetSitemapEntriesOptions, d as ManagedFAQData, f as ManagedLink, M as ManagedMetadataResult, m as ManagedRedirect, o as ManagedScript, R as RedirectResult, q as RobotsDirective, a as SEOPageData, b as SchemaMarkup, s as SitemapEntry, S as SonorSEOConfig } from '../types-DYyIAgQg.mjs';
3
- export { b as generateSitemap, c as getABVariant, g as getManagedMetadata, a as getManagedMetadataWithAB, d as getRedirect, e as getRobotsDirective, i as isIndexable } from '../routing-trNzR1Pz.mjs';
1
+ import { a as ManagedSchemaProps, c as ManagedFAQProps, d as ManagedInternalLinksProps, e as ManagedContentProps, f as ManagedContentBlock, g as ManagedScriptsProps } from '../types-DPsbBKiu.mjs';
2
+ export { A as ABTest, k as ABTestResult, F as FAQItem, l as GetABVariantOptions, G as GetManagedMetadataOptions, n as GetRedirectOptions, q as GetRobotsOptions, s as GetSitemapEntriesOptions, b as ManagedFAQData, j as ManagedLink, M as ManagedMetadataResult, m as ManagedRedirect, o as ManagedScript, R as RedirectResult, p as RobotsDirective, h as SEOPageData, i as SchemaMarkup, r as SitemapEntry, S as SonorSEOConfig } from '../types-DPsbBKiu.mjs';
3
+ export { E as EntityType, P as PageSeoContext, S as SEOEntity, e as generateSitemap, m as getABTest, b as getABVariant, l as getContentBlock, t as getEntities, v as getEntityEnhancedSchema, j as getFAQData, k as getInternalLinks, g as getManagedMetadata, a as getManagedMetadataWithAB, o as getManagedScripts, u as getPrimaryEntity, c as getRedirect, n as getRedirectData, p as getRobotsData, d as getRobotsDirective, f as getSEOPageData, h as getSchemaMarkups, q as getSitemapEntries, w as getVisibilityScore, x as getVisibilitySummary, i as isIndexable, r as recordABImpression, s as registerSitemap } from '../server-mX5At_6x.mjs';
4
4
  import { ResolvingMetadata, Metadata } from 'next';
5
5
  import * as React from 'react';
6
6
  export { SitemapSync } from './client.mjs';
7
7
 
8
- /**
9
- * @sonordev/site-kit/seo - Server-Only API Functions
10
- *
11
- * SECURITY: These functions use private environment variables
12
- * and should ONLY be imported in server-side code (RSC, API routes, server actions).
13
- *
14
- * DO NOT import this file in client components or it will expose API keys.
15
- */
16
-
17
- /** Response shape for POST /api/public/seo/page-context */
18
- type PageSeoContext = {
19
- page: any | null;
20
- schemas: any[];
21
- project: {
22
- id: string;
23
- title?: string | null;
24
- domain?: string | null;
25
- logo_url?: string | null;
26
- site_url?: string | null;
27
- } | null;
28
- };
29
- /**
30
- * Fetch SEO page data - cached per request
31
- * @server-only
32
- */
33
- declare const getSEOPageData: (path: string) => Promise<{
34
- page: any;
35
- project: {
36
- id: string;
37
- title?: string | null;
38
- domain?: string | null;
39
- logo_url?: string | null;
40
- site_url?: string | null;
41
- } | null;
42
- }>;
43
- /**
44
- * Fetch schema markups for a page - cached per request (shares loadPageSeoContext with getSEOPageData)
45
- * @server-only
46
- */
47
- declare const getSchemaMarkups: (path: string, options?: {
48
- includeTypes?: string[];
49
- excludeTypes?: string[];
50
- }) => Promise<any[]>;
51
- /**
52
- * Fetch FAQ data for a page - cached per request
53
- * @server-only
54
- */
55
- declare const getFAQData: (path: string) => Promise<any>;
56
- /**
57
- * Fetch internal links for a page - cached per request
58
- * @server-only
59
- */
60
- declare const getInternalLinks: (sourcePath: string, options?: {
61
- position?: string;
62
- limit?: number;
63
- }) => Promise<any[]>;
64
- /**
65
- * Fetch content block - cached per request
66
- * @server-only
67
- */
68
- declare const getContentBlock: (path: string, section: string) => Promise<any>;
69
- /**
70
- * Fetch A/B test and determine variant - cached per request
71
- * @server-only
72
- */
73
- declare const getABTest: (path: string, field: string) => Promise<any>;
74
- /**
75
- * Record A/B test impression
76
- * @server-only
77
- */
78
- declare function recordABImpression(testId: string, variant: 'a' | 'b', sessionId?: string): Promise<void>;
79
- /**
80
- * Fetch redirect for a path - cached per request
81
- * @server-only
82
- */
83
- declare const getRedirectData: (path: string) => Promise<any>;
84
- /**
85
- * Fetch managed scripts - cached per request
86
- * @server-only
87
- */
88
- declare const getManagedScripts: (position: string, currentPath?: string) => Promise<any[]>;
89
- /**
90
- * Fetch robots directive for a page - cached per request
91
- * @server-only
92
- */
93
- declare const getRobotsData: (path: string) => Promise<any>;
94
- /**
95
- * Fetch sitemap entries - cached per request
96
- * @server-only
97
- */
98
- declare const getSitemapEntries: (options?: {
99
- publishedOnly?: boolean;
100
- }) => Promise<any[]>;
101
- /**
102
- * Register/sync sitemap entries from the client site
103
- * Call this at build time to populate seo_pages from your sitemap.xml
104
- * @server-only
105
- *
106
- * @example
107
- * ```ts
108
- * // scripts/register-sitemap.ts (run at build time)
109
- * import { registerSitemap } from '@sonordev/seo/server'
110
- *
111
- * await registerSitemap([
112
- * { path: '/', priority: 1.0, changefreq: 'daily' },
113
- * { path: '/about', priority: 0.8, changefreq: 'weekly' },
114
- * ])
115
- * ```
116
- */
117
- declare function registerSitemap(entries: Array<{
118
- path: string;
119
- title?: string;
120
- priority?: number;
121
- changefreq?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';
122
- }>, options?: {
123
- /**
124
- * Sync mode:
125
- * - 'full-replace': replaces the entire page set (default for build-time sync)
126
- * - 'additive': adds/updates pages but never deletes existing ones
127
- */
128
- mode?: 'additive' | 'full-replace';
129
- }): Promise<{
130
- success: boolean;
131
- created: number;
132
- updated: number;
133
- }>;
134
- /**
135
- * Entity types for the knowledge graph
136
- */
137
- type EntityType = 'organization' | 'person' | 'service' | 'product' | 'location' | 'concept' | 'credential';
138
- /**
139
- * Entity from the knowledge graph
140
- */
141
- interface SEOEntity {
142
- id: string;
143
- project_id: string;
144
- entity_type: EntityType;
145
- name: string;
146
- slug: string;
147
- properties: Record<string, unknown>;
148
- knows_about: string[];
149
- same_as: string[];
150
- schema_type?: string;
151
- is_primary: boolean;
152
- }
153
- /**
154
- * Fetch entities for a project - cached per request
155
- * Returns the entity graph for enhanced schema markup
156
- * @server-only
157
- */
158
- declare const getEntities: (options?: {
159
- type?: EntityType;
160
- }) => Promise<SEOEntity[]>;
161
- /**
162
- * Fetch primary entity (the business) - cached per request
163
- * @server-only
164
- */
165
- declare const getPrimaryEntity: () => Promise<SEOEntity | null>;
166
- /**
167
- * Fetch entity-enhanced schema for a page
168
- * Returns Organization schema with knowsAbout, areaServed, employee, etc.
169
- * @server-only
170
- */
171
- declare const getEntityEnhancedSchema: (pagePath: string) => Promise<object[]>;
172
- /**
173
- * Get AI visibility score for a page
174
- * @server-only
175
- */
176
- declare const getVisibilityScore: (pagePath: string) => Promise<{
177
- overall_score: number;
178
- entity_coverage: number;
179
- answer_density: number;
180
- chunk_readability: number;
181
- authority_signals: number;
182
- schema_completeness: number;
183
- } | null>;
184
- /**
185
- * Get AI visibility summary for project
186
- * @server-only
187
- */
188
- declare const getVisibilitySummary: () => Promise<{
189
- overall_score: number;
190
- total_entities: number;
191
- pages_analyzed: number;
192
- top_recommendations: Array<{
193
- priority: string;
194
- type: string;
195
- message: string;
196
- }>;
197
- } | null>;
198
-
199
8
  /**
200
9
  * @sonordev/site-kit/seo - withManagedMetadata
201
10
  *
@@ -323,17 +132,13 @@ declare function ManagedNoScripts(props: {
323
132
  }): React.ReactElement;
324
133
 
325
134
  /**
326
- * LocationPageContent - Server Component for fetching location page sections
327
- *
328
- * Usage:
329
- * <LocationPageContent
330
- * projectId="uuid"
331
- * path="/areas/seattle-wa/plumbing"
332
- * section="hero"
333
- * />
135
+ * @deprecated DO NOT USE. This component calls an endpoint that does not exist
136
+ * (and sends project_id from the client, violating the x-api-key-only auth
137
+ * contract) — it has only ever rendered its fallback. Location page content is
138
+ * served through the standard managed-content path instead. Kept exported so
139
+ * existing imports keep compiling; it renders the fallback and warns.
334
140
  *
335
- * This component fetches content from seo_location_pages.sections for a given
336
- * path and section, making it editable in Portal without code deploys.
141
+ * LocationPageContent - Server Component for fetching location page sections
337
142
  */
338
143
 
339
144
  interface LocationPageContentProps {
@@ -388,4 +193,4 @@ declare const getLocationSection: (projectId: string, path: string, section: str
388
193
  */
389
194
  declare function LocationPageContent(props: LocationPageContentProps): React.ReactElement;
390
195
 
391
- export { type EntityType, LLMSchema, type HeroSectionContent as LocationHeroContent, LocationPageContent, type LocationPageContentProps, type LocationSectionData, type ServiceGridContent as LocationServicesContent, type TextSectionContent as LocationTextContent, ManagedContent, ManagedContentBlock, ManagedContentProps, ManagedFAQ, ManagedFAQProps, ManagedInternalLinks, ManagedInternalLinksProps, ManagedNoScripts, ManagedSchema, ManagedSchemaProps, ManagedScripts, ManagedScriptsProps, type PageSeoContext, type SEOEntity, createBreadcrumbSchema, createSchema, createWebSiteOrganizationStub, getABTest, getContentBlock, getEntities, getEntityEnhancedSchema, getFAQData, getInternalLinks, getLocationSection, getManagedContentData, getManagedScripts, getPrimaryEntity, getRedirectData, getRobotsData, getSEOPageData, getSchemaMarkups, getSitemapEntries, getVisibilityScore, getVisibilitySummary, recordABImpression, registerSitemap, withManagedMetadata };
196
+ export { LLMSchema, type HeroSectionContent as LocationHeroContent, LocationPageContent, type LocationPageContentProps, type LocationSectionData, type ServiceGridContent as LocationServicesContent, type TextSectionContent as LocationTextContent, ManagedContent, ManagedContentBlock, ManagedContentProps, ManagedFAQ, ManagedFAQProps, ManagedInternalLinks, ManagedInternalLinksProps, ManagedNoScripts, ManagedSchema, ManagedSchemaProps, ManagedScripts, ManagedScriptsProps, createBreadcrumbSchema, createSchema, createWebSiteOrganizationStub, getLocationSection, getManagedContentData, withManagedMetadata };