@gradial/aci 0.1.46 → 0.1.48
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.
- package/README.md +4 -4
- package/dist/astro/error-page.d.ts +56 -0
- package/dist/astro/error-page.js +79 -0
- package/dist/astro/index.d.ts +4 -0
- package/dist/astro/index.js +9 -0
- package/dist/cli/emit-error-pages.d.mts +39 -0
- package/dist/cli/emit-error-pages.mjs +402 -0
- package/dist/collect/aci.js +1 -1
- package/dist/collect/aci.js.sha256 +1 -1
- package/dist/collect/webvitals.d.ts +3 -0
- package/dist/collect/webvitals.js +21 -11
- package/dist/content/default-error-page-html.d.ts +2 -0
- package/dist/content/default-error-page-html.js +6 -0
- package/dist/content/default-error-page.d.ts +23 -0
- package/dist/content/default-error-page.js +34 -0
- package/dist/content/error-page-kind.d.ts +57 -0
- package/dist/content/error-page-kind.js +106 -0
- package/dist/content/error-pages.d.ts +88 -0
- package/dist/content/error-pages.js +120 -0
- package/dist/content/index.d.ts +2 -0
- package/dist/content/index.js +2 -0
- package/dist/content/types.d.ts +12 -0
- package/dist/edge/personalization-core.js +6 -2
- package/dist/next/config.js +17 -0
- package/dist/next/error-page-instrumentation.d.ts +23 -0
- package/dist/next/error-page-instrumentation.js +36 -0
- package/dist/next/error-page-marker.d.ts +11 -0
- package/dist/next/error-page-marker.js +20 -0
- package/dist/next/error-pages.d.ts +52 -0
- package/dist/next/error-pages.js +94 -0
- package/dist/next/error-seat-payload.d.ts +33 -0
- package/dist/next/error-seat-payload.js +143 -0
- package/dist/next/error-seat.d.ts +57 -0
- package/dist/next/error-seat.js +83 -0
- package/dist/next/index.d.ts +4 -0
- package/dist/next/index.js +8 -0
- package/package.json +14 -8
- package/src/cli/emit-error-pages.mjs +424 -0
- package/templates/astro/template/package.json.tmpl +1 -1
- package/templates/astro/template/src/aci/error-page-instrumentation.mjs +33 -0
- package/templates/astro/template/src/pages/404.astro +54 -0
- package/templates/astro/template/src/pages/500.astro +53 -0
- package/templates/astro/template/src/pages/[...slug].astro +15 -2
- package/templates/common/.agents/skills/authoring/SKILL.md +71 -0
- package/templates/common/.content/config/robots.txt +4 -0
- package/templates/common/.content/config/site.json.tmpl +4 -0
- package/templates/common/.content/pages/404/_index.json +31 -0
- package/templates/common/.content/pages/error/_index.json +31 -0
- package/templates/common/.content/schemas/site.schema.json +9 -0
- package/templates/common/starter-content/.content/config/robots.txt +2 -0
- package/templates/common/starter-content/.content/config/site.json +4 -0
- package/templates/common/starter-content/.content/pages/404/_index.json +31 -0
- package/templates/common/starter-content/.content/pages/about/_index.json +3 -6
- package/templates/common/starter-content/.content/pages/error/_index.json +31 -0
- package/templates/common/starter-content/.content/pages/home/_index.json +10 -19
- package/templates/common/starter-content/.content/schemas/site.schema.json +9 -0
- package/templates/nextjs/template/package.json.tmpl +1 -1
- package/templates/nextjs/template/src/aci/error-page-instrumentation.d.mts +4 -0
- package/templates/nextjs/template/src/aci/error-page-instrumentation.mjs +38 -0
- package/templates/nextjs/template/src/app/error.tsx +18 -0
- package/templates/nextjs/template/src/app/not-found.tsx +13 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Error-page analytics policy — THIS FILE IS YOURS (customer-owned;
|
|
2
|
+
// code-organization R-14 / authorable-error-pages D-15). The ACI SDK owns
|
|
3
|
+
// the delivery mechanism (when this runs, serialization, the
|
|
4
|
+
// data-aci-error-page marker); this file owns WHAT is pushed.
|
|
5
|
+
//
|
|
6
|
+
// Contract:
|
|
7
|
+
// - pushErrorPageView MUST stay self-contained: browser globals only — no
|
|
8
|
+
// imports, no closed-over identifiers. The Astro seats and the 500.html
|
|
9
|
+
// emitter execute it STRINGIFIED via toString(), so anything outside the
|
|
10
|
+
// function body is lost.
|
|
11
|
+
// - Emptying the body to a no-op disables error-page analytics on every
|
|
12
|
+
// surface (404.astro, 500.astro, emitted static copies) — the sanctioned
|
|
13
|
+
// alternative to GTM-container-side suppression.
|
|
14
|
+
// - Plain .mjs on purpose: it is consumed verbatim, never transpiled.
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Pushes one `page_view` event per error-surface view (D-14 default
|
|
18
|
+
* payload). `page_path` is the visitor's requested path, so 404 reports
|
|
19
|
+
* double as a broken-link inventory.
|
|
20
|
+
*
|
|
21
|
+
* @param {'not_found' | 'error'} pageType
|
|
22
|
+
*/
|
|
23
|
+
export function pushErrorPageView(pageType) {
|
|
24
|
+
window.dataLayer = window.dataLayer || [];
|
|
25
|
+
window.dataLayer.push({
|
|
26
|
+
event: 'page_view',
|
|
27
|
+
page_type: pageType,
|
|
28
|
+
page_path: window.location.pathname,
|
|
29
|
+
page_location: window.location.href,
|
|
30
|
+
page_referrer: document.referrer || '(direct)',
|
|
31
|
+
page_title: document.title,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Astro's native notFound error seat (authorable-error-pages REQ-7).
|
|
3
|
+
// Renders the page bound by site.json#errorPages.notFound through the normal
|
|
4
|
+
// SiteLayout path; static builds emit dist/404.html, which doubles as the
|
|
5
|
+
// direct /404/ route and the error surface (D-11 — the instrumentation on a
|
|
6
|
+
// direct visit is the accepted consequence). If the bound page cannot be
|
|
7
|
+
// loaded, the SDK degrades to its shipped default fragment — this seat never
|
|
8
|
+
// throws.
|
|
9
|
+
import { getErrorPageData, createRenderChildren } from '@gradial/aci/astro';
|
|
10
|
+
import { registry } from '../cms/registry';
|
|
11
|
+
import SiteLayout from '../layouts/SiteLayout.astro';
|
|
12
|
+
import { pushErrorPageView } from '../aci/error-page-instrumentation.mjs';
|
|
13
|
+
|
|
14
|
+
const errorPage = await getErrorPageData(Astro, 'notFound', { pushErrorPageView });
|
|
15
|
+
const data = errorPage.data;
|
|
16
|
+
const renderChildren = createRenderChildren(registry);
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
<SiteLayout
|
|
20
|
+
title={errorPage.meta.title}
|
|
21
|
+
description={errorPage.meta.description}
|
|
22
|
+
canonical={errorPage.meta.canonical}
|
|
23
|
+
siteName={errorPage.meta.siteName}
|
|
24
|
+
>
|
|
25
|
+
{data ? (
|
|
26
|
+
data.layout.slots.map((slot) => {
|
|
27
|
+
const blocks = data.slots[slot.name];
|
|
28
|
+
if (!blocks?.length) return null;
|
|
29
|
+
const rendered = renderChildren(blocks);
|
|
30
|
+
if (slot.name === 'main') {
|
|
31
|
+
return (
|
|
32
|
+
<main id="content-main">
|
|
33
|
+
{rendered.map((child) => {
|
|
34
|
+
const Comp = child.Component as any;
|
|
35
|
+
return Comp ? <Comp {...child.props} /> : null;
|
|
36
|
+
})}
|
|
37
|
+
</main>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
return rendered.map((child) => {
|
|
41
|
+
const Comp = child.Component as any;
|
|
42
|
+
return Comp ? <Comp {...child.props} /> : null;
|
|
43
|
+
});
|
|
44
|
+
})
|
|
45
|
+
) : (
|
|
46
|
+
<Fragment set:html={errorPage.fallbackHtml} />
|
|
47
|
+
)}
|
|
48
|
+
<!-- D-14 error-surface analytics: ONE page_view push with the visitor's
|
|
49
|
+
requested path. The push body is site-owned (D-15): the SDK serializes
|
|
50
|
+
src/aci/error-page-instrumentation.mjs into this tag, injected like
|
|
51
|
+
SiteLayout's collectTag; inline scripts execute here — Astro serves
|
|
52
|
+
real HTML documents (unlike the Next flight-rendered seat). -->
|
|
53
|
+
<Fragment set:html={errorPage.instrumentationTag} />
|
|
54
|
+
</SiteLayout>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Astro's native catchAll (500) error seat (authorable-error-pages REQ-7).
|
|
3
|
+
// Renders the page bound by site.json#errorPages.catchAll through the normal
|
|
4
|
+
// SiteLayout path. Static builds emit dist/500.html for the hosting layer;
|
|
5
|
+
// server output serves this seat when a route throws during on-demand
|
|
6
|
+
// rendering. If the bound page cannot be loaded, the SDK degrades to its
|
|
7
|
+
// shipped default fragment — this seat never throws.
|
|
8
|
+
import { getErrorPageData, createRenderChildren } from '@gradial/aci/astro';
|
|
9
|
+
import { registry } from '../cms/registry';
|
|
10
|
+
import SiteLayout from '../layouts/SiteLayout.astro';
|
|
11
|
+
import { pushErrorPageView } from '../aci/error-page-instrumentation.mjs';
|
|
12
|
+
|
|
13
|
+
const errorPage = await getErrorPageData(Astro, 'catchAll', { pushErrorPageView });
|
|
14
|
+
const data = errorPage.data;
|
|
15
|
+
const renderChildren = createRenderChildren(registry);
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
<SiteLayout
|
|
19
|
+
title={errorPage.meta.title}
|
|
20
|
+
description={errorPage.meta.description}
|
|
21
|
+
canonical={errorPage.meta.canonical}
|
|
22
|
+
siteName={errorPage.meta.siteName}
|
|
23
|
+
>
|
|
24
|
+
{data ? (
|
|
25
|
+
data.layout.slots.map((slot) => {
|
|
26
|
+
const blocks = data.slots[slot.name];
|
|
27
|
+
if (!blocks?.length) return null;
|
|
28
|
+
const rendered = renderChildren(blocks);
|
|
29
|
+
if (slot.name === 'main') {
|
|
30
|
+
return (
|
|
31
|
+
<main id="content-main">
|
|
32
|
+
{rendered.map((child) => {
|
|
33
|
+
const Comp = child.Component as any;
|
|
34
|
+
return Comp ? <Comp {...child.props} /> : null;
|
|
35
|
+
})}
|
|
36
|
+
</main>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
return rendered.map((child) => {
|
|
40
|
+
const Comp = child.Component as any;
|
|
41
|
+
return Comp ? <Comp {...child.props} /> : null;
|
|
42
|
+
});
|
|
43
|
+
})
|
|
44
|
+
) : (
|
|
45
|
+
<Fragment set:html={errorPage.fallbackHtml} />
|
|
46
|
+
)}
|
|
47
|
+
<!-- D-14 error-surface analytics: ONE page_view push with the visitor's
|
|
48
|
+
requested path. The push body is site-owned (D-15): the SDK serializes
|
|
49
|
+
src/aci/error-page-instrumentation.mjs into this tag, injected like
|
|
50
|
+
SiteLayout's collectTag; inline scripts execute here — Astro serves
|
|
51
|
+
real HTML documents (unlike the Next flight-rendered seat). -->
|
|
52
|
+
<Fragment set:html={errorPage.instrumentationTag} />
|
|
53
|
+
</SiteLayout>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
import { getGradialStaticPaths, getPageData, createRenderChildren } from '@gradial/aci/astro';
|
|
2
|
+
import { getGradialStaticPaths, getPageData, createRenderChildren, PageNotFoundError, type PageData } from '@gradial/aci/astro';
|
|
3
3
|
import { registry } from '../cms/registry';
|
|
4
4
|
import SiteLayout from '../layouts/SiteLayout.astro';
|
|
5
5
|
|
|
@@ -7,7 +7,20 @@ export const getStaticPaths = getGradialStaticPaths;
|
|
|
7
7
|
|
|
8
8
|
// SDK owns route normalization, content loading, metadata, layout/slot
|
|
9
9
|
// resolution, and collect-tag emission. No FileContentProvider imports.
|
|
10
|
-
|
|
10
|
+
//
|
|
11
|
+
// Server output: unknown routes raise PageNotFoundError here; rewrite to the
|
|
12
|
+
// 404 seat so misses answer HTTP 404, not 500 (authorable-error-pages D-10).
|
|
13
|
+
// src/pages/404.astro is load-bearing for this rewrite — deleting it turns
|
|
14
|
+
// every miss into a 500.
|
|
15
|
+
// Static output never takes this branch — getStaticPaths only emits known
|
|
16
|
+
// routes.
|
|
17
|
+
let data: PageData;
|
|
18
|
+
try {
|
|
19
|
+
data = await getPageData(Astro);
|
|
20
|
+
} catch (error) {
|
|
21
|
+
if (error instanceof PageNotFoundError) return Astro.rewrite('/404/');
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
11
24
|
const renderChildren = createRenderChildren(registry);
|
|
12
25
|
---
|
|
13
26
|
|
|
@@ -238,6 +238,77 @@ Global elements (navigation, footer, branding, SEO) live in
|
|
|
238
238
|
}
|
|
239
239
|
```
|
|
240
240
|
|
|
241
|
+
### Error Pages
|
|
242
|
+
|
|
243
|
+
Sites bind their error pages in `site.json` via `errorPages`:
|
|
244
|
+
|
|
245
|
+
```json
|
|
246
|
+
{
|
|
247
|
+
"errorPages": {
|
|
248
|
+
"notFound": "/404/",
|
|
249
|
+
"catchAll": "/error/"
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
- Each binding is a route path resolving to an **ordinary content page**
|
|
255
|
+
(`.content/pages/404/_index.json`, `.content/pages/error/_index.json`) —
|
|
256
|
+
authored with normal layouts and components like any other page.
|
|
257
|
+
`notFound` serves unknown routes with HTTP 404; `catchAll` serves
|
|
258
|
+
application errors (500-class).
|
|
259
|
+
- Error pages stay out of discovery: set `metadata.sitemap.visible: false`
|
|
260
|
+
on both pages, and the template `robots.txt` disallows both routes.
|
|
261
|
+
- If `errorPages` is absent, the build warns loudly and ships a
|
|
262
|
+
branded-neutral default page (warn-then-enforce migration: absent
|
|
263
|
+
bindings will eventually fail the build, so author them). Present but
|
|
264
|
+
broken bindings — malformed paths, no matching page, unpublished page —
|
|
265
|
+
fail the build immediately.
|
|
266
|
+
- Error pages are base-audience only: no personalization, audience, or
|
|
267
|
+
experiment overlays (validation rejects them), and collect/assignment
|
|
268
|
+
markup is stripped from materialized copies.
|
|
269
|
+
- Analytics: error-surface renders push one `page_view` event to
|
|
270
|
+
`window.dataLayer` with `page_type` (`not_found` | `error`),
|
|
271
|
+
`page_path` (the visitor's requested path), `page_location`,
|
|
272
|
+
`page_referrer`, and `page_title`. The push body is site-owned
|
|
273
|
+
(`src/aci/error-page-instrumentation.mjs` — see the GTM/GA4 section
|
|
274
|
+
below); the SDK wrappers handle delivery.
|
|
275
|
+
|
|
276
|
+
### Error-page analytics (GTM/GA4)
|
|
277
|
+
|
|
278
|
+
The push body is YOURS: it lives in the site-owned
|
|
279
|
+
`src/aci/error-page-instrumentation.mjs`, whose `pushErrorPageView`
|
|
280
|
+
function every error surface (Next seats, Astro seats, the emitted static
|
|
281
|
+
`500.html`) executes. The scaffolded default pushes one event to
|
|
282
|
+
`window.dataLayer`:
|
|
283
|
+
|
|
284
|
+
```js
|
|
285
|
+
{ event: 'page_view', page_type: 'not_found' | 'error',
|
|
286
|
+
page_path, page_location, page_referrer, page_title }
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
- **Customize** — edit the function in place. It must stay self-contained
|
|
290
|
+
(browser globals only, no imports, no closed-over identifiers): some
|
|
291
|
+
surfaces execute it stringified.
|
|
292
|
+
- **Disable** — make the function body a no-op. That turns off error-page
|
|
293
|
+
analytics on every surface, and no GTM-side suppression is needed.
|
|
294
|
+
|
|
295
|
+
Container-side dedup is only needed when you KEEP the default push
|
|
296
|
+
alongside automatic GA4 page views: because the event name is `page_view`,
|
|
297
|
+
either disable `send_page_view` and drive all page views from the
|
|
298
|
+
dataLayer, or add a trigger exception on the config tag for error
|
|
299
|
+
surfaces. The stable suppression signal is the `data-aci-error-page`
|
|
300
|
+
attribute (values `not_found` | `error`; the attribute is SDK-owned and
|
|
301
|
+
stable regardless of what your push does): present on the instrumentation
|
|
302
|
+
script tag (Astro, parse-time) and on a server-rendered marker element
|
|
303
|
+
(Next, in the DOM before container load), so container-load triggers can
|
|
304
|
+
gate on it in both frameworks. If you leave automatic page views on and
|
|
305
|
+
also keep the default push, error pages will double-count — that is a
|
|
306
|
+
container configuration choice, not an SDK default.
|
|
307
|
+
|
|
308
|
+
Direct visits to `/404/` and `/error/` do not carry error context and do
|
|
309
|
+
not push the event (Astro static output is the one exception: its
|
|
310
|
+
`404.html` doubles as the direct route).
|
|
311
|
+
|
|
241
312
|
### ID Conventions
|
|
242
313
|
|
|
243
314
|
- Every block must have a unique `id` within the page.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$type": "page",
|
|
3
|
+
"id": "meridian-not-found",
|
|
4
|
+
"status": "published",
|
|
5
|
+
"layout": "default",
|
|
6
|
+
"metadata": {
|
|
7
|
+
"title": "Page not found — Meridian",
|
|
8
|
+
"description": "The page you are looking for does not exist.",
|
|
9
|
+
"sitemap": { "visible": false }
|
|
10
|
+
},
|
|
11
|
+
"regions": {
|
|
12
|
+
"main": [
|
|
13
|
+
{
|
|
14
|
+
"id": "not-found-hero",
|
|
15
|
+
"component": "home_hero",
|
|
16
|
+
"props": {
|
|
17
|
+
"eyebrow": "404",
|
|
18
|
+
"headline": "This page drifted out of orbit.",
|
|
19
|
+
"description": "The page you are looking for does not exist or has moved. Check the address, or head back to the homepage.",
|
|
20
|
+
"ctaLabel": "Back to home",
|
|
21
|
+
"ctaHref": "/",
|
|
22
|
+
"image": {
|
|
23
|
+
"$type": "dam.assetRef",
|
|
24
|
+
"assetId": "ast_site_home_hero",
|
|
25
|
+
"alt": "Light abstract background with satellite constellation"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$type": "page",
|
|
3
|
+
"id": "meridian-error",
|
|
4
|
+
"status": "published",
|
|
5
|
+
"layout": "default",
|
|
6
|
+
"metadata": {
|
|
7
|
+
"title": "Something went wrong — Meridian",
|
|
8
|
+
"description": "An unexpected error occurred while serving this page.",
|
|
9
|
+
"sitemap": { "visible": false }
|
|
10
|
+
},
|
|
11
|
+
"regions": {
|
|
12
|
+
"main": [
|
|
13
|
+
{
|
|
14
|
+
"id": "error-hero",
|
|
15
|
+
"component": "home_hero",
|
|
16
|
+
"props": {
|
|
17
|
+
"eyebrow": "Temporary interruption",
|
|
18
|
+
"headline": "Something went wrong on our end.",
|
|
19
|
+
"description": "An unexpected error occurred while serving this page. Please try again in a moment, or head back to the homepage.",
|
|
20
|
+
"ctaLabel": "Back to home",
|
|
21
|
+
"ctaHref": "/",
|
|
22
|
+
"image": {
|
|
23
|
+
"$type": "dam.assetRef",
|
|
24
|
+
"assetId": "ast_site_home_hero",
|
|
25
|
+
"alt": "Light abstract background with satellite constellation"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -7,6 +7,15 @@
|
|
|
7
7
|
"additionalProperties": true,
|
|
8
8
|
"properties": {
|
|
9
9
|
"$type": { "const": "site" },
|
|
10
|
+
"errorPages": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"required": ["notFound", "catchAll"],
|
|
13
|
+
"additionalProperties": false,
|
|
14
|
+
"properties": {
|
|
15
|
+
"notFound": { "type": "string", "pattern": "^/(?:[A-Za-z0-9._~-]+/)+$" },
|
|
16
|
+
"catchAll": { "type": "string", "pattern": "^/(?:[A-Za-z0-9._~-]+/)+$" }
|
|
17
|
+
}
|
|
18
|
+
},
|
|
10
19
|
"id": { "type": "string", "minLength": 1 },
|
|
11
20
|
"status": { "type": "string", "minLength": 1 },
|
|
12
21
|
"title": { "type": "string", "minLength": 1 },
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$type": "page",
|
|
3
|
+
"id": "meridian-not-found",
|
|
4
|
+
"status": "published",
|
|
5
|
+
"layout": "default",
|
|
6
|
+
"metadata": {
|
|
7
|
+
"title": "Page not found — Meridian",
|
|
8
|
+
"description": "The page you are looking for does not exist.",
|
|
9
|
+
"sitemap": { "visible": false }
|
|
10
|
+
},
|
|
11
|
+
"regions": {
|
|
12
|
+
"main": [
|
|
13
|
+
{
|
|
14
|
+
"id": "not-found-hero",
|
|
15
|
+
"component": "home_hero",
|
|
16
|
+
"props": {
|
|
17
|
+
"eyebrow": "404",
|
|
18
|
+
"headline": "This page drifted out of orbit.",
|
|
19
|
+
"description": "The page you are looking for does not exist or has moved. Check the address, or head back to the homepage.",
|
|
20
|
+
"ctaLabel": "Back to home",
|
|
21
|
+
"ctaHref": "/",
|
|
22
|
+
"image": {
|
|
23
|
+
"$type": "dam.assetRef",
|
|
24
|
+
"assetId": "ast_site_home_hero",
|
|
25
|
+
"alt": "Light abstract background with satellite constellation"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -24,8 +24,7 @@
|
|
|
24
24
|
"assetId": "ast_site_about_hero",
|
|
25
25
|
"alt": "Meridian network coverage map with soft gradient"
|
|
26
26
|
}
|
|
27
|
-
}
|
|
28
|
-
"$type": "component"
|
|
27
|
+
}
|
|
29
28
|
},
|
|
30
29
|
{
|
|
31
30
|
"id": "about-actions",
|
|
@@ -42,12 +41,10 @@
|
|
|
42
41
|
"variant": "primary",
|
|
43
42
|
"icon": "arrow_forward",
|
|
44
43
|
"iconPosition": "after"
|
|
45
|
-
}
|
|
46
|
-
"$type": "component"
|
|
44
|
+
}
|
|
47
45
|
}
|
|
48
46
|
]
|
|
49
|
-
}
|
|
50
|
-
"$type": "component"
|
|
47
|
+
}
|
|
51
48
|
}
|
|
52
49
|
]
|
|
53
50
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$type": "page",
|
|
3
|
+
"id": "meridian-error",
|
|
4
|
+
"status": "published",
|
|
5
|
+
"layout": "default",
|
|
6
|
+
"metadata": {
|
|
7
|
+
"title": "Something went wrong — Meridian",
|
|
8
|
+
"description": "An unexpected error occurred while serving this page.",
|
|
9
|
+
"sitemap": { "visible": false }
|
|
10
|
+
},
|
|
11
|
+
"regions": {
|
|
12
|
+
"main": [
|
|
13
|
+
{
|
|
14
|
+
"id": "error-hero",
|
|
15
|
+
"component": "home_hero",
|
|
16
|
+
"props": {
|
|
17
|
+
"eyebrow": "Temporary interruption",
|
|
18
|
+
"headline": "Something went wrong on our end.",
|
|
19
|
+
"description": "An unexpected error occurred while serving this page. Please try again in a moment, or head back to the homepage.",
|
|
20
|
+
"ctaLabel": "Back to home",
|
|
21
|
+
"ctaHref": "/",
|
|
22
|
+
"image": {
|
|
23
|
+
"$type": "dam.assetRef",
|
|
24
|
+
"assetId": "ast_site_home_hero",
|
|
25
|
+
"alt": "Light abstract background with satellite constellation"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"status": "published",
|
|
5
5
|
"layout": "default",
|
|
6
6
|
"metadata": {
|
|
7
|
-
"title": "Meridian
|
|
7
|
+
"title": "Meridian \u2014 Global Satellite Internet",
|
|
8
8
|
"description": "Connect from anywhere on Earth. Low-latency satellite internet for homes, businesses, and beyond.",
|
|
9
9
|
"canonical": "https://www.meridian.local/"
|
|
10
10
|
},
|
|
@@ -24,8 +24,7 @@
|
|
|
24
24
|
"assetId": "ast_site_home_hero",
|
|
25
25
|
"alt": "Light abstract hero background with satellite constellation"
|
|
26
26
|
}
|
|
27
|
-
}
|
|
28
|
-
"$type": "component"
|
|
27
|
+
}
|
|
29
28
|
},
|
|
30
29
|
{
|
|
31
30
|
"id": "why-meridian",
|
|
@@ -41,8 +40,7 @@
|
|
|
41
40
|
"icon": "globe",
|
|
42
41
|
"title": "Global coverage",
|
|
43
42
|
"description": "A constellation of low-orbit satellites keeps you connected across continents, oceans, and remote terrain."
|
|
44
|
-
}
|
|
45
|
-
"$type": "component"
|
|
43
|
+
}
|
|
46
44
|
},
|
|
47
45
|
{
|
|
48
46
|
"id": "low-latency",
|
|
@@ -51,8 +49,7 @@
|
|
|
51
49
|
"icon": "speed",
|
|
52
50
|
"title": "Low latency",
|
|
53
51
|
"description": "Sub-50ms latency means video calls, gaming, and real-time control feel local, even in the middle of nowhere."
|
|
54
|
-
}
|
|
55
|
-
"$type": "component"
|
|
52
|
+
}
|
|
56
53
|
},
|
|
57
54
|
{
|
|
58
55
|
"id": "easy-setup",
|
|
@@ -61,12 +58,10 @@
|
|
|
61
58
|
"icon": "setup",
|
|
62
59
|
"title": "Easy setup",
|
|
63
60
|
"description": "A compact terminal and a phone app. Point at the sky, power on, and get online in minutes."
|
|
64
|
-
}
|
|
65
|
-
"$type": "component"
|
|
61
|
+
}
|
|
66
62
|
}
|
|
67
63
|
]
|
|
68
|
-
}
|
|
69
|
-
"$type": "component"
|
|
64
|
+
}
|
|
70
65
|
},
|
|
71
66
|
{
|
|
72
67
|
"id": "stats",
|
|
@@ -94,8 +89,7 @@
|
|
|
94
89
|
"suffix": ""
|
|
95
90
|
}
|
|
96
91
|
]
|
|
97
|
-
}
|
|
98
|
-
"$type": "component"
|
|
92
|
+
}
|
|
99
93
|
},
|
|
100
94
|
{
|
|
101
95
|
"id": "cta",
|
|
@@ -113,8 +107,7 @@
|
|
|
113
107
|
"variant": "primary",
|
|
114
108
|
"icon": "arrow_forward",
|
|
115
109
|
"iconPosition": "after"
|
|
116
|
-
}
|
|
117
|
-
"$type": "component"
|
|
110
|
+
}
|
|
118
111
|
},
|
|
119
112
|
{
|
|
120
113
|
"id": "cta-secondary",
|
|
@@ -125,12 +118,10 @@
|
|
|
125
118
|
"variant": "outline",
|
|
126
119
|
"icon": "arrow_forward",
|
|
127
120
|
"iconPosition": "after"
|
|
128
|
-
}
|
|
129
|
-
"$type": "component"
|
|
121
|
+
}
|
|
130
122
|
}
|
|
131
123
|
]
|
|
132
|
-
}
|
|
133
|
-
"$type": "component"
|
|
124
|
+
}
|
|
134
125
|
}
|
|
135
126
|
]
|
|
136
127
|
}
|
|
@@ -7,6 +7,15 @@
|
|
|
7
7
|
"additionalProperties": true,
|
|
8
8
|
"properties": {
|
|
9
9
|
"$type": { "const": "site" },
|
|
10
|
+
"errorPages": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"required": ["notFound", "catchAll"],
|
|
13
|
+
"additionalProperties": false,
|
|
14
|
+
"properties": {
|
|
15
|
+
"notFound": { "type": "string", "pattern": "^/(?:[A-Za-z0-9._~-]+/)+$" },
|
|
16
|
+
"catchAll": { "type": "string", "pattern": "^/(?:[A-Za-z0-9._~-]+/)+$" }
|
|
17
|
+
}
|
|
18
|
+
},
|
|
10
19
|
"id": { "type": "string", "minLength": 1 },
|
|
11
20
|
"status": { "type": "string", "minLength": 1 },
|
|
12
21
|
"title": { "type": "string", "minLength": 1 },
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"dev": "ACI_CONTENT_PROVIDER=file next dev -H 0.0.0.0",
|
|
8
|
-
"build": "next build",
|
|
8
|
+
"build": "node ./node_modules/@gradial/aci/src/cli/emit-error-pages.mjs prepare && next build && node ./node_modules/@gradial/aci/src/cli/emit-error-pages.mjs emit",
|
|
9
9
|
"preview": "next start -H 0.0.0.0",
|
|
10
10
|
"typecheck": "tsc --noEmit",
|
|
11
11
|
"test": "node --import tsx --test tests/**/*.test.ts",
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// Type surface for the customer-owned policy module beside this file
|
|
2
|
+
// (authorable-error-pages D-15). Edit the .mjs; this declaration only needs
|
|
3
|
+
// touching if you change the function's signature.
|
|
4
|
+
export declare function pushErrorPageView(pageType: 'not_found' | 'error'): void;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Error-page analytics policy — THIS FILE IS YOURS (customer-owned;
|
|
2
|
+
// code-organization R-14 / authorable-error-pages D-15). The ACI SDK owns
|
|
3
|
+
// the delivery mechanism (when this runs, serialization, the
|
|
4
|
+
// data-aci-error-page marker); this file owns WHAT is pushed.
|
|
5
|
+
//
|
|
6
|
+
// Contract:
|
|
7
|
+
// - pushErrorPageView MUST stay self-contained: browser globals only — no
|
|
8
|
+
// imports, no closed-over identifiers. The Astro seats and the 500.html
|
|
9
|
+
// emitter execute it STRINGIFIED via toString(), so anything outside the
|
|
10
|
+
// function body is lost.
|
|
11
|
+
// - Emptying the body to a no-op disables error-page analytics on every
|
|
12
|
+
// surface (Next seats, Astro seats, 500.html) — the sanctioned
|
|
13
|
+
// alternative to GTM-container-side suppression.
|
|
14
|
+
// - Plain .mjs on purpose: it is consumed verbatim, never transpiled.
|
|
15
|
+
//
|
|
16
|
+
// 'use client' (Next only, inert elsewhere): the not-found seat is a server
|
|
17
|
+
// component and can only hand a function to the client-side instrumentation
|
|
18
|
+
// component as a client reference.
|
|
19
|
+
'use client';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Pushes one `page_view` event per error-surface view (D-14 default
|
|
23
|
+
* payload). `page_path` is the visitor's requested path, so 404 reports
|
|
24
|
+
* double as a broken-link inventory.
|
|
25
|
+
*
|
|
26
|
+
* @param {'not_found' | 'error'} pageType
|
|
27
|
+
*/
|
|
28
|
+
export function pushErrorPageView(pageType) {
|
|
29
|
+
window.dataLayer = window.dataLayer || [];
|
|
30
|
+
window.dataLayer.push({
|
|
31
|
+
event: 'page_view',
|
|
32
|
+
page_type: pageType,
|
|
33
|
+
page_path: window.location.pathname,
|
|
34
|
+
page_location: window.location.href,
|
|
35
|
+
page_referrer: document.referrer || '(direct)',
|
|
36
|
+
page_title: document.title,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { ErrorSeat } from '@gradial/aci/next/error-seat';
|
|
4
|
+
import { registry } from '@/cms/registry';
|
|
5
|
+
import { pushErrorPageView } from '../aci/error-page-instrumentation.mjs';
|
|
6
|
+
|
|
7
|
+
// Next's app-error seat (client-only by contract). ErrorSeat renders the
|
|
8
|
+
// page bound by site.json#errorPages.catchAll from content serialized into
|
|
9
|
+
// the bundle at build time — nothing is fetched while the app is failing.
|
|
10
|
+
// The analytics push comes from the site-owned policy module (D-15):
|
|
11
|
+
// edit src/aci/error-page-instrumentation.mjs to customize or disable it.
|
|
12
|
+
//
|
|
13
|
+
// Coverage: render errors below the root layout. NOT covered here: root
|
|
14
|
+
// layout errors (global-error), middleware failures, and platform 5xx —
|
|
15
|
+
// those are served the static public/500.html emitted by the build.
|
|
16
|
+
export default function AppError({ error }: { error: Error & { digest?: string } }) {
|
|
17
|
+
return <ErrorSeat registry={registry} error={error} pushErrorPageView={pushErrorPageView} />;
|
|
18
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createNotFound } from '@gradial/aci/next';
|
|
2
|
+
import { registry } from '@/cms/registry';
|
|
3
|
+
import { pushErrorPageView } from '../aci/error-page-instrumentation.mjs';
|
|
4
|
+
|
|
5
|
+
// Next's native not-found seat: the SDK catch-all invokes notFound() on
|
|
6
|
+
// missing content, and this renders the page bound by
|
|
7
|
+
// site.json#errorPages.notFound with a real HTTP 404. If that page is itself
|
|
8
|
+
// missing at request time, the SDK falls back to its shipped default inline —
|
|
9
|
+
// this seat never recurses and never throws.
|
|
10
|
+
//
|
|
11
|
+
// The analytics push comes from the site-owned policy module (D-15):
|
|
12
|
+
// edit src/aci/error-page-instrumentation.mjs to customize or disable it.
|
|
13
|
+
export default createNotFound(registry, { pushErrorPageView });
|