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
package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/scripts/sanity-typegen.ts
CHANGED
|
@@ -10,27 +10,24 @@ import { resolve } from "node:path";
|
|
|
10
10
|
const sanityDir = resolve(import.meta.dir, "../integrations/sanity");
|
|
11
11
|
|
|
12
12
|
if (!process.env.NEXT_PUBLIC_SANITY_PROJECT_ID) {
|
|
13
|
+
// Annotate CI runs: the generated-files check verifies nothing without a project.
|
|
13
14
|
console.info(
|
|
14
|
-
|
|
15
|
+
process.env.CI
|
|
16
|
+
? "::warning::Skipping sanity:typegen — NEXT_PUBLIC_SANITY_PROJECT_ID not set, so committed Sanity types aren't verified"
|
|
17
|
+
: " Skipping sanity:typegen — NEXT_PUBLIC_SANITY_PROJECT_ID not set",
|
|
15
18
|
);
|
|
16
19
|
process.exit(0);
|
|
17
20
|
}
|
|
18
21
|
|
|
19
|
-
//
|
|
20
|
-
// match lint — but only locally. In CI/Vercel the build discards the formatted
|
|
21
|
-
// result, so running biome there is pure per-deploy waste. (Scoped to the one
|
|
22
|
-
// file; linting the whole dir would rewrite unrelated source.)
|
|
23
|
-
const format =
|
|
24
|
-
process.env.CI || process.env.VERCEL
|
|
25
|
-
? ""
|
|
26
|
-
: " && bun biome check --write --unsafe sanity.types.ts";
|
|
22
|
+
// No formatting pass: `sanity.types.ts` is in biome's ignore list, so `biome check` on it exits 1.
|
|
27
23
|
|
|
28
24
|
try {
|
|
29
25
|
// No `--env-file` — dotenvx already injected the env into process.env.
|
|
30
26
|
execSync(
|
|
31
|
-
`cd ${sanityDir} && bun sanity schema extract && bun sanity typegen generate
|
|
27
|
+
`cd ${sanityDir} && bun sanity schema extract && bun sanity typegen generate`,
|
|
32
28
|
{ stdio: "inherit" },
|
|
33
29
|
);
|
|
34
30
|
} catch {
|
|
35
|
-
console.
|
|
31
|
+
console.error(" sanity:typegen failed");
|
|
32
|
+
process.exit(1);
|
|
36
33
|
}
|
|
@@ -1,36 +1,13 @@
|
|
|
1
1
|
import type { Metadata } from "next";
|
|
2
|
+
import { stegaClean } from "next-sanity";
|
|
2
3
|
import { urlForImage } from "@/lib/integrations/sanity/utils/image";
|
|
3
4
|
import { getBaseUrl } from "./base-url";
|
|
4
5
|
|
|
5
|
-
interface GenerateMetadataOptions {
|
|
6
|
-
title?: string | undefined;
|
|
7
|
-
description?: string | undefined;
|
|
8
|
-
keywords?: string[] | undefined;
|
|
9
|
-
ogTitle?: string | undefined;
|
|
10
|
-
ogDescription?: string | undefined;
|
|
11
|
-
canonicalUrl?: string | undefined;
|
|
12
|
-
image?:
|
|
13
|
-
| {
|
|
14
|
-
url?: string | undefined;
|
|
15
|
-
width?: number | undefined;
|
|
16
|
-
height?: number | undefined;
|
|
17
|
-
}
|
|
18
|
-
| undefined;
|
|
19
|
-
url?: string | undefined;
|
|
20
|
-
siteName?: string | undefined;
|
|
21
|
-
index?: boolean | undefined;
|
|
22
|
-
noIndex?: boolean | undefined;
|
|
23
|
-
type?: "website" | "article";
|
|
24
|
-
publishedTime?: string | undefined;
|
|
25
|
-
modifiedTime?: string | undefined;
|
|
26
|
-
authors?: string[] | undefined;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
6
|
interface SanityMetadataImage {
|
|
30
7
|
asset?: {
|
|
31
8
|
_ref?: string;
|
|
32
9
|
_type?: "reference";
|
|
33
|
-
};
|
|
10
|
+
} | null;
|
|
34
11
|
}
|
|
35
12
|
|
|
36
13
|
interface SanityPageMetadata {
|
|
@@ -44,6 +21,7 @@ interface SanityPageMetadata {
|
|
|
44
21
|
image?: SanityMetadataImage | null;
|
|
45
22
|
} | null;
|
|
46
23
|
index?: boolean | null;
|
|
24
|
+
follow?: boolean | null;
|
|
47
25
|
title?: string | null;
|
|
48
26
|
description?: string | null;
|
|
49
27
|
image?: SanityMetadataImage | null;
|
|
@@ -52,138 +30,133 @@ interface SanityPageMetadata {
|
|
|
52
30
|
|
|
53
31
|
interface SanityMetadataDocument {
|
|
54
32
|
title?: string | null;
|
|
55
|
-
subtitle?: string | null;
|
|
56
33
|
metadata?: SanityPageMetadata | null;
|
|
57
34
|
}
|
|
58
35
|
|
|
36
|
+
// The Structured Data singleton: its name is the og:site_name and its share
|
|
37
|
+
// image covers any page without an OG image of its own.
|
|
38
|
+
interface SiteMetadata {
|
|
39
|
+
name?: string | null;
|
|
40
|
+
shareImage?: SanityMetadataImage | null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface ArticleMetadata {
|
|
44
|
+
publishedTime?: string | null | undefined;
|
|
45
|
+
modifiedTime?: string | null | undefined;
|
|
46
|
+
authors?: readonly (string | null | undefined)[] | null | undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
59
49
|
interface GenerateSanityMetadataOptions {
|
|
60
50
|
document: SanityMetadataDocument;
|
|
61
51
|
url?: string;
|
|
62
|
-
|
|
63
|
-
|
|
52
|
+
site?: SiteMetadata | null | undefined;
|
|
53
|
+
fallbackDescription?: string | null | undefined;
|
|
54
|
+
fallbackImage?: SanityMetadataImage | null | undefined;
|
|
55
|
+
article?: ArticleMetadata | undefined;
|
|
64
56
|
}
|
|
65
57
|
|
|
58
|
+
const OG_WIDTH = 1200;
|
|
59
|
+
const OG_HEIGHT = 630;
|
|
60
|
+
const DESCRIPTION_LENGTH = 160;
|
|
61
|
+
|
|
66
62
|
const APP_BASE_URL = getBaseUrl();
|
|
67
63
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
url
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
const
|
|
64
|
+
// A relative override is rooted, so "blog/post" can't resolve against the
|
|
65
|
+
// current path.
|
|
66
|
+
const resolveCanonical = (
|
|
67
|
+
override: string | null | undefined,
|
|
68
|
+
ownPath: string,
|
|
69
|
+
) => {
|
|
70
|
+
const value = override?.trim();
|
|
71
|
+
if (!value) return ownPath;
|
|
72
|
+
if (/^https?:\/\//i.test(value)) return value;
|
|
73
|
+
return `/${value.replace(/^\/+/, "")}`;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Trims body copy to a meta description, cut at a word boundary.
|
|
77
|
+
const summarize = (
|
|
78
|
+
text: string | null | undefined,
|
|
79
|
+
maxLength = DESCRIPTION_LENGTH,
|
|
80
|
+
) => {
|
|
81
|
+
const clean = text?.replace(/\s+/g, " ").trim();
|
|
82
|
+
if (!clean) return undefined;
|
|
83
|
+
if (clean.length <= maxLength) return clean;
|
|
84
|
+
const cut = clean.slice(0, maxLength - 1);
|
|
85
|
+
const lastSpace = cut.lastIndexOf(" ");
|
|
86
|
+
return `${(lastSpace > 0 ? cut.slice(0, lastSpace) : cut).replace(/[\s,;:.–—-]+$/, "")}…`;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const socialImage = (source: SanityMetadataImage | null | undefined) =>
|
|
90
|
+
source?.asset
|
|
91
|
+
? urlForImage(source).width(OG_WIDTH).height(OG_HEIGHT).url()
|
|
92
|
+
: undefined;
|
|
93
|
+
|
|
94
|
+
export const generateSanityMetadata = ({
|
|
95
|
+
document,
|
|
96
|
+
url = "/",
|
|
97
|
+
site,
|
|
98
|
+
fallbackDescription,
|
|
99
|
+
fallbackImage,
|
|
100
|
+
article,
|
|
101
|
+
}: GenerateSanityMetadataOptions): Metadata => {
|
|
102
|
+
const { name, shareImage } = stegaClean(site) ?? {};
|
|
103
|
+
const siteName = name?.trim() || undefined;
|
|
104
|
+
|
|
105
|
+
const seo = document.metadata;
|
|
106
|
+
const title = seo?.metaTitle || seo?.title || document.title || siteName;
|
|
107
|
+
const description =
|
|
108
|
+
seo?.metaDescription || seo?.description || summarize(fallbackDescription);
|
|
109
|
+
const socialTitle = seo?.ogTitle || title;
|
|
110
|
+
const socialDescription = seo?.ogDescription || description;
|
|
111
|
+
const imageUrl =
|
|
112
|
+
socialImage(seo?.image) ||
|
|
113
|
+
socialImage(seo?.og?.image) ||
|
|
114
|
+
socialImage(fallbackImage) ||
|
|
115
|
+
socialImage(shareImage);
|
|
116
|
+
const images = imageUrl
|
|
117
|
+
? [{ url: imageUrl, width: OG_WIDTH, height: OG_HEIGHT }]
|
|
118
|
+
: undefined;
|
|
119
|
+
|
|
120
|
+
const canonical = resolveCanonical(seo?.canonicalUrl, url);
|
|
121
|
+
|
|
122
|
+
const index = seo?.index ?? (seo?.noIndex != null ? !seo.noIndex : true);
|
|
123
|
+
// Unset on pages that predate the toggle, which were `nofollow` whenever
|
|
124
|
+
// they were `noindex`.
|
|
125
|
+
const follow = seo?.follow ?? index;
|
|
126
|
+
const authors = article?.authors?.filter((author): author is string =>
|
|
127
|
+
Boolean(author),
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
return {
|
|
116
131
|
metadataBase: new URL(APP_BASE_URL),
|
|
117
132
|
title,
|
|
118
133
|
description,
|
|
119
|
-
keywords,
|
|
120
|
-
alternates: { canonical
|
|
134
|
+
keywords: seo?.keywords?.length ? seo.keywords : undefined,
|
|
135
|
+
alternates: { canonical },
|
|
121
136
|
openGraph: {
|
|
137
|
+
title: socialTitle,
|
|
122
138
|
description: socialDescription,
|
|
123
|
-
url
|
|
139
|
+
// Follows the canonical, so an override doesn't leave og:url pointing
|
|
140
|
+
// at the duplicate.
|
|
141
|
+
url: new URL(canonical, APP_BASE_URL).toString(),
|
|
124
142
|
siteName,
|
|
125
143
|
locale: "en_US",
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
144
|
+
images,
|
|
145
|
+
...(article
|
|
146
|
+
? {
|
|
147
|
+
type: "article",
|
|
148
|
+
publishedTime: article.publishedTime || undefined,
|
|
149
|
+
modifiedTime: article.modifiedTime || undefined,
|
|
150
|
+
authors: authors?.length ? authors : undefined,
|
|
151
|
+
}
|
|
152
|
+
: { type: "website" }),
|
|
132
153
|
},
|
|
133
154
|
twitter: {
|
|
134
155
|
card: "summary_large_image",
|
|
156
|
+
title: socialTitle,
|
|
135
157
|
description: socialDescription,
|
|
136
|
-
images
|
|
137
|
-
...(socialTitle && { title: socialTitle }),
|
|
158
|
+
images,
|
|
138
159
|
},
|
|
139
|
-
|
|
160
|
+
...(index && follow ? {} : { robots: { index, follow } }),
|
|
140
161
|
};
|
|
141
|
-
|
|
142
|
-
if (!shouldIndex) metadata.robots = { index: false, follow: false };
|
|
143
|
-
|
|
144
|
-
return metadata;
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
export const generateSanityMetadata = ({
|
|
148
|
-
document,
|
|
149
|
-
url,
|
|
150
|
-
siteName,
|
|
151
|
-
type = "website",
|
|
152
|
-
}: GenerateSanityMetadataOptions): Metadata => {
|
|
153
|
-
const pageMetadata = document.metadata;
|
|
154
|
-
const title =
|
|
155
|
-
pageMetadata?.metaTitle ||
|
|
156
|
-
pageMetadata?.title ||
|
|
157
|
-
document.title ||
|
|
158
|
-
undefined;
|
|
159
|
-
const description =
|
|
160
|
-
pageMetadata?.metaDescription ||
|
|
161
|
-
pageMetadata?.description ||
|
|
162
|
-
document.subtitle ||
|
|
163
|
-
undefined;
|
|
164
|
-
const socialImageSource = pageMetadata?.image || pageMetadata?.og?.image;
|
|
165
|
-
const imageUrl = socialImageSource
|
|
166
|
-
? urlForImage(socialImageSource).width(1200).height(630).url()
|
|
167
|
-
: undefined;
|
|
168
|
-
const index =
|
|
169
|
-
pageMetadata?.index ??
|
|
170
|
-
(pageMetadata?.noIndex != null ? !pageMetadata.noIndex : true);
|
|
171
|
-
|
|
172
|
-
const keywords = pageMetadata?.keywords?.length
|
|
173
|
-
? pageMetadata.keywords
|
|
174
|
-
: undefined;
|
|
175
|
-
|
|
176
|
-
return generatePageMetadata({
|
|
177
|
-
title,
|
|
178
|
-
description,
|
|
179
|
-
keywords,
|
|
180
|
-
ogTitle: pageMetadata?.ogTitle || undefined,
|
|
181
|
-
ogDescription: pageMetadata?.ogDescription || undefined,
|
|
182
|
-
canonicalUrl: pageMetadata?.canonicalUrl || undefined,
|
|
183
|
-
image: imageUrl ? { url: imageUrl, width: 1200, height: 630 } : undefined,
|
|
184
|
-
url,
|
|
185
|
-
siteName,
|
|
186
|
-
index,
|
|
187
|
-
type,
|
|
188
|
-
});
|
|
189
162
|
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { headers } from "next/headers";
|
|
2
|
+
|
|
3
|
+
const STUDIO_PATH = "/studio";
|
|
4
|
+
|
|
5
|
+
const isStudioReferer = (referer: string | null) => {
|
|
6
|
+
if (!referer) return false;
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
return new URL(referer).pathname.startsWith(STUDIO_PATH);
|
|
10
|
+
} catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const isStudioEmbed = async () => {
|
|
16
|
+
const headerList = await headers();
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
headerList.get("sec-fetch-dest") === "iframe" ||
|
|
20
|
+
isStudioReferer(headerList.get("referer"))
|
|
21
|
+
);
|
|
22
|
+
};
|
|
@@ -7,9 +7,10 @@ import {
|
|
|
7
7
|
import { hubspotFormField } from "./form-field"
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Sample
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* Sample HubSpot form block. Register it in your schema's type list, then render
|
|
11
|
+
* its `form` with <HubSpotForm /> from the integration core. In a page-builder
|
|
12
|
+
* project re-declare it through `defineBlock` so it becomes an inline block —
|
|
13
|
+
* the `hubspot-pagebuilder-block` skill walks through it:
|
|
13
14
|
*
|
|
14
15
|
* @example
|
|
15
16
|
* import { HubSpotForm } from "@/lib/integrations/hubspot/form"
|
|
@@ -51,10 +52,3 @@ export const hubspotFormBlock = defineType({
|
|
|
51
52
|
},
|
|
52
53
|
},
|
|
53
54
|
})
|
|
54
|
-
|
|
55
|
-
export const hubspotFormBlockReference = defineType({
|
|
56
|
-
name: "hubspotFormBlockReference",
|
|
57
|
-
title: "HubSpot Form Block",
|
|
58
|
-
type: "reference",
|
|
59
|
-
to: [{ type: "hubspotFormBlock" }],
|
|
60
|
-
})
|
|
@@ -105,10 +105,15 @@ selectable block. Adding this integration installs the
|
|
|
105
105
|
HubSpot form into the page builder" and it makes the edits and verifies the
|
|
106
106
|
typecheck. To do it by hand:
|
|
107
107
|
|
|
108
|
-
1. `schemas/
|
|
108
|
+
1. `schemas/components/reusable/hubspot-form.ts` — re-declare the adapter
|
|
109
|
+
through `defineBlock`, so the block is an inline object with Anchor ID and
|
|
110
|
+
Presentation Mode; export it from `schemas/components/index.ts` and add it to
|
|
111
|
+
`schema.types` in `schemas/index.ts`
|
|
109
112
|
2. `page-builder-config.ts` — add `"hubspotFormBlock"` to
|
|
110
|
-
`PageBuilderDocumentType` and a `
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
decodes `block.form` and renders `<HubSpotForm />`
|
|
113
|
+
`PageBuilderDocumentType` and a `pageBuilderBlockMembers` entry with
|
|
114
|
+
`pageTypes` and `category`
|
|
115
|
+
3. `components/page-builder/registry.tsx` — one entry keyed `hubspotFormBlock`
|
|
116
|
+
that decodes `block.form` and renders `<HubSpotForm />`
|
|
117
|
+
4. `components/page-builder/library.ts` — a fixture entry, which is what the
|
|
118
|
+
insert menu, Studio → Components and `/components` render
|
|
114
119
|
5. `bun run sanity:typegen && bun typecheck`
|
|
@@ -44,10 +44,3 @@ export const ashbyJobBoardBlock = defineType({
|
|
|
44
44
|
},
|
|
45
45
|
},
|
|
46
46
|
})
|
|
47
|
-
|
|
48
|
-
export const ashbyJobBoardBlockReference = defineType({
|
|
49
|
-
name: "ashbyJobBoardBlockReference",
|
|
50
|
-
title: "Ashby Job Board",
|
|
51
|
-
type: "reference",
|
|
52
|
-
to: [{ type: "ashbyJobBoardBlock" }],
|
|
53
|
-
})
|
|
@@ -125,10 +125,16 @@ selectable block. Adding this integration installs the
|
|
|
125
125
|
Ashby job board into the page builder" and it makes the edits and verifies the
|
|
126
126
|
typecheck. To do it by hand:
|
|
127
127
|
|
|
128
|
-
1. `schemas/
|
|
128
|
+
1. `schemas/components/reusable/ashby-job-board.ts` — re-declare the adapter
|
|
129
|
+
through `defineBlock`, so the block is an inline object with Anchor ID and
|
|
130
|
+
Presentation Mode; export it from `schemas/components/index.ts` and add it to
|
|
131
|
+
`schema.types` in `schemas/index.ts`
|
|
129
132
|
2. `page-builder-config.ts` — add `"ashbyJobBoardBlock"` to
|
|
130
|
-
`PageBuilderDocumentType` and
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
that awaits `getCareerJobs(block.boardName)` and renders
|
|
133
|
+
`PageBuilderDocumentType` and a `pageBuilderBlockMembers` entry with
|
|
134
|
+
`pageTypes` and `category`
|
|
135
|
+
3. `components/page-builder/registry.tsx` — one entry keyed
|
|
136
|
+
`ashbyJobBoardBlock` that awaits `getCareerJobs(block.boardName)` and renders
|
|
137
|
+
the list
|
|
138
|
+
4. `components/page-builder/library.ts` — a fixture entry, which is what the
|
|
139
|
+
insert menu, Studio → Components and `/components` render
|
|
134
140
|
5. `bun run sanity:typegen && bun typecheck`
|
|
@@ -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(?:/|$)).*)",
|
|
@@ -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
|