bsmnt 0.13.1 → 0.15.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.
- package/dist/core/create/internal-ids.d.ts +1 -0
- package/dist/core/create/internal-ids.d.ts.map +1 -1
- package/dist/core/create/internal-ids.js +1 -0
- package/dist/core/create/internal-ids.js.map +1 -1
- package/dist/domain/skills.d.ts +5 -0
- package/dist/domain/skills.d.ts.map +1 -1
- package/dist/domain/skills.js +9 -1
- package/dist/domain/skills.js.map +1 -1
- package/dist/infrastructure/create/format-project.d.ts +9 -0
- package/dist/infrastructure/create/format-project.d.ts.map +1 -0
- package/dist/infrastructure/create/format-project.js +21 -0
- package/dist/infrastructure/create/format-project.js.map +1 -0
- package/dist/infrastructure/create/lifecycle-registry.d.ts.map +1 -1
- package/dist/infrastructure/create/lifecycle-registry.js +5 -0
- package/dist/infrastructure/create/lifecycle-registry.js.map +1 -1
- package/dist/modules/features/cms/sanity-pagebuilder/config.d.ts.map +1 -1
- package/dist/modules/features/cms/sanity-pagebuilder/config.js +1 -0
- package/dist/modules/features/cms/sanity-pagebuilder/config.js.map +1 -1
- package/dist/modules/features/cms/sanity-pagebuilder/index.d.ts.map +1 -1
- package/dist/modules/features/cms/sanity-pagebuilder/index.js +1 -0
- package/dist/modules/features/cms/sanity-pagebuilder/index.js.map +1 -1
- package/dist/modules/features/cms/sanity-pagebuilder-cache/config.d.ts.map +1 -1
- package/dist/modules/features/cms/sanity-pagebuilder-cache/config.js +1 -0
- package/dist/modules/features/cms/sanity-pagebuilder-cache/config.js.map +1 -1
- package/dist/modules/features/cms/sanity-pagebuilder-cache/index.d.ts.map +1 -1
- package/dist/modules/features/cms/sanity-pagebuilder-cache/index.js +1 -0
- package/dist/modules/features/cms/sanity-pagebuilder-cache/index.js.map +1 -1
- package/package.json +1 -1
- package/src/agent-skills/ashby-pagebuilder-block/SKILL.md +29 -11
- package/src/agent-skills/hubspot-pagebuilder-block/SKILL.md +31 -12
- package/src/agent-skills/launch-ready/SKILL.md +114 -0
- package/src/agent-skills/launch-ready/references/a11y.md +74 -0
- package/src/agent-skills/launch-ready/references/content.md +65 -0
- package/src/agent-skills/launch-ready/references/crawlers.md +89 -0
- package/src/agent-skills/launch-ready/references/launch.md +44 -0
- package/src/agent-skills/launch-ready/references/metadata.md +83 -0
- package/src/agent-skills/launch-ready/references/performance.md +36 -0
- package/src/agent-skills/launch-ready/references/routing.md +47 -0
- package/src/agent-skills/launch-ready/references/security.md +39 -0
- package/src/agent-skills/page-builder-block/SKILL.md +26 -13
- package/src/modules/features/cms/sanity/files/app/layout.tsx +2 -2
- package/src/modules/features/cms/sanity/files/lib/utils/metadata.ts +2 -2
- package/src/modules/features/cms/sanity/files/lib/utils/url.ts +6 -3
- package/src/modules/features/cms/sanity-pagebuilder/files/app/api/revalidate/route.ts +2 -2
- package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/rich-text.tsx +4 -2
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/block-actions-item.tsx +2 -2
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/component-preview-frame.tsx +124 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/component-preview-pane.tsx +25 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/page-builder-input.tsx +3 -26
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/page-builder-insert-menu.tsx +121 -93
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/redirect-import-dialog.tsx +538 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/csv.ts +88 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/fetchers/breadcrumbs.ts +22 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/icons.ts +2 -2
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-builder-config.ts +36 -49
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/presentation.ts +1 -50
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/queries.ts +76 -64
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/redirect-import.ts +651 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/sanity.config.ts +6 -8
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/index.ts +0 -1
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/index.ts +1 -4
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/redirect.ts +9 -6
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/structured-data.ts +8 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/block.ts +4 -4
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/metadata.ts +60 -6
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page-builder.ts +4 -18
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page.ts +33 -38
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/richText.ts +0 -1
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/singletons.ts +0 -16
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/structure.ts +52 -35
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/tags.ts +5 -2
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/scripts/sanity-typegen.ts +8 -11
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/metadata.ts +109 -136
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/studio-embed.ts +22 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/revalidate/route.ts +2 -2
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/sanity/rich-text.tsx +4 -2
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/block-actions-item.tsx +2 -2
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/component-preview-frame.tsx +124 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/component-preview-pane.tsx +25 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/page-builder-input.tsx +3 -26
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/page-builder-insert-menu.tsx +121 -93
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/redirect-import-dialog.tsx +538 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/csv.ts +88 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/fetchers/breadcrumbs.ts +25 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/icons.ts +2 -2
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/page-builder-config.ts +36 -49
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/presentation.ts +1 -50
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/queries.ts +76 -64
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/redirect-import.ts +651 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/sanity.config.ts +6 -8
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/index.ts +0 -1
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/index.ts +1 -4
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/redirect.ts +9 -6
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/structured-data.ts +8 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/block.ts +4 -4
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/metadata.ts +60 -6
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/page-builder.ts +4 -18
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/page.ts +33 -38
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/richText.ts +0 -1
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/singletons.ts +0 -16
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/structure.ts +52 -35
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/tags.ts +6 -2
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/scripts/sanity-typegen.ts +8 -11
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/metadata.ts +109 -136
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/studio-embed.ts +22 -0
- package/src/modules/features/forms/hubspot/files/_sanity-adapter/form-block.ts +4 -10
- package/src/modules/features/forms/hubspot/files/lib/integrations/hubspot/README.md +10 -5
- package/src/modules/features/jobs/ashby/files/_sanity-adapter/job-board-block.ts +0 -7
- package/src/modules/features/jobs/ashby/files/lib/integrations/ashby/README.md +11 -5
- package/src/templates/next-default/.env.example +3 -1
- package/src/templates/next-default/app/layout.tsx +2 -1
- package/src/templates/next-default/app/robots.ts +9 -3
- package/src/templates/next-default/lib/utils/metadata.ts +2 -1
- package/src/templates/next-default/lib/utils/url.ts +20 -3
- package/src/templates/next-default/next.config.ts +16 -0
- package/src/templates/next-experiments/.env.example +3 -1
- package/src/templates/next-experiments/app/layout.tsx +2 -1
- package/src/templates/next-experiments/app/robots.ts +9 -3
- package/src/templates/next-experiments/lib/utils/metadata.ts +2 -1
- package/src/templates/next-experiments/lib/utils/url.ts +20 -3
- package/src/templates/next-experiments/next.config.ts +16 -0
- package/src/templates/next-pagebuilder/.env.example +3 -1
- package/src/templates/next-pagebuilder/.github/workflows/ci.yml +32 -8
- package/src/templates/next-pagebuilder/AGENTS.md +22 -7
- package/src/templates/next-pagebuilder/README.md +15 -3
- package/src/templates/next-pagebuilder/app/(content)/[[...slug]]/page.tsx +45 -7
- package/src/templates/next-pagebuilder/app/actions/unlock-gated-content.ts +5 -4
- package/src/templates/next-pagebuilder/app/components/page.tsx +81 -0
- package/src/templates/next-pagebuilder/app/layout.tsx +3 -41
- package/src/templates/next-pagebuilder/app/robots.ts +9 -3
- package/src/templates/next-pagebuilder/biome.json +2 -1
- package/src/templates/next-pagebuilder/components/layout/footer/index.tsx +1 -1
- package/src/templates/next-pagebuilder/components/page-builder/components/gated-content/index.tsx +8 -3
- package/src/templates/next-pagebuilder/components/page-builder/library.ts +183 -0
- package/src/templates/next-pagebuilder/components/page-builder/registry.tsx +4 -1
- package/src/templates/next-pagebuilder/components/page-builder/renderer.tsx +3 -1
- package/src/templates/next-pagebuilder/components/page-builder/stage.tsx +10 -0
- package/src/templates/next-pagebuilder/components/page-document/index.tsx +28 -35
- package/src/templates/next-pagebuilder/lefthook.yml +1 -1
- package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.types.ts +170 -299
- package/src/templates/next-pagebuilder/lib/utils/base-url.ts +18 -3
- package/src/templates/next-pagebuilder/lib/utils/breadcrumbs.ts +70 -0
- package/src/templates/next-pagebuilder/next.config.ts +16 -0
- package/src/templates/next-pagebuilder/package.json +1 -1
- package/src/templates/next-pagebuilder-cache/.env.example +3 -1
- package/src/templates/next-pagebuilder-cache/.github/workflows/ci.yml +32 -8
- package/src/templates/next-pagebuilder-cache/AGENTS.md +22 -7
- package/src/templates/next-pagebuilder-cache/README.md +15 -3
- package/src/templates/next-pagebuilder-cache/app/(content)/[[...slug]]/page.tsx +46 -7
- package/src/templates/next-pagebuilder-cache/app/actions/unlock-gated-content.ts +5 -4
- package/src/templates/next-pagebuilder-cache/app/components/page.tsx +91 -0
- package/src/templates/next-pagebuilder-cache/app/layout.tsx +5 -46
- package/src/templates/next-pagebuilder-cache/app/robots.ts +9 -3
- package/src/templates/next-pagebuilder-cache/biome.json +2 -1
- package/src/templates/next-pagebuilder-cache/components/layout/footer/index.tsx +1 -1
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/gated-content/index.tsx +8 -3
- package/src/templates/next-pagebuilder-cache/components/page-builder/library.ts +183 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/registry.tsx +4 -1
- package/src/templates/next-pagebuilder-cache/components/page-builder/renderer.tsx +3 -1
- package/src/templates/next-pagebuilder-cache/components/page-builder/stage.tsx +10 -0
- package/src/templates/next-pagebuilder-cache/components/page-document/index.tsx +28 -35
- package/src/templates/next-pagebuilder-cache/lefthook.yml +1 -1
- package/src/templates/next-pagebuilder-cache/lib/integrations/sanity/sanity.types.ts +170 -299
- package/src/templates/next-pagebuilder-cache/lib/utils/base-url.ts +18 -3
- package/src/templates/next-pagebuilder-cache/lib/utils/breadcrumbs.ts +70 -0
- package/src/templates/next-pagebuilder-cache/next.config.ts +16 -0
- package/src/templates/next-pagebuilder-cache/package.json +1 -1
- package/src/templates/next-webgl/.env.example +3 -1
- package/src/templates/next-webgl/app/layout.tsx +2 -1
- package/src/templates/next-webgl/app/robots.ts +9 -3
- package/src/templates/next-webgl/lib/utils/metadata.ts +2 -1
- package/src/templates/next-webgl/lib/utils/url.ts +20 -3
- package/src/templates/next-webgl/next.config.ts +16 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/use-studio-preview-urls.ts +0 -62
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/studio-preview.ts +0 -37
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/use-studio-preview-urls.ts +0 -62
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/studio-preview.ts +0 -37
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Accessibility
|
|
2
|
+
|
|
3
|
+
Contrast, motion and target size are design decisions. Report failures; don't
|
|
4
|
+
change brand colors or layout unasked.
|
|
5
|
+
|
|
6
|
+
## A11Y-01 · Every page has an `h1`
|
|
7
|
+
|
|
8
|
+
**Fix before launch**
|
|
9
|
+
|
|
10
|
+
One `h1` that names the page. Screen reader users jump to it first.
|
|
11
|
+
|
|
12
|
+
**Check:** `curl -s localhost:3000/<route> | grep -c '<h1'` prints at least `1`.
|
|
13
|
+
|
|
14
|
+
## A11Y-02 · Everything works by keyboard
|
|
15
|
+
|
|
16
|
+
**Fix before launch**
|
|
17
|
+
|
|
18
|
+
Interactive elements are real controls (`<button>`, `<Link>`, inputs), reachable
|
|
19
|
+
with Tab, with a visible focus style. A `<div onClick>` can't be reached
|
|
20
|
+
without a mouse. Menus and modals close on Escape and return focus.
|
|
21
|
+
|
|
22
|
+
**Check (browser):** Tab through the page. Every control is reachable, and
|
|
23
|
+
you can always see where focus is.
|
|
24
|
+
|
|
25
|
+
## A11Y-03 · Icon-only controls have a name
|
|
26
|
+
|
|
27
|
+
**Fix before launch**
|
|
28
|
+
|
|
29
|
+
An icon-only button or link carries an `aria-label` or visually hidden text.
|
|
30
|
+
Otherwise a screen reader announces only "button".
|
|
31
|
+
|
|
32
|
+
## A11Y-04 · Inputs have labels
|
|
33
|
+
|
|
34
|
+
**Fix before launch**
|
|
35
|
+
|
|
36
|
+
Each input has a `<label>`. A placeholder isn't one; it disappears as soon as
|
|
37
|
+
someone types. Fields for personal data carry the matching `autocomplete`
|
|
38
|
+
value (`email`, `name`, `tel`).
|
|
39
|
+
|
|
40
|
+
## A11Y-05 · Text meets contrast
|
|
41
|
+
|
|
42
|
+
**Fix before launch**
|
|
43
|
+
|
|
44
|
+
Body text is at least 4.5:1 against its background, and large text (24px, or
|
|
45
|
+
19px bold) at least 3:1. Logos, disabled controls and decorative text are
|
|
46
|
+
exempt.
|
|
47
|
+
|
|
48
|
+
**Check (browser):** Lighthouse's accessibility audit, or the axe DevTools
|
|
49
|
+
extension.
|
|
50
|
+
|
|
51
|
+
## A11Y-06 · Motion can be paused or reduced
|
|
52
|
+
|
|
53
|
+
**Fix before launch**
|
|
54
|
+
|
|
55
|
+
Anything that moves on its own for more than 5 seconds (autoplay video,
|
|
56
|
+
marquees, carousels) has a pause control. Non-essential animation, smooth
|
|
57
|
+
scrolling included, respects `prefers-reduced-motion`.
|
|
58
|
+
|
|
59
|
+
## A11Y-07 · Targets are big enough; zoom stays on
|
|
60
|
+
|
|
61
|
+
**Fix before launch**
|
|
62
|
+
|
|
63
|
+
Tap targets are at least 24×24px, or have 24px of clearance. Links inside a
|
|
64
|
+
sentence are exempt.
|
|
65
|
+
|
|
66
|
+
Never set `maximumScale: 1` or `userScalable: false` in the viewport. If iOS
|
|
67
|
+
zooms into a focused input, set the input's font size to at least 16px instead.
|
|
68
|
+
|
|
69
|
+
## A11Y-08 · No horizontal scroll at 320px
|
|
70
|
+
|
|
71
|
+
**Fix before launch**
|
|
72
|
+
|
|
73
|
+
The page fits a 320px-wide viewport without horizontal scrolling. Maps, data
|
|
74
|
+
tables and diagrams are exempt.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Content
|
|
2
|
+
|
|
3
|
+
## CONTENT-01 · Indexed copy is in the server HTML
|
|
4
|
+
|
|
5
|
+
**Must not ship** · only if indexed
|
|
6
|
+
|
|
7
|
+
Text meant for search, link previews and AI is in the server HTML. Don't draw
|
|
8
|
+
it only in a canvas, and don't load it only through `dynamic(..., { ssr: false })`.
|
|
9
|
+
A WebGL scene can stay client-only; its headline and copy can't.
|
|
10
|
+
|
|
11
|
+
**Check:** a sentence from the page's main copy appears in
|
|
12
|
+
`curl -s localhost:3000/<route>`.
|
|
13
|
+
|
|
14
|
+
## CONTENT-02 · No placeholder output
|
|
15
|
+
|
|
16
|
+
**Must not ship**
|
|
17
|
+
|
|
18
|
+
No `undefined`, `null`, `NaN`, `[object Object]` or lorem ipsum in rendered
|
|
19
|
+
text. These usually appear after launch, when an editor leaves a CMS field
|
|
20
|
+
empty, so render nothing for a missing field instead.
|
|
21
|
+
|
|
22
|
+
**Check:**
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
curl -s localhost:3000/<route> | grep -noiE 'undefined|NaN|\[object Object\]|lorem ipsum'
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
A match is a lead, not proof: documentation can say "returns `undefined`" on
|
|
29
|
+
purpose.
|
|
30
|
+
|
|
31
|
+
## CONTENT-03 · Each fact has one source
|
|
32
|
+
|
|
33
|
+
**Must not ship**
|
|
34
|
+
|
|
35
|
+
A price, spec or limit shown in more than one place reads from one module or
|
|
36
|
+
one CMS field. Two copies drift, and the site contradicts itself.
|
|
37
|
+
|
|
38
|
+
**Check:** search the repo for the value. It appears once, where it's defined.
|
|
39
|
+
|
|
40
|
+
## CONTENT-04 · No test pages, drafts or staging links
|
|
41
|
+
|
|
42
|
+
**Must not ship**
|
|
43
|
+
|
|
44
|
+
Test pages, unpublished drafts and links to staging or preview hosts aren't
|
|
45
|
+
reachable in production or listed in the sitemap.
|
|
46
|
+
|
|
47
|
+
**Check:** search the production HTML and `sitemap.xml` for `test`, `staging`,
|
|
48
|
+
`preview` and `vercel.app`.
|
|
49
|
+
|
|
50
|
+
## CONTENT-05 · No starter leftovers
|
|
51
|
+
|
|
52
|
+
**Fix before launch**
|
|
53
|
+
|
|
54
|
+
Replace basement's placeholders with the site's own, wherever they remain:
|
|
55
|
+
|
|
56
|
+
- the `app/favicon.ico` every template ships
|
|
57
|
+
- the `"Basement Starter"` title in `app/layout.tsx`
|
|
58
|
+
- `siteName = "Basement"` in `lib/utils/metadata.ts`
|
|
59
|
+
- the `"Basement Next Starter"` homepage heading
|
|
60
|
+
|
|
61
|
+
**Check:**
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
grep -rn 'Basement Starter\|Basement Next Starter\|siteName = "Basement"' app lib components
|
|
65
|
+
```
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Crawlers
|
|
2
|
+
|
|
3
|
+
## CRAWL-01 · Indexing matches the decision
|
|
4
|
+
|
|
5
|
+
**Must not ship** when production and the decision disagree
|
|
6
|
+
|
|
7
|
+
`app/robots.ts` and each page's `noIndex` follow *Indexed by search engines*.
|
|
8
|
+
Previews are never indexable.
|
|
9
|
+
|
|
10
|
+
The starter indexes only the host of `NEXT_PUBLIC_BASE_URL`, and on Vercel
|
|
11
|
+
only on a production deployment. Every other host, including `*.vercel.app`,
|
|
12
|
+
gets `X-Robots-Tag: noindex` and a disallow-all `robots.txt`. Without
|
|
13
|
+
`NEXT_PUBLIC_BASE_URL`, nothing is indexed.
|
|
14
|
+
|
|
15
|
+
**Check:**
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
curl -s https://<production>/robots.txt
|
|
19
|
+
curl -sI https://<production>/ | grep -i x-robots-tag
|
|
20
|
+
curl -sI https://<preview>/ | grep -i x-robots-tag
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Production matches the decision. The preview prints `noindex`.
|
|
24
|
+
|
|
25
|
+
## CRAWL-02 · Canonicals point at the right page
|
|
26
|
+
|
|
27
|
+
**Must not ship**
|
|
28
|
+
|
|
29
|
+
A canonical is absolute and points at a page that returns 200 and is indexable.
|
|
30
|
+
|
|
31
|
+
If `app/layout.tsx` sets `alternates.canonical: "/"`, every page that doesn't
|
|
32
|
+
set its own canonical inherits it, which points the whole site at the
|
|
33
|
+
homepage. Set it per page with `generatePageMetadata({ url })`, or remove it
|
|
34
|
+
from the layout.
|
|
35
|
+
|
|
36
|
+
If the layout declares an `en-US` alternate at `/en-US`, remove it unless a
|
|
37
|
+
route serves that locale.
|
|
38
|
+
|
|
39
|
+
**Check:**
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
curl -s localhost:3000/<route> | grep -o '<link rel="canonical"[^>]*>'
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
It names that route, not `/`.
|
|
46
|
+
|
|
47
|
+
## CRAWL-03 · The sitemap lists every indexable page
|
|
48
|
+
|
|
49
|
+
**Fix before launch** · only if indexed
|
|
50
|
+
|
|
51
|
+
Build the sitemap from the same data as the routes: every static route, plus
|
|
52
|
+
each CMS document with a public URL. The starter's `app/sitemap.xml/route.ts`
|
|
53
|
+
lists only `/`; add each route to `getStaticEntries()`. When the sitemap reads
|
|
54
|
+
a CMS, a new content type needs adding to its query.
|
|
55
|
+
|
|
56
|
+
**Check:**
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
curl -s localhost:3000/sitemap.xml | grep -c '<loc>'
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The count matches the number of public pages.
|
|
63
|
+
|
|
64
|
+
## CRAWL-04 · The sitemap lists only live pages
|
|
65
|
+
|
|
66
|
+
**Fix before launch** · only if indexed
|
|
67
|
+
|
|
68
|
+
No sitemap URL redirects, 404s or is `noindex`, and each one uses the
|
|
69
|
+
production host.
|
|
70
|
+
|
|
71
|
+
**Check:**
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
curl -s localhost:3000/sitemap.xml | grep -o '<loc>[^<]*' | cut -c6- |
|
|
75
|
+
while read -r u; do printf '%s %s\n' "$(curl -so /dev/null -w '%{http_code}' "$u")" "$u"; done
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Every line starts with `200`.
|
|
79
|
+
|
|
80
|
+
## CRAWL-05 · AI crawler policy matches the decision
|
|
81
|
+
|
|
82
|
+
**Fix before launch**
|
|
83
|
+
|
|
84
|
+
- *AI crawlers: allowed* needs nothing.
|
|
85
|
+
- *AI crawlers: blocked* adds a `disallow: "/"` rule in `robots.ts` for each
|
|
86
|
+
agent: `GPTBot`, `ClaudeBot`, `PerplexityBot`, `Google-Extended`, `CCBot`.
|
|
87
|
+
- *llms.txt: yes* serves `/llms.txt`.
|
|
88
|
+
|
|
89
|
+
**Check:** `curl -s https://<production>/robots.txt` matches the decision.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# On the real domain
|
|
2
|
+
|
|
3
|
+
These only fail in production, so check them against the deployed URL.
|
|
4
|
+
|
|
5
|
+
## LAUNCH-01 · Forms deliver on production
|
|
6
|
+
|
|
7
|
+
**Must not ship** · only if the site has forms
|
|
8
|
+
|
|
9
|
+
Submit each form on production and confirm the submission arrives. The usual
|
|
10
|
+
failure: the form's API key is set for Preview but not for Production, so the
|
|
11
|
+
form works on every preview and silently drops submissions after launch.
|
|
12
|
+
|
|
13
|
+
## LAUNCH-02 · CMS publishing updates production
|
|
14
|
+
|
|
15
|
+
**Must not ship** · only if the site uses a CMS
|
|
16
|
+
|
|
17
|
+
Publish a small change in the CMS and confirm production shows it. The
|
|
18
|
+
revalidation webhook points at the production URL and carries its secret.
|
|
19
|
+
|
|
20
|
+
## LAUNCH-03 · One host
|
|
21
|
+
|
|
22
|
+
**Fix before launch**
|
|
23
|
+
|
|
24
|
+
Pick the apex domain or `www`. In Vercel's domain settings, the other
|
|
25
|
+
redirects to it permanently.
|
|
26
|
+
|
|
27
|
+
**Check:**
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
curl -sI https://<other-host>/ | grep -i '^location'
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
It points at the chosen host.
|
|
34
|
+
|
|
35
|
+
## LAUNCH-04 · Old URLs redirect on a relaunch
|
|
36
|
+
|
|
37
|
+
**Fix before launch** · only if *Replaces an existing site: yes*
|
|
38
|
+
|
|
39
|
+
Every old URL with traffic or backlinks redirects to its new equivalent
|
|
40
|
+
through `redirects()` in `next.config.ts`. Ask the user for the old site's URL
|
|
41
|
+
list: its sitemap, or its top pages in analytics.
|
|
42
|
+
|
|
43
|
+
**Check:** request a sample of old URLs. Each returns `308` to a page that
|
|
44
|
+
returns `200`.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Metadata
|
|
2
|
+
|
|
3
|
+
## META-01 · Every page has a real title
|
|
4
|
+
|
|
5
|
+
**Must not ship**
|
|
6
|
+
|
|
7
|
+
Each route exports `metadata` or `generateMetadata` with its own title. If
|
|
8
|
+
`app/layout.tsx` still has the `"Basement Starter"` default and its
|
|
9
|
+
`"%s - Basement Starter"` template, replace both with the site's name.
|
|
10
|
+
|
|
11
|
+
**Check:**
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
curl -s localhost:3000/<route> | grep -o '<title>[^<]*'
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## META-02 · Metadata is in the server HTML
|
|
18
|
+
|
|
19
|
+
**Must not ship**
|
|
20
|
+
|
|
21
|
+
Set titles, descriptions and share tags through the Metadata API, never with
|
|
22
|
+
`document.title` or a client effect. Crawlers and link previews don't run
|
|
23
|
+
JavaScript.
|
|
24
|
+
|
|
25
|
+
**Check:** the tags appear in `curl -s localhost:3000/<route>`, not only in
|
|
26
|
+
the browser's DOM.
|
|
27
|
+
|
|
28
|
+
## META-03 · The share image loads
|
|
29
|
+
|
|
30
|
+
**Must not ship**
|
|
31
|
+
|
|
32
|
+
A declared `og:image` is absolute and returns an image.
|
|
33
|
+
|
|
34
|
+
If the layout or `generatePageMetadata` defaults to `/opengraph-image.jpg`,
|
|
35
|
+
add that file: no template ships it. Until `public/opengraph-image.jpg`
|
|
36
|
+
exists, every share card is broken, and so is the Organization JSON-LD logo,
|
|
37
|
+
which uses the same path.
|
|
38
|
+
|
|
39
|
+
**Check:**
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
img=$(curl -s localhost:3000/ | grep -o 'property="og:image" content="[^"]*' | cut -d'"' -f4)
|
|
43
|
+
curl -so /dev/null -w '%{http_code} %{content_type}\n' "$img"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Expect `200 image/...`.
|
|
47
|
+
|
|
48
|
+
## META-04 · No `localhost` URLs in production
|
|
49
|
+
|
|
50
|
+
**Must not ship**
|
|
51
|
+
|
|
52
|
+
`metadataBase`, canonicals and share URLs read `NEXT_PUBLIC_BASE_URL`, then
|
|
53
|
+
the Vercel deployment URL, and fall back to `http://localhost:3000` off Vercel.
|
|
54
|
+
Set it for the Production environment.
|
|
55
|
+
|
|
56
|
+
**Check:**
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
curl -s https://<production>/ | grep -c 'localhost:3000'
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Expect `0`.
|
|
63
|
+
|
|
64
|
+
## META-05 · Titles are unique
|
|
65
|
+
|
|
66
|
+
**Fix before launch**
|
|
67
|
+
|
|
68
|
+
Distinct pages have distinct titles. Variants that canonicalize to one page
|
|
69
|
+
are exempt.
|
|
70
|
+
|
|
71
|
+
## META-06 · Public pages have a description
|
|
72
|
+
|
|
73
|
+
**Fix before launch**
|
|
74
|
+
|
|
75
|
+
Slack, LinkedIn and iMessage show the description as the preview text. If the
|
|
76
|
+
layout's default reads `package.json`'s `description`, write a real one there.
|
|
77
|
+
|
|
78
|
+
## META-07 · `<html lang>` matches the content
|
|
79
|
+
|
|
80
|
+
**Fix before launch**
|
|
81
|
+
|
|
82
|
+
The starter sets `lang="en"`. Change it when the site isn't English, and set
|
|
83
|
+
it per locale on a multilingual site.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Performance
|
|
2
|
+
|
|
3
|
+
## PERF-01 · The LCP image isn't lazy
|
|
4
|
+
|
|
5
|
+
**Fix before launch**
|
|
6
|
+
|
|
7
|
+
The largest image in the first screen (usually the hero) passes `priority` to
|
|
8
|
+
`@/components/ui/image`. Everything below the fold stays lazy, which is the
|
|
9
|
+
default.
|
|
10
|
+
|
|
11
|
+
## PERF-02 · Image `sizes` match the layout
|
|
12
|
+
|
|
13
|
+
**Fix before launch**
|
|
14
|
+
|
|
15
|
+
`sizes` describes the width the image renders at. Without it, the image
|
|
16
|
+
component falls back to `100vw` and the browser downloads the largest
|
|
17
|
+
variant, which is only right for full-width images. Build `sizes` with
|
|
18
|
+
`buildImageSizes` or `buildGridImageSizes` from `lib/utils/image-sizes.ts`.
|
|
19
|
+
|
|
20
|
+
The `no-fill-without-sizes` lint rule catches `fill` with no `sizes`. It can't
|
|
21
|
+
tell whether the value is correct.
|
|
22
|
+
|
|
23
|
+
## PERF-03 · Responses are compressed
|
|
24
|
+
|
|
25
|
+
**Fix before launch** · only when hosted outside Vercel
|
|
26
|
+
|
|
27
|
+
Vercel compresses responses by default. Another host has to serve HTML, JS
|
|
28
|
+
and CSS with gzip or brotli.
|
|
29
|
+
|
|
30
|
+
**Check:**
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
curl -sI -H 'Accept-Encoding: gzip, br' https://<production>/ | grep -i content-encoding
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Expect `gzip` or `br`.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Routing
|
|
2
|
+
|
|
3
|
+
## ROUTE-01 · Missing pages return 404
|
|
4
|
+
|
|
5
|
+
**Must not ship**
|
|
6
|
+
|
|
7
|
+
When a dynamic or CMS route has no data, call `notFound()` before any
|
|
8
|
+
`<Suspense>`. Once a response starts streaming, its status is already 200.
|
|
9
|
+
Next can't change it then, and injects a `noindex` tag instead.
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
export default async function Page({ params }: PageProps<"/blog/[slug]">) {
|
|
13
|
+
const { slug } = await params
|
|
14
|
+
const post = await getPost(slug)
|
|
15
|
+
if (!post) notFound()
|
|
16
|
+
return <Post post={post} />
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Check:**
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
curl -so /dev/null -w '%{http_code}\n' localhost:3000/does-not-exist
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Expect `404`. Repeat under each dynamic segment, such as `/blog/does-not-exist`.
|
|
27
|
+
|
|
28
|
+
**Not this:** redirecting unknown paths to `/`. That's still a missing page
|
|
29
|
+
answering 200.
|
|
30
|
+
|
|
31
|
+
## ROUTE-02 · No internal link is broken
|
|
32
|
+
|
|
33
|
+
**Must not ship**
|
|
34
|
+
|
|
35
|
+
Every internal link resolves to a page that returns 200. A control that only
|
|
36
|
+
runs JavaScript is a `<button>`, not `<a href="#">`. An `#id` that exists on the
|
|
37
|
+
page, or a "back to top" link, is fine.
|
|
38
|
+
|
|
39
|
+
**Check:** list each route's links and request them.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
curl -s localhost:3000/ | grep -o 'href="/[^"#]*"' | cut -d'"' -f2 | sort -u |
|
|
43
|
+
while read -r p; do printf '%s %s\n' "$(curl -so /dev/null -w '%{http_code}' "localhost:3000$p")" "$p"; done
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Every line starts with `200`. A broken link in the nav or footer breaks on every
|
|
47
|
+
page, so check those first.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Security and hygiene
|
|
2
|
+
|
|
3
|
+
## SEC-01 · `NEXT_PUBLIC_` holds only public keys
|
|
4
|
+
|
|
5
|
+
**Must not ship**
|
|
6
|
+
|
|
7
|
+
Anything prefixed `NEXT_PUBLIC_` ships in the browser bundle, readable by
|
|
8
|
+
anyone. Only keys designed to be public belong there: a Sanity project ID, a
|
|
9
|
+
Stripe `pk_` key, an analytics site ID. Read every other secret on the server.
|
|
10
|
+
|
|
11
|
+
**Check:**
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
grep -rhoE 'NEXT_PUBLIC_[A-Z0-9_]+' app components lib | sort -u
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Confirm each name on the list is meant to be public.
|
|
18
|
+
|
|
19
|
+
## SEC-02 · Tracking matches the consent decision
|
|
20
|
+
|
|
21
|
+
**Fix before launch** · only if consent is required
|
|
22
|
+
|
|
23
|
+
No analytics or ad script runs, and no tracking cookie is set, before the
|
|
24
|
+
visitor answers the consent banner. The `ketch-consent-check` skill covers
|
|
25
|
+
gating a script behind Ketch, when the project has it.
|
|
26
|
+
|
|
27
|
+
**Check (browser):** in a fresh private window, before you answer the banner,
|
|
28
|
+
DevTools → Application → Cookies lists no tracking cookies.
|
|
29
|
+
|
|
30
|
+
## SEC-03 · No first-party console errors
|
|
31
|
+
|
|
32
|
+
**Fix before launch**
|
|
33
|
+
|
|
34
|
+
The console shows no errors from the site's own code on load, and no
|
|
35
|
+
hydration mismatches. A mismatch means the server and the client rendered
|
|
36
|
+
different content. Errors from third-party scripts are out of scope.
|
|
37
|
+
|
|
38
|
+
**Check (browser):** load each route with DevTools open, then read the
|
|
39
|
+
console.
|
|
@@ -13,21 +13,23 @@ description: >-
|
|
|
13
13
|
|
|
14
14
|
# Page-builder block conventions
|
|
15
15
|
|
|
16
|
-
Blocks are **
|
|
16
|
+
Blocks are **inline objects** in the `pageBuilder` array — never documents, and
|
|
17
|
+
never referenced. A placement belongs to exactly one page, so the block lives on
|
|
18
|
+
that page and nothing is dereferenced at read time. They are drawn by
|
|
17
19
|
`components/page-builder/renderer.tsx` through the registry in
|
|
18
20
|
`components/page-builder/registry.tsx`. Read one existing block end to end
|
|
19
21
|
before writing a new one — `hero` is the smallest complete example:
|
|
20
22
|
|
|
21
23
|
- schema: `lib/integrations/sanity/schemas/components/reusable/hero.ts`
|
|
22
24
|
- component: `components/page-builder/components/hero.tsx`
|
|
25
|
+
- fixture: `components/page-builder/library.ts`
|
|
23
26
|
- registration: `lib/integrations/sanity/schemas/components/index.ts`,
|
|
24
27
|
`lib/integrations/sanity/schemas/index.ts`,
|
|
25
28
|
`lib/integrations/sanity/page-builder-config.ts`,
|
|
26
|
-
`lib/integrations/sanity/structure.ts`,
|
|
27
29
|
`components/page-builder/registry.tsx`
|
|
28
30
|
|
|
29
31
|
A block exists in two halves — a schema and a component — and each half has one
|
|
30
|
-
place that makes it a block rather than an ordinary
|
|
32
|
+
place that makes it a block rather than an ordinary object type or a loose React
|
|
31
33
|
component. Use them and the conventions below come for free; skip them and a
|
|
32
34
|
typecheck fails.
|
|
33
35
|
|
|
@@ -35,8 +37,10 @@ typecheck fails.
|
|
|
35
37
|
|
|
36
38
|
1. **Define the schema with `defineBlock`, never `defineType`.**
|
|
37
39
|
`lib/integrations/sanity/schemas/shared/block.ts` implies
|
|
38
|
-
`type: "
|
|
39
|
-
fields
|
|
40
|
+
`type: "object"` and appends Anchor ID + Presentation Mode as the last two
|
|
41
|
+
fields. The array member's type name is the block's `_type`, so the schema
|
|
42
|
+
name is also what the registry, the insert menu and the fixture library key
|
|
43
|
+
off:
|
|
40
44
|
|
|
41
45
|
```ts
|
|
42
46
|
import { defineField } from "sanity"
|
|
@@ -123,7 +127,15 @@ typecheck fails.
|
|
|
123
127
|
`SANITY_LAYOUT_TAGS` (`fetchers/layout.ts`, or `tags.ts` on the Cache
|
|
124
128
|
Components variant), same as navbar and footer.
|
|
125
129
|
|
|
126
|
-
5. **
|
|
130
|
+
5. **Ship a fixture in `components/page-builder/library.ts`.** One entry per
|
|
131
|
+
block: a stand-in value typed as the block's *query result* (so a schema or
|
|
132
|
+
projection change breaks the fixture at typecheck), a one-line note and a
|
|
133
|
+
stage background. That entry is the block's only preview — the live pane in
|
|
134
|
+
the insert menu, its row in Studio → Components and the `/components` route
|
|
135
|
+
all render it through the production renderer. Nothing needs adding to
|
|
136
|
+
`structure.ts`.
|
|
137
|
+
|
|
138
|
+
6. **Regenerate types after a schema change:** `bun run sanity:typegen`. The
|
|
127
139
|
page query spreads `...` for non-gated blocks, so new fields flow through
|
|
128
140
|
automatically — except `gatedContent`, whose GROQ branch lists fields
|
|
129
141
|
explicitly (`lib/integrations/sanity/queries.ts`). Add new fields there by
|
|
@@ -131,20 +143,21 @@ typecheck fails.
|
|
|
131
143
|
|
|
132
144
|
## Checklist for a new block
|
|
133
145
|
|
|
134
|
-
- [ ] Schema in `schemas/components/reusable/<block>.ts
|
|
135
|
-
|
|
146
|
+
- [ ] Schema in `schemas/components/reusable/<block>.ts`, built with
|
|
147
|
+
`defineBlock` and carrying a `preview`.
|
|
136
148
|
- [ ] Icon in `lib/integrations/sanity/icons.ts` (phosphor duotone).
|
|
137
149
|
- [ ] Exported from `schemas/components/index.ts` and registered in
|
|
138
150
|
`schemas/index.ts` (`schema.types`).
|
|
139
151
|
- [ ] Member added to `page-builder-config.ts`: the `PageBuilderDocumentType`
|
|
140
|
-
union plus an entry in `
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
when nothing fits).
|
|
144
|
-
- [ ] Listed in `structure.ts` under the right folder.
|
|
152
|
+
union plus an entry in `pageBuilderBlockMembers` carrying **both** axes —
|
|
153
|
+
`pageTypes` (which page documents may host it) and `category` (what the
|
|
154
|
+
block is, one of `componentCategories`; `misc` only when nothing fits).
|
|
145
155
|
- [ ] Component in `components/page-builder/components/<block>.tsx`, typed with
|
|
146
156
|
`Extract<PageBuilderBlock, { _type: "<block>" }>`, no outer section, no
|
|
147
157
|
vertical padding. Variants via `cva`/`cn` when the block has variants.
|
|
148
158
|
- [ ] Entry in `components/page-builder/registry.tsx`, with `isEmpty` when the
|
|
149
159
|
block can be left unfilled.
|
|
160
|
+
- [ ] Fixture in `components/page-builder/library.ts`, typed as the block's
|
|
161
|
+
query result. It is the block's preview everywhere, so nothing needs
|
|
162
|
+
adding to `structure.ts`.
|
|
150
163
|
- [ ] `bun run sanity:typegen && bun run typecheck && bun run lint && bun run build && bun run knip`.
|
|
@@ -9,13 +9,13 @@ import { fontsVariable } from "@/lib/styles/fonts";
|
|
|
9
9
|
import AppData from "@/package.json";
|
|
10
10
|
import "@/lib/styles/css/index.css";
|
|
11
11
|
import { cn } from "@/lib/styles/cn";
|
|
12
|
+
import { getBaseUrl } from "@/lib/utils/url";
|
|
12
13
|
|
|
13
14
|
const APP_NAME = AppData.name;
|
|
14
15
|
const APP_DEFAULT_TITLE = "Basement Starter";
|
|
15
16
|
const APP_TITLE_TEMPLATE = "%s - Basement Starter";
|
|
16
17
|
const APP_DESCRIPTION = AppData.description;
|
|
17
|
-
const APP_BASE_URL =
|
|
18
|
-
process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000";
|
|
18
|
+
const APP_BASE_URL = getBaseUrl();
|
|
19
19
|
|
|
20
20
|
const geist = Geist({
|
|
21
21
|
subsets: ["latin"],
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Metadata } from "next";
|
|
2
|
+
import { getBaseUrl } from "@/lib/utils/url";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Metadata Generation Utilities
|
|
@@ -26,8 +27,7 @@ interface GenerateMetadataOptions {
|
|
|
26
27
|
authors?: string[];
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
const APP_BASE_URL =
|
|
30
|
-
process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000";
|
|
30
|
+
const APP_BASE_URL = getBaseUrl();
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Generate complete metadata object for pages
|
|
@@ -3,15 +3,18 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Priority:
|
|
5
5
|
* 1. NEXT_PUBLIC_BASE_URL - explicit override (custom domains)
|
|
6
|
-
* 2. VERCEL_PROJECT_PRODUCTION_URL - auto-set by Vercel
|
|
7
|
-
* 3. VERCEL_URL -
|
|
6
|
+
* 2. VERCEL_PROJECT_PRODUCTION_URL - auto-set by Vercel, on production deployments only
|
|
7
|
+
* 3. VERCEL_URL - the deployment's own URL, so previews never emit production URLs
|
|
8
8
|
* 4. localhost fallback
|
|
9
9
|
*/
|
|
10
10
|
export function getBaseUrl(): string {
|
|
11
11
|
if (process.env.NEXT_PUBLIC_BASE_URL) {
|
|
12
12
|
return process.env.NEXT_PUBLIC_BASE_URL;
|
|
13
13
|
}
|
|
14
|
-
if (
|
|
14
|
+
if (
|
|
15
|
+
process.env.VERCEL_ENV === "production" &&
|
|
16
|
+
process.env.VERCEL_PROJECT_PRODUCTION_URL
|
|
17
|
+
) {
|
|
15
18
|
return `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`;
|
|
16
19
|
}
|
|
17
20
|
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`;
|
|
@@ -8,7 +8,7 @@ import { parseBody } from "next-sanity/webhook";
|
|
|
8
8
|
import { directClient } from "@/lib/integrations/sanity/client";
|
|
9
9
|
import { SANITY_LAYOUT_TAGS } from "@/lib/integrations/sanity/fetchers/layout";
|
|
10
10
|
import { SANITY_REDIRECTS_TAG } from "@/lib/integrations/sanity/fetchers/redirects";
|
|
11
|
-
import {
|
|
11
|
+
import { pageBuilderBlockMembers } from "@/lib/integrations/sanity/page-builder-config";
|
|
12
12
|
import {
|
|
13
13
|
FOLDER_PATH_QUERY,
|
|
14
14
|
P_REF_DOC_PAGES_QUERY,
|
|
@@ -63,7 +63,7 @@ type RevalidateWebhookBody = WebhookDocument & {
|
|
|
63
63
|
};
|
|
64
64
|
|
|
65
65
|
const referenceableTypes = new Set<string>([
|
|
66
|
-
...
|
|
66
|
+
...pageBuilderBlockMembers.map((member) => member.documentType),
|
|
67
67
|
...REFERENCEABLE_TYPES,
|
|
68
68
|
]);
|
|
69
69
|
|
|
@@ -4,7 +4,7 @@ import { SanityImage } from "@/components/ui/sanity-image";
|
|
|
4
4
|
import {
|
|
5
5
|
getLinkAttributes,
|
|
6
6
|
urlForReference,
|
|
7
|
-
} from "
|
|
7
|
+
} from "@/lib/integrations/sanity/utils/link";
|
|
8
8
|
|
|
9
9
|
interface RichTextProps {
|
|
10
10
|
content: PortableTextProps["value"];
|
|
@@ -80,7 +80,9 @@ export const RichText = ({ content }: RichTextProps) => {
|
|
|
80
80
|
externalLink: LinkMark,
|
|
81
81
|
},
|
|
82
82
|
block: {
|
|
83
|
-
h1:
|
|
83
|
+
// The h1 style is retired: the page owns its H1, so legacy content
|
|
84
|
+
// drops a level.
|
|
85
|
+
h1: ({ children }) => <h2 className="h2">{children}</h2>,
|
|
84
86
|
h2: ({ children }) => <h2 className="h2">{children}</h2>,
|
|
85
87
|
h3: ({ children }) => <h3 className="h3">{children}</h3>,
|
|
86
88
|
h4: ({ children }) => <h4 className="h4">{children}</h4>,
|