bsmnt 0.10.0 → 0.11.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/application/add-integration/index.js.map +1 -1
- package/dist/application/shared/node-version.js.map +1 -1
- package/dist/core/create/internal-ids.d.ts +3 -0
- package/dist/core/create/internal-ids.d.ts.map +1 -1
- package/dist/core/create/internal-ids.js +4 -0
- package/dist/core/create/internal-ids.js.map +1 -1
- package/dist/domain/agents.d.ts.map +1 -1
- package/dist/domain/animations.d.ts.map +1 -1
- package/dist/domain/cms.d.ts.map +1 -1
- package/dist/domain/forms.d.ts.map +1 -1
- package/dist/domain/hooks.d.ts.map +1 -1
- package/dist/domain/skills.d.ts.map +1 -1
- package/dist/domain/templates.d.ts +10 -0
- package/dist/domain/templates.d.ts.map +1 -1
- package/dist/domain/templates.js +5 -0
- package/dist/domain/templates.js.map +1 -1
- package/dist/index.js +18 -5
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/agents/register-sanity-mcp.js +3 -8
- package/dist/infrastructure/agents/register-sanity-mcp.js.map +1 -1
- package/dist/infrastructure/agents/register-storybook-mcp.d.ts +18 -0
- package/dist/infrastructure/agents/register-storybook-mcp.d.ts.map +1 -0
- package/dist/infrastructure/agents/register-storybook-mcp.js +157 -0
- package/dist/infrastructure/agents/register-storybook-mcp.js.map +1 -0
- package/dist/infrastructure/create/lifecycle-registry.d.ts.map +1 -1
- package/dist/infrastructure/create/lifecycle-registry.js +39 -1
- package/dist/infrastructure/create/lifecycle-registry.js.map +1 -1
- package/dist/infrastructure/create/merge-registry.d.ts.map +1 -1
- package/dist/infrastructure/create/merge-registry.js +4 -0
- package/dist/infrastructure/create/merge-registry.js.map +1 -1
- package/dist/infrastructure/create/sanity-auth.js.map +1 -1
- package/dist/infrastructure/create/setup-agent.d.ts +6 -1
- package/dist/infrastructure/create/setup-agent.d.ts.map +1 -1
- package/dist/infrastructure/create/setup-agent.js +23 -21
- package/dist/infrastructure/create/setup-agent.js.map +1 -1
- package/dist/infrastructure/create/setup-dotenvx.js.map +1 -1
- package/dist/infrastructure/create/setup-remote.js.map +1 -1
- package/dist/infrastructure/create/setup-sanity.d.ts.map +1 -1
- package/dist/infrastructure/create/setup-sanity.js +12 -1
- package/dist/infrastructure/create/setup-sanity.js.map +1 -1
- package/dist/infrastructure/integrations/inject-integration.d.ts.map +1 -1
- package/dist/infrastructure/integrations/inject-integration.js +2 -0
- package/dist/infrastructure/integrations/inject-integration.js.map +1 -1
- package/dist/infrastructure/update/notifier.js.map +1 -1
- package/dist/modules/features/cms/index.d.ts.map +1 -1
- package/dist/modules/features/cms/index.js +2 -0
- package/dist/modules/features/cms/index.js.map +1 -1
- package/dist/modules/features/cms/sanity/config.d.ts.map +1 -1
- package/dist/modules/features/cms/sanity/config.js +1 -0
- package/dist/modules/features/cms/sanity/config.js.map +1 -1
- package/dist/modules/features/cms/sanity/index.d.ts.map +1 -1
- package/dist/modules/features/cms/sanity/index.js +10 -1
- package/dist/modules/features/cms/sanity/index.js.map +1 -1
- package/dist/modules/features/cms/sanity/mergers/check-integration.d.ts.map +1 -1
- package/dist/modules/features/cms/sanity/mergers/check-integration.js +15 -6
- package/dist/modules/features/cms/sanity/mergers/check-integration.js.map +1 -1
- package/dist/modules/features/cms/sanity/mergers/knip.d.ts.map +1 -1
- package/dist/modules/features/cms/sanity/mergers/knip.js +1 -0
- package/dist/modules/features/cms/sanity/mergers/knip.js.map +1 -1
- package/dist/modules/features/cms/sanity/mergers/layout.js.map +1 -1
- package/dist/modules/features/cms/sanity/mergers/package-json.d.ts +12 -0
- package/dist/modules/features/cms/sanity/mergers/package-json.d.ts.map +1 -0
- package/dist/modules/features/cms/sanity/mergers/package-json.js +19 -0
- package/dist/modules/features/cms/sanity/mergers/package-json.js.map +1 -0
- package/dist/modules/features/cms/sanity-pagebuilder/index.d.ts.map +1 -1
- package/dist/modules/features/cms/sanity-pagebuilder/index.js +7 -1
- package/dist/modules/features/cms/sanity-pagebuilder/index.js.map +1 -1
- package/dist/modules/features/cms/sanity-pagebuilder-cache/config.d.ts +14 -0
- package/dist/modules/features/cms/sanity-pagebuilder-cache/config.d.ts.map +1 -0
- package/dist/modules/features/cms/sanity-pagebuilder-cache/config.js +50 -0
- package/dist/modules/features/cms/sanity-pagebuilder-cache/config.js.map +1 -0
- package/dist/modules/features/cms/sanity-pagebuilder-cache/index.d.ts +3 -0
- package/dist/modules/features/cms/sanity-pagebuilder-cache/index.d.ts.map +1 -0
- package/dist/modules/features/cms/sanity-pagebuilder-cache/index.js +55 -0
- package/dist/modules/features/cms/sanity-pagebuilder-cache/index.js.map +1 -0
- package/dist/modules/presets/index.d.ts.map +1 -1
- package/dist/modules/presets/index.js +2 -0
- package/dist/modules/presets/index.js.map +1 -1
- package/dist/modules/presets/next-pagebuilder-cache.d.ts +3 -0
- package/dist/modules/presets/next-pagebuilder-cache.d.ts.map +1 -0
- package/dist/modules/presets/next-pagebuilder-cache.js +10 -0
- package/dist/modules/presets/next-pagebuilder-cache.js.map +1 -0
- package/dist/paths.d.ts +6 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +6 -0
- package/dist/paths.js.map +1 -1
- package/package.json +5 -2
- package/src/modules/features/cms/sanity/files/lib/integrations/check-integration.ts +7 -6
- package/src/modules/features/cms/sanity/files/lib/integrations/sanity/README.md +1 -1
- package/src/modules/features/cms/sanity/files/lib/integrations/sanity/components/rich-text.tsx +1 -0
- package/src/modules/features/cms/sanity/files/lib/integrations/sanity/env.ts +2 -2
- package/src/modules/features/cms/sanity-pagebuilder/files/app/api/[[...slug]]/route.ts +4 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/app/api/revalidate/route.ts +172 -24
- package/src/modules/features/cms/sanity-pagebuilder/files/app/feed.xml/route.ts +14 -7
- package/src/modules/features/cms/sanity-pagebuilder/files/app/sitemap.md/route.ts +2 -2
- package/src/modules/features/cms/sanity-pagebuilder/files/app/studio/[[...tool]]/page.tsx +16 -1
- package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/rich-text.tsx +1 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/visual-editing.tsx +2 -2
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/check-integration.ts +6 -5
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/client.ts +8 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/slug-notices.tsx +6 -16
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/env.ts +2 -2
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/fetchers/layout.ts +22 -52
- 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/live/index.tsx +3 -1
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-builder-config.ts +7 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-tree-pane.tsx +70 -57
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/queries.ts +119 -29
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/revalidate-paths.ts +70 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/index.ts +1 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/rich-text-block.ts +39 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/index.ts +2 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page.ts +12 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/tags.ts +56 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/utils/page-builder-markdown.ts +12 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/sitemap.ts +11 -8
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/.knip.json +8 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/[[...slug]]/route.ts +112 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/draft-mode/disable/route.ts +7 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/draft-mode/enable/route.ts +20 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/revalidate/route.ts +292 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/feed.xml/route.ts +66 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/sitemap.md/route.ts +132 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/studio/[[...tool]]/layout.tsx +26 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/studio/[[...tool]]/page.tsx +23 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/sanity/draft-mode-toggle.tsx +27 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/sanity/rich-text.tsx +88 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/sanity/visual-editing.tsx +11 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/ui/sanity-image/index.tsx +51 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/check-integration.ts +6 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/client.ts +37 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/block-actions-item.tsx +81 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/disable-draft-mode.tsx +23 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/page-builder-input.tsx +39 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/page-category-input.tsx +50 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/same-as-input.tsx +66 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/slug-field.tsx +24 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/slug-input.tsx +14 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/slug-notices.tsx +125 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/confirm-publish-action.ts +40 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/env.ts +34 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/fetchers/layout.ts +39 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/fetchers/redirects.ts +22 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/icons.ts +81 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/live/index.tsx +140 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/markdown-proxy.config.ts +50 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/page-builder-config.ts +144 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/page-category.ts +28 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/page-tree-pane.tsx +431 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/pinned-pages.ts +54 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/presentation.ts +119 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/queries.ts +548 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/revalidate-paths.ts +70 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/sanity.cli.ts +29 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/sanity.config.ts +116 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/index.ts +6 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/blog-content.ts +89 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/description.ts +29 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/gated-content.ts +178 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/hero.ts +28 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/rich-text-block.ts +39 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/singleton/content-collection.ts +45 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/content/author.ts +70 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/content/blog-category.ts +55 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/index.ts +134 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/company-data.ts +70 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/footer.ts +79 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/navbar.ts +75 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/redirect.ts +152 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/structured-data.ts +124 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/link.ts +125 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/logo-field.ts +9 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/metadata.ts +132 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/nav-objects.ts +192 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/page-builder.ts +43 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/page-folder.ts +124 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/page.ts +265 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/richText.ts +63 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/structured-data-blocks.ts +436 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/singletons.ts +51 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/structure.ts +334 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/tags.ts +89 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/utils/image.ts +8 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/utils/link.ts +140 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/utils/page-builder-markdown.ts +113 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/utils/page-json-ld.ts +208 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/scripts/sanity-typegen.ts +36 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/check-redirect.ts +30 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/json-ld.tsx +522 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/metadata.ts +189 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/rss.ts +96 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/sitemap.ts +58 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/proxy.ts +90 -0
- package/src/modules/features/env/dotenvx/files/vercelignore +5 -0
- package/src/templates/next-default/.env.example +2 -2
- package/src/templates/next-default/.github/workflows/ci.yml +1 -1
- package/src/templates/next-default/.vscode/settings.json +0 -2
- package/src/templates/next-default/components/ui/image/index.tsx +2 -4
- package/src/templates/next-default/knip.json +1 -1
- package/src/templates/next-default/lefthook.yml +1 -1
- package/src/templates/next-default/lib/utils/README.md +18 -5
- package/src/templates/next-default/lib/utils/image-sizes.ts +72 -0
- package/src/templates/next-default/lib/utils/merge-refs.ts +56 -0
- package/src/templates/next-default/lib/utils/strings.ts +1 -51
- package/src/templates/next-default/next.config.ts +2 -3
- package/src/templates/next-default/package.json +2 -5
- package/src/templates/next-experiments/.env.example +2 -2
- package/src/templates/next-experiments/.github/workflows/ci.yml +1 -1
- package/src/templates/next-experiments/.vscode/settings.json +0 -2
- package/src/templates/next-experiments/app/page.tsx +1 -1
- package/src/templates/next-experiments/components/ui/image/index.tsx +2 -4
- package/src/templates/next-experiments/knip.json +1 -1
- package/src/templates/next-experiments/lefthook.yml +1 -1
- package/src/templates/next-experiments/lib/utils/README.md +18 -5
- package/src/templates/next-experiments/lib/utils/image-sizes.ts +72 -0
- package/src/templates/next-experiments/lib/utils/merge-refs.ts +56 -0
- package/src/templates/next-experiments/lib/utils/strings.ts +1 -51
- package/src/templates/next-experiments/next.config.ts +2 -3
- package/src/templates/next-experiments/package.json +2 -5
- package/src/templates/next-pagebuilder/.env.example +1 -0
- package/src/templates/next-pagebuilder/.github/workflows/ci.yml +1 -1
- package/src/templates/next-pagebuilder/.vscode/settings.json +0 -2
- package/src/templates/next-pagebuilder/README.md +64 -0
- package/src/templates/next-pagebuilder/app/sitemap.xml/route.ts +3 -1
- package/src/templates/next-pagebuilder/biome.json +2 -1
- package/src/templates/next-pagebuilder/components/layout/footer/index.tsx +3 -6
- package/src/templates/next-pagebuilder/components/layout/header/index.tsx +3 -2
- package/src/templates/next-pagebuilder/components/layout/json-ld/index.tsx +3 -8
- package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/content-filters.tsx +23 -40
- package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/index.tsx +88 -58
- package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/skeleton.tsx +4 -16
- package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/types.ts +14 -1
- package/src/templates/next-pagebuilder/components/page-builder/components/rich-text-block.tsx +16 -0
- package/src/templates/next-pagebuilder/components/page-builder/renderer.tsx +21 -7
- package/src/templates/next-pagebuilder/components/page-document/index.tsx +1 -0
- package/src/templates/next-pagebuilder/components/ui/image/index.tsx +2 -1
- package/src/templates/next-pagebuilder/knip.json +3 -2
- package/src/templates/next-pagebuilder/lefthook.yml +1 -1
- package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.types.ts +1221 -977
- package/src/templates/next-pagebuilder/lib/utils/image-sizes.ts +72 -0
- package/src/templates/next-pagebuilder/lib/utils/merge-refs.ts +56 -0
- package/src/templates/next-pagebuilder/next.config.ts +2 -3
- package/src/templates/next-pagebuilder/package.json +4 -6
- package/src/templates/next-pagebuilder-cache/.biome/plugins/README.md +21 -0
- package/src/templates/next-pagebuilder-cache/.biome/plugins/no-anchor-element.grit +12 -0
- package/src/templates/next-pagebuilder-cache/.biome/plugins/no-relative-parent-imports.grit +10 -0
- package/src/templates/next-pagebuilder-cache/.biome/plugins/no-unnecessary-forwardref.grit +9 -0
- package/src/templates/next-pagebuilder-cache/.env.example +40 -0
- package/src/templates/next-pagebuilder-cache/.github/PULL_REQUEST_TEMPLATE.md +13 -0
- package/src/templates/next-pagebuilder-cache/.github/workflows/ci.yml +105 -0
- package/src/templates/next-pagebuilder-cache/.vscode/extensions.json +19 -0
- package/src/templates/next-pagebuilder-cache/.vscode/settings.json +103 -0
- package/src/templates/next-pagebuilder-cache/.zed/settings.json +47 -0
- package/src/templates/next-pagebuilder-cache/README.md +212 -0
- package/src/templates/next-pagebuilder-cache/_gitignore +65 -0
- package/src/templates/next-pagebuilder-cache/app/(content)/[[...slug]]/page.tsx +124 -0
- package/src/templates/next-pagebuilder-cache/app/(content)/layout.tsx +28 -0
- package/src/templates/next-pagebuilder-cache/app/actions/unlock-gated-content.ts +53 -0
- package/src/templates/next-pagebuilder-cache/app/favicon.ico +0 -0
- package/src/templates/next-pagebuilder-cache/app/layout.tsx +83 -0
- package/src/templates/next-pagebuilder-cache/app/robots.ts +13 -0
- package/src/templates/next-pagebuilder-cache/app/sitemap.xml/route.ts +84 -0
- package/src/templates/next-pagebuilder-cache/biome.json +257 -0
- package/src/templates/next-pagebuilder-cache/components/layout/footer/index.tsx +127 -0
- package/src/templates/next-pagebuilder-cache/components/layout/header/components/cta-button.tsx +28 -0
- package/src/templates/next-pagebuilder-cache/components/layout/header/components/mega-menu-panel.tsx +90 -0
- package/src/templates/next-pagebuilder-cache/components/layout/header/components/nav-item-renderer.tsx +98 -0
- package/src/templates/next-pagebuilder-cache/components/layout/header/components/nav-leaf-item.tsx +33 -0
- package/src/templates/next-pagebuilder-cache/components/layout/header/components/types.ts +7 -0
- package/src/templates/next-pagebuilder-cache/components/layout/header/header-client.tsx +110 -0
- package/src/templates/next-pagebuilder-cache/components/layout/header/index.tsx +32 -0
- package/src/templates/next-pagebuilder-cache/components/layout/json-ld/index.tsx +79 -0
- package/src/templates/next-pagebuilder-cache/components/layout/wrapper/index.tsx +33 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/article-content/index.tsx +78 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/article-content/related-post-item.tsx +27 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/content-card.tsx +66 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/content-filters.tsx +76 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/content-grid.tsx +40 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/content-pagination-nav.tsx +71 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/index.tsx +306 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/skeleton.tsx +41 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/types.ts +32 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/description.tsx +17 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/gated-content/index.tsx +169 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/hero.tsx +17 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/rich-text-block.tsx +16 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/renderer.tsx +71 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/types.ts +25 -0
- package/src/templates/next-pagebuilder-cache/components/page-document/index.tsx +147 -0
- package/src/templates/next-pagebuilder-cache/components/ui/image/index.tsx +216 -0
- package/src/templates/next-pagebuilder-cache/components/ui/link/index.tsx +152 -0
- package/src/templates/next-pagebuilder-cache/components.json +24 -0
- package/src/templates/next-pagebuilder-cache/knip.json +35 -0
- package/src/templates/next-pagebuilder-cache/lefthook.yml +25 -0
- package/src/templates/next-pagebuilder-cache/lib/integrations/sanity/sanity.types.ts +1588 -0
- package/src/templates/next-pagebuilder-cache/lib/scripts/setup-ci-secrets.ts +117 -0
- package/src/templates/next-pagebuilder-cache/lib/styles/cn.ts +5 -0
- package/src/templates/next-pagebuilder-cache/lib/styles/global.css +69 -0
- package/src/templates/next-pagebuilder-cache/lib/utils/base-url.ts +17 -0
- package/src/templates/next-pagebuilder-cache/lib/utils/format-date.ts +8 -0
- package/src/templates/next-pagebuilder-cache/lib/utils/slug-tag.ts +6 -0
- package/src/templates/next-pagebuilder-cache/next.config.ts +176 -0
- package/src/templates/next-pagebuilder-cache/package.json +62 -0
- package/src/templates/next-pagebuilder-cache/postcss.config.mjs +65 -0
- package/src/templates/next-pagebuilder-cache/svg.d.ts +6 -0
- package/src/templates/next-pagebuilder-cache/tsconfig.json +38 -0
- package/src/templates/next-webgl/.env.example +2 -2
- package/src/templates/next-webgl/.github/workflows/ci.yml +1 -1
- package/src/templates/next-webgl/.vscode/settings.json +0 -2
- package/src/templates/next-webgl/components/ui/image/index.tsx +2 -4
- package/src/templates/next-webgl/knip.json +1 -1
- package/src/templates/next-webgl/lefthook.yml +1 -1
- package/src/templates/next-webgl/lib/utils/README.md +18 -5
- package/src/templates/next-webgl/lib/utils/image-sizes.ts +72 -0
- package/src/templates/next-webgl/lib/utils/merge-refs.ts +56 -0
- package/src/templates/next-webgl/lib/utils/strings.ts +1 -51
- package/src/templates/next-webgl/next.config.ts +2 -3
- package/src/templates/next-webgl/package.json +4 -7
- package/src/templates/next-experiments/tsconfig.tsbuildinfo +0 -1
- package/src/templates/next-pagebuilder/app/actions/refresh.ts +0 -5
- package/src/templates/next-webgl/tsconfig.tsbuildinfo +0 -1
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# Page Builder Template (Cache Components)
|
|
2
|
+
|
|
3
|
+
Next.js + Sanity CMS page builder architecture. All URL structure is managed in Sanity, not in code.
|
|
4
|
+
|
|
5
|
+
This is the [Cache Components](https://nextjs.org/docs/app/api-reference/config/next-config-js/cacheComponents)
|
|
6
|
+
variant: `cacheComponents: true`, so every Sanity read sits behind a `'use cache'`
|
|
7
|
+
boundary and lands in the static shell, and `cacheLife` runs on a ~1-year window.
|
|
8
|
+
Nothing is time-based — the publish webhook is what invalidates, and
|
|
9
|
+
`<SanityLive />` only runs in draft mode. See "Content invalidation" below; without
|
|
10
|
+
that webhook, published changes never reach the deployed site.
|
|
11
|
+
|
|
12
|
+
## Getting Started
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
bun dev
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Architecture
|
|
19
|
+
|
|
20
|
+
- Single catch-all route `[[...slug]]` handles all pages
|
|
21
|
+
- Sanity page builder with composable content blocks
|
|
22
|
+
- Blog with categories, filtering, and pagination
|
|
23
|
+
- Visual editing and live preview support
|
|
24
|
+
|
|
25
|
+
## Commands
|
|
26
|
+
|
|
27
|
+
- `bun dev` - Start dev server
|
|
28
|
+
- `bun build` - Production build
|
|
29
|
+
- `bun lint` - Run linter
|
|
30
|
+
- `bun sanity:typegen` - Generate Sanity types
|
|
31
|
+
<!-- bsmnt:if dotenvx -->
|
|
32
|
+
- `bun env:set KEY value` - Encrypt one value into `.env` (`--plain`, `--preview`/`--prod`)
|
|
33
|
+
- `bun env:encrypt-all` - Bulk-encrypt every plaintext value in a file (`--preview`/`--prod`)
|
|
34
|
+
- `bun env:setup` - Restore `.env.keys` from 1Password
|
|
35
|
+
- `bun env:view` - Print the decrypted `.env` (`--preview` / `--prod`)
|
|
36
|
+
- `bun env:setup-remote` - Push private keys to GitHub / Vercel (`github`/`vercel`, or pick)
|
|
37
|
+
- `bun setup-remote` - Create the GitHub repo, link a Vercel project, and push the key to Vercel env
|
|
38
|
+
- `bun env:save` - Save the private key to 1Password (Development vault)
|
|
39
|
+
<!-- bsmnt:endif -->
|
|
40
|
+
|
|
41
|
+
<!-- bsmnt:if dotenvx -->
|
|
42
|
+
## Environment variables
|
|
43
|
+
|
|
44
|
+
Secrets are encrypted at rest with [dotenvx](https://dotenvx.com): the committed `.env` holds encrypted values, the private keys live in `.env.keys` (gitignored), and dev/build run through `dotenvx run` so values decrypt automatically.
|
|
45
|
+
|
|
46
|
+
**First run.** `bun install` restores `.env.keys` from 1Password (via `DOTENV_PRIVATE_KEY_OP_REF` in `.env`), or run `bun run env:setup`. Needs 1Password access + the `op` CLI:
|
|
47
|
+
|
|
48
|
+
- macOS: `brew install --cask 1password-cli`
|
|
49
|
+
- Windows: `winget install 1password-cli`
|
|
50
|
+
- Linux / other: [1Password CLI docs](https://developer.1password.com/docs/cli/get-started/)
|
|
51
|
+
|
|
52
|
+
**Add or change a variable** (in `.env`):
|
|
53
|
+
|
|
54
|
+
- Secret: `bun run env:set KEY value` (`--preview`/`--prod` to target an environment)
|
|
55
|
+
- Public / `NEXT_PUBLIC_*`: `bun run env:set KEY value --plain`
|
|
56
|
+
- Bulk-encrypt a hand-edited file: `bun run env:encrypt-all`
|
|
57
|
+
- Inspect: `bun run env:view` (`--preview`/`--prod`)
|
|
58
|
+
|
|
59
|
+
**Per environment.** Scripts load `.env.$VERCEL_ENV` over `.env`. After adding a per-env file, push its key with `bun run env:setup-remote vercel` — `instrumentation.ts` uses it to decrypt at runtime.
|
|
60
|
+
|
|
61
|
+
**Edge runtime.** dotenvx decrypts only in the Node runtime; secrets read in `middleware.ts` or a `runtime = "edge"` route must be set as plain Vercel env vars.
|
|
62
|
+
|
|
63
|
+
A gitleaks pre-commit hook + CI scan block committed plaintext secrets (install: `brew install gitleaks`).
|
|
64
|
+
|
|
65
|
+
<!-- bsmnt:endif -->
|
|
66
|
+
<!-- bsmnt:if !dotenvx -->
|
|
67
|
+
## Environment variables
|
|
68
|
+
|
|
69
|
+
Copy `.env.example` to `.env.local` and fill in the values you need. `.env.local` is gitignored, so it stays off version control. Only `NEXT_PUBLIC_*` variables are exposed to the browser — everything else is server-only.
|
|
70
|
+
|
|
71
|
+
For deploys, set the same variables in your host's dashboard (e.g. Vercel → Settings → Environment Variables).
|
|
72
|
+
|
|
73
|
+
<!-- bsmnt:endif -->
|
|
74
|
+
## Sanity Dashboard (Studio in an iframe)
|
|
75
|
+
|
|
76
|
+
The Studio at `/studio` can be embedded in the Sanity Dashboard
|
|
77
|
+
(`https://www.sanity.io/@<org>/studio/<appId>/<workspace>`), which loads it in an
|
|
78
|
+
iframe from `www.sanity.io`. Three things have to be true for that to work:
|
|
79
|
+
|
|
80
|
+
1. **Headers.** `next.config.ts` already exempts `/studio/:path*` from the
|
|
81
|
+
site-wide `X-Frame-Options` / `frame-ancestors 'self'` headers, sending
|
|
82
|
+
`frame-ancestors 'self' https://*.sanity.io` there instead and dropping
|
|
83
|
+
`X-Frame-Options` entirely for that path (it has no "allow this origin"
|
|
84
|
+
form — it must be absent, not overridden). Nothing to do here unless
|
|
85
|
+
you've hand-edited `headers()`.
|
|
86
|
+
2. **Bridge script.** Already wired in: `next-sanity/studio`'s `NextStudio`
|
|
87
|
+
re-exports `NextStudioWithBridge`, which renders the
|
|
88
|
+
`core.sanity-cdn.com/bridge.js` tag the Dashboard needs. Don't hand-roll
|
|
89
|
+
that script tag, and don't switch the Studio route to
|
|
90
|
+
`next-sanity/studio/client-component` (the bridge-less variant).
|
|
91
|
+
3. **Registered URL.** In [Sanity Manage](https://sanity.io/manage), the
|
|
92
|
+
Studio application's URL must be the **full path**
|
|
93
|
+
(`https://example.com/studio`), not just the domain. This can't be set
|
|
94
|
+
from code — either click through Manage, or run
|
|
95
|
+
`sanity deploy --external --url https://example.com/studio --yes` from
|
|
96
|
+
`lib/integrations/sanity`. Writing it via the API needs the
|
|
97
|
+
`sanity.project.deployStudio` grant, which read/write tokens don't have.
|
|
98
|
+
|
|
99
|
+
Verify against a production build (`bun build && bun start`), not dev. One trap:
|
|
100
|
+
the Dashboard reports `Failed to load iframe due to X-Frame-Options: SAMEORIGIN`
|
|
101
|
+
for *any* framing failure, including a wrong registered URL with already-correct
|
|
102
|
+
headers. Trust the browser console's `Framing '<url>' violates …` line instead —
|
|
103
|
+
it names the URL actually being framed.
|
|
104
|
+
|
|
105
|
+
## Content invalidation (publish webhook) — required
|
|
106
|
+
|
|
107
|
+
Cached content is invalidated **on demand**, not on a timer: `cacheLife` is set to
|
|
108
|
+
a ~1-year window because a publish is supposed to clear the affected entries. That
|
|
109
|
+
clearing happens in `app/api/revalidate/route.ts`, which only runs if a Sanity
|
|
110
|
+
webhook calls it. **Until you create that webhook, published changes will not
|
|
111
|
+
appear on the deployed site until the next deploy.**
|
|
112
|
+
|
|
113
|
+
`SANITY_REVALIDATE_SECRET` is generated for you during `bsmnt create` (see your
|
|
114
|
+
env file). The webhook itself is manual, because the deployed URL isn't known at
|
|
115
|
+
scaffold time. Once deployed, in **sanity.io/manage → API → Webhooks → Create**:
|
|
116
|
+
|
|
117
|
+
| Field | Value |
|
|
118
|
+
| --- | --- |
|
|
119
|
+
| URL | `https://<your-domain>/api/revalidate` |
|
|
120
|
+
| Dataset | `production` |
|
|
121
|
+
| Trigger on | Create, Update, Delete |
|
|
122
|
+
| Filter | leave empty (the route decides what to invalidate) |
|
|
123
|
+
| Projection | see below — the route needs `operation` and `before()` |
|
|
124
|
+
| HTTP method | `POST` |
|
|
125
|
+
| API version | `v2021-03-25` or later |
|
|
126
|
+
| Secret | the `SANITY_REVALIDATE_SECRET` value from your env |
|
|
127
|
+
|
|
128
|
+
The projection is not optional: the route reads `operation` and the pre-delete
|
|
129
|
+
document to invalidate a removed page, and neither is in the default payload.
|
|
130
|
+
|
|
131
|
+
```groq
|
|
132
|
+
{
|
|
133
|
+
"_id": _id,
|
|
134
|
+
"_type": _type,
|
|
135
|
+
"operation": delta::operation(),
|
|
136
|
+
"before": before(),
|
|
137
|
+
"after": after()
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Make sure the same secret is set in your hosting provider's environment, not just
|
|
142
|
+
locally. To check it end to end: publish a change in the Studio, then reload the
|
|
143
|
+
affected page — it should update without a redeploy. Close any other tab on the
|
|
144
|
+
site first: in draft mode `<SanityLive>` refreshes the router on its own, which
|
|
145
|
+
can make a broken webhook look like it works.
|
|
146
|
+
|
|
147
|
+
### Watch that it keeps working
|
|
148
|
+
|
|
149
|
+
This webhook is the only thing that invalidates published content — `<SanityLive>`
|
|
150
|
+
is mounted in draft mode only, so nothing else clears a cache entry. A webhook that
|
|
151
|
+
silently stops delivering therefore freezes the site until the next deploy, with no
|
|
152
|
+
other symptom.
|
|
153
|
+
|
|
154
|
+
**sanity.io/manage → API → Webhooks → your webhook → Delivery log** is where to
|
|
155
|
+
check. A 401 means the secret doesn't match; repeated 5xx means the route is
|
|
156
|
+
erroring; no attempts at all means the filter or dataset is wrong. Worth an alert
|
|
157
|
+
if you have somewhere to send one.
|
|
158
|
+
|
|
159
|
+
### Recovering from a missed invalidation
|
|
160
|
+
|
|
161
|
+
If a page is stale and you don't know which tag was missed, `POST` a purge to the
|
|
162
|
+
same route. It clears every fixed tag plus every published page's per-slug tag —
|
|
163
|
+
including the `/api/<slug>.md` routes, which no `revalidatePath` reaches:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
curl -X POST https://<your-domain>/api/revalidate \
|
|
167
|
+
-H "content-type: application/json" \
|
|
168
|
+
-H "sanity-webhook-signature: <signature>" \
|
|
169
|
+
-d '{"purgeAll": true}'
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The route validates the same signature as a real webhook, so the simplest way to
|
|
173
|
+
send it is a second Sanity webhook with `{"purgeAll": true}` as a fixed projection,
|
|
174
|
+
triggered by hand from sanity.io/manage.
|
|
175
|
+
|
|
176
|
+
## Continuous Integration
|
|
177
|
+
|
|
178
|
+
A GitHub Actions workflow (`.github/workflows/ci.yml`) runs on every pull request to `main` and on pushes to `main`. It runs type checking, linting, the production build, tests, and `knip`.
|
|
179
|
+
|
|
180
|
+
The workflow takes its runtime versions from your manifests, so there's a single source of truth and nothing to keep in sync by hand:
|
|
181
|
+
|
|
182
|
+
- **Node** comes from `.nvmrc`.
|
|
183
|
+
- **Bun** comes from the `packageManager` field in `package.json`.
|
|
184
|
+
|
|
185
|
+
It also caches Bun's install cache between runs, and the test step is a no-op until you add real test files.
|
|
186
|
+
|
|
187
|
+
<!-- bsmnt:if dotenvx -->
|
|
188
|
+
The build decrypts the committed `.env` with the dotenvx private key, pulled from 1Password: set an **org-level** `OP_SERVICE_ACCOUNT_TOKEN` (a read-only [service account](https://developer.1password.com/docs/service-accounts/) scoped to the `Development` vault) — one secret for every repo — and CI reads the key via the op-ref committed in `.env`. A per-repo `DOTENV_PRIVATE_KEY` secret (`bun run env:setup-remote github`) works as a fallback; builds pass without either (only plaintext / `NEXT_PUBLIC_*` is read).
|
|
189
|
+
|
|
190
|
+
<!-- bsmnt:endif -->
|
|
191
|
+
### Sanity environment
|
|
192
|
+
|
|
193
|
+
The build reads Sanity config from GitHub Actions variables/secrets (empty and harmless until you configure Sanity). Once your repo is on GitHub, the quickest way to set them is:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
bun run ci:secrets
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
This reads the values from `.env.local` and pushes them to this repo with the GitHub CLI — `NEXT_PUBLIC_SANITY_PROJECT_ID` / `NEXT_PUBLIC_SANITY_DATASET` as **Variables** (they are public) and `SANITY_API_READ_TOKEN` as a **Secret**. Requires the [GitHub CLI](https://cli.github.com), authenticated (`gh auth login`).
|
|
200
|
+
|
|
201
|
+
To set them by hand instead, go to **Settings → Secrets and variables → Actions** and add the two `NEXT_PUBLIC_*` values under **Variables** and the token under **Secrets**.
|
|
202
|
+
|
|
203
|
+
### Branch protection
|
|
204
|
+
|
|
205
|
+
To block merges when CI fails, enable branch protection on `main`:
|
|
206
|
+
|
|
207
|
+
1. Go to **Settings → Branches → Add branch ruleset** (or **Add rule**).
|
|
208
|
+
2. Target the `main` branch.
|
|
209
|
+
3. Enable **Require status checks to pass before merging** and select the **CI** check.
|
|
210
|
+
4. (Recommended) Enable **Require a pull request before merging**.
|
|
211
|
+
|
|
212
|
+
Pull requests then cannot be merged until the CI workflow passes.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
2
|
+
|
|
3
|
+
# dependencies
|
|
4
|
+
/node_modules
|
|
5
|
+
/.pnp
|
|
6
|
+
.pnp.*
|
|
7
|
+
.turbo
|
|
8
|
+
.npmrc
|
|
9
|
+
.yarn/*
|
|
10
|
+
!.yarn/patches
|
|
11
|
+
!.yarn/plugins
|
|
12
|
+
!.yarn/releases
|
|
13
|
+
!.yarn/versions
|
|
14
|
+
|
|
15
|
+
# testing
|
|
16
|
+
/coverage
|
|
17
|
+
|
|
18
|
+
# next.js
|
|
19
|
+
/.next/
|
|
20
|
+
/out/
|
|
21
|
+
|
|
22
|
+
# TypeScript
|
|
23
|
+
*.tsbuildinfo
|
|
24
|
+
|
|
25
|
+
# production
|
|
26
|
+
/build
|
|
27
|
+
/public/sw.js
|
|
28
|
+
|
|
29
|
+
# misc
|
|
30
|
+
.DS_Store
|
|
31
|
+
*.pem
|
|
32
|
+
|
|
33
|
+
# debug
|
|
34
|
+
npm-debug.log*
|
|
35
|
+
yarn-debug.log*
|
|
36
|
+
yarn-error.log*
|
|
37
|
+
|
|
38
|
+
# local env files
|
|
39
|
+
.env
|
|
40
|
+
.env.local
|
|
41
|
+
.env*.local
|
|
42
|
+
.env.development.local
|
|
43
|
+
.env.test.local
|
|
44
|
+
.env.production.local
|
|
45
|
+
|
|
46
|
+
# vercel
|
|
47
|
+
.vercel
|
|
48
|
+
|
|
49
|
+
# tldr
|
|
50
|
+
.tldr
|
|
51
|
+
|
|
52
|
+
# eslint
|
|
53
|
+
.eslintcache
|
|
54
|
+
|
|
55
|
+
# certificates
|
|
56
|
+
certificates
|
|
57
|
+
|
|
58
|
+
next-env.d.ts
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# unlighthouse
|
|
62
|
+
.unlighthouse/
|
|
63
|
+
|
|
64
|
+
# storybook
|
|
65
|
+
*storybook.log
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { cacheLife, cacheTag } from "next/cache"
|
|
2
|
+
import { notFound } from "next/navigation"
|
|
3
|
+
import { PageDocument } from "@/components/page-document"
|
|
4
|
+
import {
|
|
5
|
+
type DynamicFetchOptions,
|
|
6
|
+
getDynamicFetchOptions,
|
|
7
|
+
toSanityFetchOptions,
|
|
8
|
+
sanityFetch,
|
|
9
|
+
sanityFetchStaticParams,
|
|
10
|
+
} from "@/lib/integrations/sanity/live"
|
|
11
|
+
import {
|
|
12
|
+
ALL_PAGE_SLUGS_QUERY,
|
|
13
|
+
PAGE_QUERY,
|
|
14
|
+
} from "@/lib/integrations/sanity/queries"
|
|
15
|
+
import { BLOG_CATEGORIES_TAG } from "@/lib/integrations/sanity/tags"
|
|
16
|
+
import type {
|
|
17
|
+
ALL_PAGE_SLUGS_QUERY_RESULT,
|
|
18
|
+
PAGE_QUERY_RESULT,
|
|
19
|
+
} from "@/lib/integrations/sanity/sanity.types"
|
|
20
|
+
import { checkRedirect } from "@/lib/utils/check-redirect"
|
|
21
|
+
import { generateSanityMetadata } from "@/lib/utils/metadata"
|
|
22
|
+
import { getSlugTag } from "@/lib/utils/slug-tag"
|
|
23
|
+
|
|
24
|
+
type SearchParams = Promise<{ [key: string]: string | string[] | undefined }>
|
|
25
|
+
type Props = { params: Promise<{ slug?: string[] }> }
|
|
26
|
+
|
|
27
|
+
const isValidSegment = (segment: string) =>
|
|
28
|
+
segment.length > 0 && segment !== "." && segment !== ".."
|
|
29
|
+
|
|
30
|
+
const toPath = (segments?: string[]) => {
|
|
31
|
+
if (!segments?.length) return null
|
|
32
|
+
if (!segments.every(isValidSegment)) return null
|
|
33
|
+
return segments.join("/")
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// One cached read of PAGE_QUERY for both the body and generateMetadata — as two
|
|
37
|
+
// functions it was two entries and two fetches per slug. The key must keep every
|
|
38
|
+
// DynamicFetchOptions field: dropping `variant` breaks release previews.
|
|
39
|
+
async function getCachedPage(
|
|
40
|
+
slug: string | null,
|
|
41
|
+
fetchOptions: DynamicFetchOptions
|
|
42
|
+
): Promise<NonNullable<PAGE_QUERY_RESULT> | null> {
|
|
43
|
+
"use cache"
|
|
44
|
+
const { data } = await sanityFetch({
|
|
45
|
+
query: PAGE_QUERY,
|
|
46
|
+
params: { slug },
|
|
47
|
+
...toSanityFetchOptions(fetchOptions),
|
|
48
|
+
})
|
|
49
|
+
const page = (data as NonNullable<PAGE_QUERY_RESULT> | null) ?? null
|
|
50
|
+
|
|
51
|
+
if (page) {
|
|
52
|
+
// Tagged inside the boundary so only real pages mint one: the slug is
|
|
53
|
+
// URL-derived, and a per-slug tag can't be enumerated or purged.
|
|
54
|
+
cacheTag(getSlugTag(slug))
|
|
55
|
+
|
|
56
|
+
// The pinned category is dereferenced into this entry, so it expires with it.
|
|
57
|
+
if (page.collectionCategory) cacheTag(BLOG_CATEGORIES_TAG)
|
|
58
|
+
} else {
|
|
59
|
+
// No document, so no publish event will ever clear this entry.
|
|
60
|
+
cacheLife("hours")
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return page
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const HOMEPAGE_PARAM: { slug: string[] } = { slug: [] }
|
|
67
|
+
|
|
68
|
+
export const generateStaticParams = async () => {
|
|
69
|
+
const { data: slugs } =
|
|
70
|
+
await sanityFetchStaticParams<ALL_PAGE_SLUGS_QUERY_RESULT>({
|
|
71
|
+
query: ALL_PAGE_SLUGS_QUERY,
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
return [
|
|
75
|
+
HOMEPAGE_PARAM,
|
|
76
|
+
...(slugs ?? []).flatMap((page) => {
|
|
77
|
+
if (!page.slug || page.slug === "/") return []
|
|
78
|
+
return { slug: page.slug.split("/") }
|
|
79
|
+
}),
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const generateMetadata = async ({ params }: Props) => {
|
|
84
|
+
const [{ slug }, fetchOptions] = await Promise.all([
|
|
85
|
+
params,
|
|
86
|
+
getDynamicFetchOptions(),
|
|
87
|
+
])
|
|
88
|
+
const path = toPath(slug)
|
|
89
|
+
|
|
90
|
+
if (slug?.length && path === null) return {}
|
|
91
|
+
|
|
92
|
+
// stega off: it would corrupt a <title> or og:image URL, and metadata renders no
|
|
93
|
+
// DOM for <VisualEditing> anyway. In published mode this is the body's key.
|
|
94
|
+
const page = await getCachedPage(path, { ...fetchOptions, stega: false })
|
|
95
|
+
|
|
96
|
+
return page
|
|
97
|
+
? generateSanityMetadata({ document: page, url: path ? `/${path}` : "/" })
|
|
98
|
+
: {}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// No Suspense wrapper needed: `getDynamicFetchOptions` is prerender-safe and
|
|
102
|
+
// `searchParams` is passed down unawaited, so the page prerenders.
|
|
103
|
+
export default async function CatchAllPage({
|
|
104
|
+
params,
|
|
105
|
+
searchParams,
|
|
106
|
+
}: Props & { searchParams: SearchParams }) {
|
|
107
|
+
const [{ slug }, fetchOptions] = await Promise.all([
|
|
108
|
+
params,
|
|
109
|
+
getDynamicFetchOptions(),
|
|
110
|
+
])
|
|
111
|
+
const path = toPath(slug)
|
|
112
|
+
|
|
113
|
+
if (slug?.length && path === null) notFound()
|
|
114
|
+
|
|
115
|
+
const page = await getCachedPage(path, fetchOptions)
|
|
116
|
+
|
|
117
|
+
if (!page) {
|
|
118
|
+
// Fall back to a CMS redirect before 404ing.
|
|
119
|
+
await checkRedirect(path)
|
|
120
|
+
notFound()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return <PageDocument page={page} path={path} searchParams={searchParams} />
|
|
124
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { draftMode } from "next/headers"
|
|
2
|
+
import { JsonLd } from "@/components/layout/json-ld"
|
|
3
|
+
import { Wrapper } from "@/components/layout/wrapper"
|
|
4
|
+
import { SanityVisualEditing } from "@/components/sanity/visual-editing"
|
|
5
|
+
import { SanityLive } from "@/lib/integrations/sanity/live"
|
|
6
|
+
|
|
7
|
+
// The one dynamic API allowed at the top of a layout: it still prerenders, and
|
|
8
|
+
// resolving it once lets each chrome component cache its published render.
|
|
9
|
+
const Layout = async ({ children }: { children: React.ReactNode }) => {
|
|
10
|
+
const { isEnabled: isDraftMode } = await draftMode()
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<>
|
|
14
|
+
<JsonLd isDraftMode={isDraftMode} />
|
|
15
|
+
<Wrapper isDraftMode={isDraftMode}>{children}</Wrapper>
|
|
16
|
+
{/* Draft mode only: for the public this costs a live connection and a
|
|
17
|
+
server action per tab, and the webhook is what invalidates. */}
|
|
18
|
+
{isDraftMode ? (
|
|
19
|
+
<>
|
|
20
|
+
<SanityLive includeDrafts />
|
|
21
|
+
<SanityVisualEditing />
|
|
22
|
+
</>
|
|
23
|
+
) : null}
|
|
24
|
+
</>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default Layout
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use server"
|
|
2
|
+
|
|
3
|
+
import { client } from "@/lib/integrations/sanity/client"
|
|
4
|
+
import {
|
|
5
|
+
getDynamicFetchOptions,
|
|
6
|
+
toSanityFetchOptions,
|
|
7
|
+
} from "@/lib/integrations/sanity/live"
|
|
8
|
+
import { GATED_CONTENT_QUERY } from "@/lib/integrations/sanity/queries"
|
|
9
|
+
import type { GATED_CONTENT_QUERY_RESULT } from "@/lib/integrations/sanity/sanity.types"
|
|
10
|
+
|
|
11
|
+
export type GatedContentPayload = NonNullable<GATED_CONTENT_QUERY_RESULT>
|
|
12
|
+
|
|
13
|
+
export type UnlockResult =
|
|
14
|
+
| { ok: true; payload: GatedContentPayload }
|
|
15
|
+
| { ok: false; error: string }
|
|
16
|
+
|
|
17
|
+
type UnlockInput = { documentId: string; email: string }
|
|
18
|
+
|
|
19
|
+
export async function unlockGatedContent({
|
|
20
|
+
documentId,
|
|
21
|
+
email,
|
|
22
|
+
}: UnlockInput): Promise<UnlockResult> {
|
|
23
|
+
if (!documentId) return { ok: false, error: "Missing content id." }
|
|
24
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
25
|
+
return { ok: false, error: "Enter a valid email address." }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Lead-capture seam: forward `email` to your CRM / email provider here.
|
|
29
|
+
// This is not access control — the payload is withheld from the page until
|
|
30
|
+
// this action runs, but anyone who submits the form receives it.
|
|
31
|
+
|
|
32
|
+
if (!client) return { ok: false, error: "Content not found." }
|
|
33
|
+
|
|
34
|
+
const fetchOptions = await getDynamicFetchOptions()
|
|
35
|
+
|
|
36
|
+
// `client`, not `directClient`: a read by id needs no post-mutation freshness,
|
|
37
|
+
// so it belongs on the CDN meter. Options threaded whole — hand-picking fields
|
|
38
|
+
// is how `variant` goes missing.
|
|
39
|
+
const payload = await client.fetch<GATED_CONTENT_QUERY_RESULT>(
|
|
40
|
+
GATED_CONTENT_QUERY,
|
|
41
|
+
{ id: documentId },
|
|
42
|
+
toSanityFetchOptions({
|
|
43
|
+
...fetchOptions,
|
|
44
|
+
// stega must stay off: it injects invisible characters into strings, which
|
|
45
|
+
// would corrupt the asset URLs returned for download/playback.
|
|
46
|
+
stega: false,
|
|
47
|
+
}),
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
if (!payload) return { ok: false, error: "Content not found." }
|
|
51
|
+
|
|
52
|
+
return { ok: true, payload }
|
|
53
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import "@/lib/styles/global.css"
|
|
2
|
+
|
|
3
|
+
import type { Metadata } from "next"
|
|
4
|
+
import { Geist } from "next/font/google"
|
|
5
|
+
import type { PropsWithChildren } from "react"
|
|
6
|
+
import { cn } from "@/lib/styles/cn"
|
|
7
|
+
import AppData from "@/package.json"
|
|
8
|
+
|
|
9
|
+
const APP_NAME = AppData.name
|
|
10
|
+
const APP_DEFAULT_TITLE = "My Site"
|
|
11
|
+
const APP_TITLE_TEMPLATE = "%s"
|
|
12
|
+
const APP_DESCRIPTION = AppData.description
|
|
13
|
+
// Build-time-stable so static routes (Studio, /_not-found) stay prerenderable
|
|
14
|
+
// under Cache Components. NEXT_PUBLIC_* is inlined at build; reading runtime env
|
|
15
|
+
// (VERCEL_URL) here would taint every route's metadata as request-time data.
|
|
16
|
+
const APP_BASE_URL =
|
|
17
|
+
process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000"
|
|
18
|
+
|
|
19
|
+
const geist = Geist({
|
|
20
|
+
subsets: ["latin"],
|
|
21
|
+
variable: "--font-geist-variable",
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
export const metadata: Metadata = {
|
|
25
|
+
alternates: {
|
|
26
|
+
canonical: "/",
|
|
27
|
+
languages: {
|
|
28
|
+
"en-US": "/en-US",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
appleWebApp: {
|
|
32
|
+
capable: true,
|
|
33
|
+
statusBarStyle: "default",
|
|
34
|
+
title: APP_DEFAULT_TITLE,
|
|
35
|
+
},
|
|
36
|
+
applicationName: APP_NAME,
|
|
37
|
+
authors: [{ name: "basement.studio", url: "https://basement.studio" }],
|
|
38
|
+
description: APP_DESCRIPTION,
|
|
39
|
+
formatDetection: { telephone: false },
|
|
40
|
+
metadataBase: new URL(APP_BASE_URL),
|
|
41
|
+
openGraph: {
|
|
42
|
+
description: APP_DESCRIPTION,
|
|
43
|
+
images: [
|
|
44
|
+
{
|
|
45
|
+
alt: APP_DEFAULT_TITLE,
|
|
46
|
+
height: 630,
|
|
47
|
+
url: "/opengraph-image.jpg",
|
|
48
|
+
width: 1200,
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
locale: "en_US",
|
|
52
|
+
siteName: APP_NAME,
|
|
53
|
+
title: {
|
|
54
|
+
default: APP_DEFAULT_TITLE,
|
|
55
|
+
template: APP_TITLE_TEMPLATE,
|
|
56
|
+
},
|
|
57
|
+
type: "website",
|
|
58
|
+
url: APP_BASE_URL,
|
|
59
|
+
},
|
|
60
|
+
other: {
|
|
61
|
+
"fb:app_id": process.env.NEXT_PUBLIC_FACEBOOK_APP_ID || "",
|
|
62
|
+
},
|
|
63
|
+
title: {
|
|
64
|
+
default: APP_DEFAULT_TITLE,
|
|
65
|
+
template: APP_TITLE_TEMPLATE,
|
|
66
|
+
},
|
|
67
|
+
twitter: {
|
|
68
|
+
card: "summary_large_image",
|
|
69
|
+
description: APP_DESCRIPTION,
|
|
70
|
+
title: {
|
|
71
|
+
default: APP_DEFAULT_TITLE,
|
|
72
|
+
template: APP_TITLE_TEMPLATE,
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const Layout = ({ children }: PropsWithChildren) => (
|
|
78
|
+
<html lang="en" dir="ltr" className={cn(geist.variable)}>
|
|
79
|
+
<body className="text-black">{children}</body>
|
|
80
|
+
</html>
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
export default Layout
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { MetadataRoute } from "next"
|
|
2
|
+
import { getBaseUrl } from "@/lib/utils/base-url"
|
|
3
|
+
|
|
4
|
+
export default function robots(): MetadataRoute.Robots {
|
|
5
|
+
return {
|
|
6
|
+
rules: {
|
|
7
|
+
userAgent: "*",
|
|
8
|
+
allow: "/",
|
|
9
|
+
disallow: [],
|
|
10
|
+
},
|
|
11
|
+
sitemap: `${getBaseUrl()}/sitemap.xml`,
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { NextResponse } from "next/server"
|
|
2
|
+
import { isSanityConfigured } from "@/lib/integrations/check-integration"
|
|
3
|
+
import type { PAGE_SITEMAP_QUERY_RESULT } from "@/lib/integrations/sanity/sanity.types"
|
|
4
|
+
import { getPageUrl, getSitemap, getSitemapBaseUrl } from "@/lib/utils/sitemap"
|
|
5
|
+
|
|
6
|
+
type PageDoc = PAGE_SITEMAP_QUERY_RESULT[number]
|
|
7
|
+
|
|
8
|
+
interface SitemapEntry {
|
|
9
|
+
url: string
|
|
10
|
+
lastModified?: string | null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const createPageEntry = (
|
|
14
|
+
baseUrl: string,
|
|
15
|
+
slug: string | null,
|
|
16
|
+
updatedAt?: string | null
|
|
17
|
+
): SitemapEntry => ({
|
|
18
|
+
url: getPageUrl(baseUrl, slug),
|
|
19
|
+
lastModified: updatedAt ?? null,
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const escapeXml = (value: string) =>
|
|
23
|
+
value
|
|
24
|
+
.replaceAll("&", "&")
|
|
25
|
+
.replaceAll("<", "<")
|
|
26
|
+
.replaceAll(">", ">")
|
|
27
|
+
.replaceAll('"', """)
|
|
28
|
+
.replaceAll("'", "'")
|
|
29
|
+
|
|
30
|
+
const renderUrl = (entry: SitemapEntry) => {
|
|
31
|
+
const parts = [" <url>", ` <loc>${escapeXml(entry.url)}</loc>`]
|
|
32
|
+
|
|
33
|
+
if (entry.lastModified) {
|
|
34
|
+
parts.push(
|
|
35
|
+
` <lastmod>${new Date(entry.lastModified).toISOString()}</lastmod>`
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
parts.push(" </url>")
|
|
40
|
+
|
|
41
|
+
return parts.join("\n")
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const renderSitemap = (entries: SitemapEntry[]) =>
|
|
45
|
+
[
|
|
46
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
47
|
+
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
|
|
48
|
+
...entries.map(renderUrl),
|
|
49
|
+
"</urlset>",
|
|
50
|
+
].join("\n")
|
|
51
|
+
|
|
52
|
+
const getSitemapResponse = (entries: SitemapEntry[], status = 200) =>
|
|
53
|
+
new NextResponse(renderSitemap(entries), {
|
|
54
|
+
status,
|
|
55
|
+
headers: { "Content-Type": "application/xml; charset=utf-8" },
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
// No `connection()`: per-request rendering meant a Sanity round trip per crawler
|
|
59
|
+
// hit. No `Cache-Control` either — a CDN copy is a second cache the webhook
|
|
60
|
+
// can't purge.
|
|
61
|
+
export const GET = async () => {
|
|
62
|
+
const baseUrl = getSitemapBaseUrl()
|
|
63
|
+
const homepageEntry = createPageEntry(baseUrl, null)
|
|
64
|
+
|
|
65
|
+
if (!isSanityConfigured()) return getSitemapResponse([homepageEntry])
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const allPages = await getSitemap()
|
|
69
|
+
|
|
70
|
+
if (!allPages) return getSitemapResponse([homepageEntry])
|
|
71
|
+
|
|
72
|
+
const pages = (allPages ?? []).filter((page) => !page.noIndex)
|
|
73
|
+
const sitemapEntries = pages.map((page: PageDoc) =>
|
|
74
|
+
createPageEntry(baseUrl, page.slug ?? null, page._updatedAt)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
return getSitemapResponse(
|
|
78
|
+
sitemapEntries.length > 0 ? sitemapEntries : [homepageEntry]
|
|
79
|
+
)
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.error("Error generating sitemap.xml:", error)
|
|
82
|
+
return getSitemapResponse([homepageEntry], 503)
|
|
83
|
+
}
|
|
84
|
+
}
|