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
|
@@ -3,15 +3,30 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Priority:
|
|
5
5
|
* 1. NEXT_PUBLIC_BASE_URL (explicit production URL)
|
|
6
|
-
* 2. VERCEL_PROJECT_PRODUCTION_URL
|
|
7
|
-
* 3. VERCEL_URL (
|
|
6
|
+
* 2. VERCEL_PROJECT_PRODUCTION_URL, on production deployments only
|
|
7
|
+
* 3. VERCEL_URL (the deployment's own URL, so previews never emit production URLs)
|
|
8
8
|
* 4. http://localhost:3000 (local development)
|
|
9
9
|
*/
|
|
10
10
|
export const getBaseUrl = () =>
|
|
11
11
|
process.env.NEXT_PUBLIC_BASE_URL ||
|
|
12
|
-
(process.env.
|
|
12
|
+
(process.env.VERCEL_ENV === "production" &&
|
|
13
|
+
process.env.VERCEL_PROJECT_PRODUCTION_URL
|
|
13
14
|
? `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`
|
|
14
15
|
: undefined) ||
|
|
15
16
|
(process.env.VERCEL_URL
|
|
16
17
|
? `https://${process.env.VERCEL_URL}`
|
|
17
18
|
: "http://localhost:3000")
|
|
19
|
+
|
|
20
|
+
// The production build also answers on *.vercel.app; only the launch domain is indexed.
|
|
21
|
+
export const getIndexableHost = () => {
|
|
22
|
+
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL
|
|
23
|
+
// Off Vercel VERCEL_ENV is unset, and the configured host is the launch domain.
|
|
24
|
+
const vercelEnv = process.env.VERCEL_ENV
|
|
25
|
+
if (!baseUrl || (vercelEnv && vercelEnv !== "production")) return null
|
|
26
|
+
return URL.canParse(baseUrl) ? new URL(baseUrl).host : null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const isIndexableHost = (host: string | null | undefined) => {
|
|
30
|
+
const indexable = getIndexableHost()
|
|
31
|
+
return indexable !== null && host?.toLowerCase() === indexable
|
|
32
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { BREADCRUMB_PAGES_QUERY_RESULT } from "@/lib/integrations/sanity/sanity.types"
|
|
2
|
+
|
|
3
|
+
type BreadcrumbPage = BREADCRUMB_PAGES_QUERY_RESULT[number]
|
|
4
|
+
|
|
5
|
+
type Breadcrumb = {
|
|
6
|
+
name: string
|
|
7
|
+
url: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const HOME_NAME = "Home"
|
|
11
|
+
|
|
12
|
+
const trimSlashes = (path: string) => path.replace(/^\/+|\/+$/g, "")
|
|
13
|
+
|
|
14
|
+
const firstNonEmpty = (...values: (string | null | undefined)[]) =>
|
|
15
|
+
values.find((value) => value?.trim())?.trim()
|
|
16
|
+
|
|
17
|
+
const urlFor = (path: string) => (path ? `/${path}` : "/")
|
|
18
|
+
|
|
19
|
+
const labelFor = (page: BreadcrumbPage | undefined) =>
|
|
20
|
+
page && firstNonEmpty(page.shortTitle, page.folderTitle, page.title)
|
|
21
|
+
|
|
22
|
+
// Every parent path, not the path itself: "a/b/c" → ["a", "a/b"].
|
|
23
|
+
const ancestorPaths = (path: string) => {
|
|
24
|
+
const segments = path.split("/").filter(Boolean)
|
|
25
|
+
return segments
|
|
26
|
+
.slice(0, -1)
|
|
27
|
+
.map((_, index) => segments.slice(0, index + 1).join("/"))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// The paths a page's crumbs are named from, besides the homepage: its
|
|
31
|
+
// ancestors and itself.
|
|
32
|
+
export const breadcrumbPaths = (path: string | null) => {
|
|
33
|
+
const current = trimSlashes(path ?? "")
|
|
34
|
+
return current ? [...ancestorPaths(current), current] : []
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Crumbs are named from the pages at each level, the current one included:
|
|
38
|
+
// Short Title, then the folder name for a folder's index page, then Title. A
|
|
39
|
+
// level with no page is skipped, so a crumb never links to a 404.
|
|
40
|
+
export const buildBreadcrumbs = ({
|
|
41
|
+
path,
|
|
42
|
+
name,
|
|
43
|
+
pages,
|
|
44
|
+
}: {
|
|
45
|
+
path: string | null
|
|
46
|
+
name: string
|
|
47
|
+
pages: readonly BreadcrumbPage[]
|
|
48
|
+
}): Breadcrumb[] => {
|
|
49
|
+
const pageAt = (target: string) =>
|
|
50
|
+
pages.find((page) => trimSlashes(page.path ?? "") === target)
|
|
51
|
+
|
|
52
|
+
const home = pageAt("")
|
|
53
|
+
const crumbs: Breadcrumb[] = [
|
|
54
|
+
{
|
|
55
|
+
name: firstNonEmpty(home?.shortTitle, home?.title) ?? HOME_NAME,
|
|
56
|
+
url: "/",
|
|
57
|
+
},
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
const current = trimSlashes(path ?? "")
|
|
61
|
+
if (!current) return crumbs
|
|
62
|
+
|
|
63
|
+
for (const ancestor of ancestorPaths(current)) {
|
|
64
|
+
const ancestorName = labelFor(pageAt(ancestor))
|
|
65
|
+
if (ancestorName) crumbs.push({ name: ancestorName, url: urlFor(ancestor) })
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
crumbs.push({ name: labelFor(pageAt(current)) ?? name, url: urlFor(current) })
|
|
69
|
+
return crumbs
|
|
70
|
+
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import withBundleAnalyzer from "@next/bundle-analyzer"
|
|
2
2
|
import type { NextConfig } from "next"
|
|
3
3
|
import { sanity } from "next-sanity/live/cache-life"
|
|
4
|
+
import { getIndexableHost } from "./lib/utils/base-url"
|
|
5
|
+
|
|
6
|
+
// Only the launch domain is indexed; every other host is sent noindex.
|
|
7
|
+
const indexableHost = getIndexableHost()
|
|
4
8
|
|
|
5
9
|
const nextConfig: NextConfig = {
|
|
6
10
|
cacheComponents: true,
|
|
@@ -97,6 +101,18 @@ const nextConfig: NextConfig = {
|
|
|
97
101
|
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
|
|
98
102
|
},
|
|
99
103
|
headers: async () => [
|
|
104
|
+
{
|
|
105
|
+
source: "/:path*",
|
|
106
|
+
...(indexableHost && {
|
|
107
|
+
missing: [
|
|
108
|
+
{
|
|
109
|
+
type: "host" as const,
|
|
110
|
+
value: indexableHost.replaceAll(".", "\\."),
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
}),
|
|
114
|
+
headers: [{ key: "X-Robots-Tag", value: "noindex" }],
|
|
115
|
+
},
|
|
100
116
|
{
|
|
101
117
|
// Excludes /studio* — must stay mutually exclusive with the rule below.
|
|
102
118
|
source: "/((?!studio(?:/|$)).*)",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"knip": "knip",
|
|
18
18
|
"knip:fix": "knip --fix",
|
|
19
19
|
"lighthouse": "bunx @unlighthouse/cli --site http://localhost:3000",
|
|
20
|
-
"lint": "biome
|
|
20
|
+
"lint": "biome ci --error-on-warnings --max-diagnostics=200 .",
|
|
21
21
|
"lint:fix": "biome check --write --unsafe --max-diagnostics=200",
|
|
22
22
|
"prepare": "lefthook install",
|
|
23
23
|
"start": "next start",
|
|
@@ -33,7 +33,9 @@
|
|
|
33
33
|
# Development: http://localhost:3000
|
|
34
34
|
# Production: https://your-domain.com
|
|
35
35
|
# Inlined at build time — scope this to the Production environment only in
|
|
36
|
-
# Vercel so Preview deployments fall through to
|
|
36
|
+
# Vercel so Preview deployments fall through to their own VERCEL_URL. On a
|
|
37
|
+
# production deployment it is also the only host that gets indexed: every other
|
|
38
|
+
# host is sent `X-Robots-Tag: noindex` and a disallow-all robots.txt.
|
|
37
39
|
NEXT_PUBLIC_BASE_URL=""
|
|
38
40
|
|
|
39
41
|
# Draft mode secret for preview functionality
|
|
@@ -11,12 +11,13 @@ import {
|
|
|
11
11
|
generateWebSiteJsonLd,
|
|
12
12
|
JsonLd,
|
|
13
13
|
} from "@/lib/utils/json-ld"
|
|
14
|
+
import { getBaseUrl } from "@/lib/utils/url"
|
|
14
15
|
|
|
15
16
|
const APP_NAME = AppData.name
|
|
16
17
|
const APP_DEFAULT_TITLE = "Basement Starter"
|
|
17
18
|
const APP_TITLE_TEMPLATE = "%s - Basement Starter"
|
|
18
19
|
const APP_DESCRIPTION = AppData.description
|
|
19
|
-
const APP_BASE_URL =
|
|
20
|
+
const APP_BASE_URL = getBaseUrl()
|
|
20
21
|
|
|
21
22
|
const geist = Geist({
|
|
22
23
|
subsets: ["latin"],
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import type { MetadataRoute } from "next"
|
|
2
|
-
import {
|
|
2
|
+
import { headers } from "next/headers"
|
|
3
|
+
import { getBaseUrl, isIndexableHost } from "@/lib/utils/url"
|
|
4
|
+
|
|
5
|
+
export default async function robots(): Promise<MetadataRoute.Robots> {
|
|
6
|
+
const host = (await headers()).get("host")
|
|
7
|
+
if (!isIndexableHost(host)) {
|
|
8
|
+
return { rules: { userAgent: "*", disallow: "/" } }
|
|
9
|
+
}
|
|
3
10
|
|
|
4
|
-
export default function robots(): MetadataRoute.Robots {
|
|
5
11
|
return {
|
|
6
12
|
rules: {
|
|
7
13
|
userAgent: "*",
|
|
8
14
|
allow: "/",
|
|
9
|
-
disallow: [],
|
|
15
|
+
disallow: ["/api/", "/studio"],
|
|
10
16
|
},
|
|
11
17
|
sitemap: `${getBaseUrl()}/sitemap.xml`,
|
|
12
18
|
}
|
|
@@ -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,7 +27,7 @@ interface GenerateMetadataOptions {
|
|
|
26
27
|
authors?: string[]
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
const APP_BASE_URL =
|
|
30
|
+
const APP_BASE_URL = getBaseUrl()
|
|
30
31
|
|
|
31
32
|
/**
|
|
32
33
|
* Generate complete metadata object for pages
|
|
@@ -3,17 +3,34 @@
|
|
|
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}`
|
|
18
21
|
return "http://localhost:3000"
|
|
19
22
|
}
|
|
23
|
+
|
|
24
|
+
// The production build also answers on *.vercel.app; only the launch domain is indexed.
|
|
25
|
+
export function getIndexableHost(): string | null {
|
|
26
|
+
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL
|
|
27
|
+
// Off Vercel VERCEL_ENV is unset, and the configured host is the launch domain.
|
|
28
|
+
const vercelEnv = process.env.VERCEL_ENV
|
|
29
|
+
if (!baseUrl || (vercelEnv && vercelEnv !== "production")) return null
|
|
30
|
+
return URL.canParse(baseUrl) ? new URL(baseUrl).host : null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function isIndexableHost(host: string | null | undefined): boolean {
|
|
34
|
+
const indexable = getIndexableHost()
|
|
35
|
+
return indexable !== null && host?.toLowerCase() === indexable
|
|
36
|
+
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import withBundleAnalyzer from "@next/bundle-analyzer"
|
|
2
2
|
import type { NextConfig } from "next"
|
|
3
|
+
import { getIndexableHost } from "./lib/utils/url"
|
|
4
|
+
|
|
5
|
+
// Only the launch domain is indexed; every other host is sent noindex.
|
|
6
|
+
const indexableHost = getIndexableHost()
|
|
3
7
|
|
|
4
8
|
const nextConfig: NextConfig = {
|
|
5
9
|
reactStrictMode: true,
|
|
@@ -92,6 +96,18 @@ const nextConfig: NextConfig = {
|
|
|
92
96
|
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
|
|
93
97
|
},
|
|
94
98
|
headers: async () => [
|
|
99
|
+
{
|
|
100
|
+
source: "/:path*",
|
|
101
|
+
...(indexableHost && {
|
|
102
|
+
missing: [
|
|
103
|
+
{
|
|
104
|
+
type: "host" as const,
|
|
105
|
+
value: indexableHost.replaceAll(".", "\\."),
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
}),
|
|
109
|
+
headers: [{ key: "X-Robots-Tag", value: "noindex" }],
|
|
110
|
+
},
|
|
95
111
|
{
|
|
96
112
|
// Excludes /studio* — must stay mutually exclusive with the rule below.
|
|
97
113
|
source: "/((?!studio(?:/|$)).*)",
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from "react";
|
|
2
|
-
import { useClient } from "sanity";
|
|
3
|
-
import { apiVersion } from "@/lib/integrations/sanity/env";
|
|
4
|
-
|
|
5
|
-
const PREVIEW_ASSETS_QUERY = `*[
|
|
6
|
-
_type == "studioPreview" &&
|
|
7
|
-
key in $keys &&
|
|
8
|
-
defined(preview.asset._ref)
|
|
9
|
-
] | order(_updatedAt desc) {
|
|
10
|
-
key,
|
|
11
|
-
"url": preview.asset->url
|
|
12
|
-
}`;
|
|
13
|
-
|
|
14
|
-
type PreviewDocument = {
|
|
15
|
-
key: string | null;
|
|
16
|
-
url: string | null;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export type StudioPreviewUrls = Record<string, string>;
|
|
20
|
-
|
|
21
|
-
const SEPARATOR = " ";
|
|
22
|
-
|
|
23
|
-
/** `undefined` while in flight, so callers can tell loading from "no preview". */
|
|
24
|
-
export function useStudioPreviewUrls(keys: readonly string[]) {
|
|
25
|
-
const client = useClient({ apiVersion });
|
|
26
|
-
const [urls, setUrls] = useState<StudioPreviewUrls>();
|
|
27
|
-
// Keys off contents, not array identity — callers pass a freshly mapped array.
|
|
28
|
-
const keyList = keys.join(SEPARATOR);
|
|
29
|
-
|
|
30
|
-
useEffect(() => {
|
|
31
|
-
if (!keyList) {
|
|
32
|
-
setUrls({});
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
let cancelled = false;
|
|
37
|
-
|
|
38
|
-
client
|
|
39
|
-
.fetch<PreviewDocument[]>(PREVIEW_ASSETS_QUERY, {
|
|
40
|
-
keys: keyList.split(SEPARATOR),
|
|
41
|
-
})
|
|
42
|
-
.then((documents) => {
|
|
43
|
-
if (cancelled) return;
|
|
44
|
-
const next: StudioPreviewUrls = {};
|
|
45
|
-
for (const document of documents) {
|
|
46
|
-
if (document.key && document.url && !next[document.key]) {
|
|
47
|
-
next[document.key] = document.url;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
setUrls(next);
|
|
51
|
-
})
|
|
52
|
-
.catch(() => {
|
|
53
|
-
if (!cancelled) setUrls({});
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
return () => {
|
|
57
|
-
cancelled = true;
|
|
58
|
-
};
|
|
59
|
-
}, [client, keyList]);
|
|
60
|
-
|
|
61
|
-
return urls;
|
|
62
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { defineField, defineType } from "sanity";
|
|
2
|
-
import { pageBuilderCardIcon } from "@/lib/integrations/sanity/icons";
|
|
3
|
-
import { pageBuilderBlockPreviewOptions } from "@/lib/integrations/sanity/page-builder-config";
|
|
4
|
-
|
|
5
|
-
export const studioPreview = defineType({
|
|
6
|
-
name: "studioPreview",
|
|
7
|
-
title: "Component Preview",
|
|
8
|
-
type: "document",
|
|
9
|
-
icon: pageBuilderCardIcon,
|
|
10
|
-
fields: [
|
|
11
|
-
defineField({
|
|
12
|
-
name: "key",
|
|
13
|
-
title: "Component",
|
|
14
|
-
type: "string",
|
|
15
|
-
options: { list: pageBuilderBlockPreviewOptions },
|
|
16
|
-
validation: (Rule) => Rule.required(),
|
|
17
|
-
}),
|
|
18
|
-
defineField({
|
|
19
|
-
name: "preview",
|
|
20
|
-
title: "Preview image",
|
|
21
|
-
type: "image",
|
|
22
|
-
options: { hotspot: true },
|
|
23
|
-
validation: (Rule) => Rule.required(),
|
|
24
|
-
}),
|
|
25
|
-
],
|
|
26
|
-
preview: {
|
|
27
|
-
select: { key: "key", media: "preview" },
|
|
28
|
-
prepare({ key, media }) {
|
|
29
|
-
return {
|
|
30
|
-
title:
|
|
31
|
-
pageBuilderBlockPreviewOptions.find((option) => option.value === key)
|
|
32
|
-
?.title ?? "Component Preview",
|
|
33
|
-
media,
|
|
34
|
-
};
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
});
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from "react";
|
|
2
|
-
import { useClient } from "sanity";
|
|
3
|
-
import { apiVersion } from "@/lib/integrations/sanity/env";
|
|
4
|
-
|
|
5
|
-
const PREVIEW_ASSETS_QUERY = `*[
|
|
6
|
-
_type == "studioPreview" &&
|
|
7
|
-
key in $keys &&
|
|
8
|
-
defined(preview.asset._ref)
|
|
9
|
-
] | order(_updatedAt desc) {
|
|
10
|
-
key,
|
|
11
|
-
"url": preview.asset->url
|
|
12
|
-
}`;
|
|
13
|
-
|
|
14
|
-
type PreviewDocument = {
|
|
15
|
-
key: string | null;
|
|
16
|
-
url: string | null;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export type StudioPreviewUrls = Record<string, string>;
|
|
20
|
-
|
|
21
|
-
const SEPARATOR = " ";
|
|
22
|
-
|
|
23
|
-
/** `undefined` while in flight, so callers can tell loading from "no preview". */
|
|
24
|
-
export function useStudioPreviewUrls(keys: readonly string[]) {
|
|
25
|
-
const client = useClient({ apiVersion });
|
|
26
|
-
const [urls, setUrls] = useState<StudioPreviewUrls>();
|
|
27
|
-
// Keys off contents, not array identity — callers pass a freshly mapped array.
|
|
28
|
-
const keyList = keys.join(SEPARATOR);
|
|
29
|
-
|
|
30
|
-
useEffect(() => {
|
|
31
|
-
if (!keyList) {
|
|
32
|
-
setUrls({});
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
let cancelled = false;
|
|
37
|
-
|
|
38
|
-
client
|
|
39
|
-
.fetch<PreviewDocument[]>(PREVIEW_ASSETS_QUERY, {
|
|
40
|
-
keys: keyList.split(SEPARATOR),
|
|
41
|
-
})
|
|
42
|
-
.then((documents) => {
|
|
43
|
-
if (cancelled) return;
|
|
44
|
-
const next: StudioPreviewUrls = {};
|
|
45
|
-
for (const document of documents) {
|
|
46
|
-
if (document.key && document.url && !next[document.key]) {
|
|
47
|
-
next[document.key] = document.url;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
setUrls(next);
|
|
51
|
-
})
|
|
52
|
-
.catch(() => {
|
|
53
|
-
if (!cancelled) setUrls({});
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
return () => {
|
|
57
|
-
cancelled = true;
|
|
58
|
-
};
|
|
59
|
-
}, [client, keyList]);
|
|
60
|
-
|
|
61
|
-
return urls;
|
|
62
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { defineField, defineType } from "sanity";
|
|
2
|
-
import { pageBuilderCardIcon } from "@/lib/integrations/sanity/icons";
|
|
3
|
-
import { pageBuilderBlockPreviewOptions } from "@/lib/integrations/sanity/page-builder-config";
|
|
4
|
-
|
|
5
|
-
export const studioPreview = defineType({
|
|
6
|
-
name: "studioPreview",
|
|
7
|
-
title: "Component Preview",
|
|
8
|
-
type: "document",
|
|
9
|
-
icon: pageBuilderCardIcon,
|
|
10
|
-
fields: [
|
|
11
|
-
defineField({
|
|
12
|
-
name: "key",
|
|
13
|
-
title: "Component",
|
|
14
|
-
type: "string",
|
|
15
|
-
options: { list: pageBuilderBlockPreviewOptions },
|
|
16
|
-
validation: (Rule) => Rule.required(),
|
|
17
|
-
}),
|
|
18
|
-
defineField({
|
|
19
|
-
name: "preview",
|
|
20
|
-
title: "Preview image",
|
|
21
|
-
type: "image",
|
|
22
|
-
options: { hotspot: true },
|
|
23
|
-
validation: (Rule) => Rule.required(),
|
|
24
|
-
}),
|
|
25
|
-
],
|
|
26
|
-
preview: {
|
|
27
|
-
select: { key: "key", media: "preview" },
|
|
28
|
-
prepare({ key, media }) {
|
|
29
|
-
return {
|
|
30
|
-
title:
|
|
31
|
-
pageBuilderBlockPreviewOptions.find((option) => option.value === key)
|
|
32
|
-
?.title ?? "Component Preview",
|
|
33
|
-
media,
|
|
34
|
-
};
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
});
|