create-brainerce-store 1.66.0 → 1.68.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/index.js +1 -1
- package/package.json +1 -1
- package/templates/nextjs/base/.env.local.ejs +52 -24
- package/templates/nextjs/base/.eslintrc.json +51 -57
- package/templates/nextjs/base/AGENTS.md.ejs +114 -81
- package/templates/nextjs/base/CLAUDE.md.ejs +125 -92
- package/templates/nextjs/base/next.config.ts +103 -86
- package/templates/nextjs/base/scripts/fetch-store-info.mjs +104 -97
- package/templates/nextjs/base/src/app/agents.md/route.ts +4 -3
- package/templates/nextjs/base/src/app/api/auth/me/route.ts +65 -59
- package/templates/nextjs/base/src/app/api/store/[...path]/route.ts +255 -242
- package/templates/nextjs/base/src/app/blog/[slug]/page.tsx.ejs +317 -308
- package/templates/nextjs/base/src/app/blog/page.tsx.ejs +277 -276
- package/templates/nextjs/base/src/app/blog/rss.xml/route.ts +4 -3
- package/templates/nextjs/base/src/app/category/[slug]/page.tsx +22 -12
- package/templates/nextjs/base/src/app/error.tsx.ejs +53 -0
- package/templates/nextjs/base/src/app/faq/page.tsx.ejs +46 -46
- package/templates/nextjs/base/src/app/indexnow-key.txt/route.ts +25 -26
- package/templates/nextjs/base/src/app/layout.tsx.ejs +273 -257
- package/templates/nextjs/base/src/app/llms.txt/route.ts +4 -3
- package/templates/nextjs/base/src/app/opengraph-image.tsx +1 -1
- package/templates/nextjs/base/src/app/page.tsx +65 -64
- package/templates/nextjs/base/src/app/pages/[slug]/page.tsx.ejs +98 -92
- package/templates/nextjs/base/src/app/products/[slug]/page.tsx +22 -11
- package/templates/nextjs/base/src/app/robots.ts +3 -2
- package/templates/nextjs/base/src/app/sitemap.ts +4 -3
- package/templates/nextjs/base/src/components/checkout/custom-fields-step.tsx +294 -292
- package/templates/nextjs/base/src/components/seo/article-json-ld.tsx +60 -59
- package/templates/nextjs/base/src/components/seo/category-json-ld.tsx +60 -61
- package/templates/nextjs/base/src/components/seo/product-json-ld.tsx +2 -1
- package/templates/nextjs/base/src/core/lib/brainerce.server.ts +81 -0
- package/templates/nextjs/base/src/core/lib/brainerce.ts.ejs +60 -110
- package/templates/nextjs/base/src/core/lib/site-url.ts +230 -0
- package/templates/nextjs/base/src/ui/product/review-form.tsx +294 -294
- package/templates/nextjs/base/src/ui/product/reviews-section.tsx.ejs +108 -108
- package/templates/nextjs/designs/atelier/app-overlay/layout.tsx.ejs +301 -285
- package/templates/nextjs/designs/atelier/ui/layout/faq-section.tsx +97 -96
- package/templates/nextjs/designs/atelier/ui/layout/site-footer.tsx.ejs +142 -141
- package/templates/nextjs/designs/atelier/ui/layout/site-header.tsx.ejs +139 -138
- package/templates/nextjs/designs/atelier/ui/product/review-form.tsx +295 -267
- package/templates/nextjs/designs/atelier/ui/product/reviews-section.tsx.ejs +148 -148
- package/templates/nextjs/ui-canvas/layout/faq-section.tsx.ejs +72 -71
- package/templates/nextjs/ui-canvas/layout/site-footer.tsx.ejs +83 -82
- package/templates/nextjs/ui-canvas/layout/site-header.tsx.ejs +120 -119
- package/templates/nextjs/ui-canvas/product/faq-section.tsx.ejs +54 -0
- package/templates/nextjs/ui-canvas/product/review-form.tsx +267 -266
- package/templates/nextjs/ui-canvas/product/reviews-section.tsx.ejs +96 -96
|
@@ -1,64 +1,65 @@
|
|
|
1
|
-
import type { Metadata } from 'next';
|
|
2
|
-
import { getServerClient } from '@/core/lib/brainerce';
|
|
3
|
-
import { buildMetaDescription } from '@/core/lib/seo';
|
|
4
|
-
import { OrganizationJsonLd } from '@/components/seo/organization-json-ld';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
client.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
<
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
1
|
+
import type { Metadata } from 'next';
|
|
2
|
+
import { getServerClient } from '@/core/lib/brainerce.server';
|
|
3
|
+
import { buildMetaDescription } from '@/core/lib/seo';
|
|
4
|
+
import { OrganizationJsonLd } from '@/components/seo/organization-json-ld';
|
|
5
|
+
import { getCanonicalSiteUrl } from '@/core/lib/site-url';
|
|
6
|
+
import { RichTextBlock } from '@/ui/layout/rich-text-block';
|
|
7
|
+
import { HomeClient } from '@/ui/home/home-client';
|
|
8
|
+
|
|
9
|
+
// Build homepage metadata server-side so Google, Facebook, WhatsApp etc.
|
|
10
|
+
// see a real <meta name="description"> on first request. Falls back to the
|
|
11
|
+
// store name when the merchant hasn't authored a meta description yet.
|
|
12
|
+
export async function generateMetadata(): Promise<Metadata> {
|
|
13
|
+
try {
|
|
14
|
+
const storeInfo = await (await getServerClient()).getStoreInfo();
|
|
15
|
+
const description = buildMetaDescription(storeInfo.metaDescription) || undefined;
|
|
16
|
+
return {
|
|
17
|
+
title: storeInfo.name,
|
|
18
|
+
description,
|
|
19
|
+
openGraph: {
|
|
20
|
+
title: storeInfo.name,
|
|
21
|
+
description,
|
|
22
|
+
type: 'website',
|
|
23
|
+
},
|
|
24
|
+
twitter: {
|
|
25
|
+
card: 'summary_large_image',
|
|
26
|
+
title: storeInfo.name,
|
|
27
|
+
description,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
} catch {
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default async function HomePage() {
|
|
36
|
+
// Fetch store info server-side so the JSON-LD is in the initial HTML —
|
|
37
|
+
// crawlers and AI Overviews see it without needing to execute JavaScript.
|
|
38
|
+
// Failure here is non-fatal: the client component re-fetches via the
|
|
39
|
+
// StoreProvider so the page still renders.
|
|
40
|
+
//
|
|
41
|
+
// Also fetch the merchant's homepage intro (RICH_TEXT key='main'). When
|
|
42
|
+
// present, it renders as a sanitized HTML block above the product grid —
|
|
43
|
+
// typical use is brand introduction, value-prop copy, or a seasonal
|
|
44
|
+
// announcement that needs more formatting than the AnnouncementBar.
|
|
45
|
+
// When absent, RichTextBlock returns null cleanly so the layout collapses
|
|
46
|
+
// to just the product grid.
|
|
47
|
+
const client = await getServerClient();
|
|
48
|
+
const [storeInfo, intro] = await Promise.all([
|
|
49
|
+
client.getStoreInfo().catch(() => null),
|
|
50
|
+
client.content.richText.get('main').catch(() => null),
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
const baseUrl = await getCanonicalSiteUrl();
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<>
|
|
57
|
+
{storeInfo && baseUrl ? <OrganizationJsonLd storeInfo={storeInfo} baseUrl={baseUrl} /> : null}
|
|
58
|
+
<RichTextBlock
|
|
59
|
+
block={intro}
|
|
60
|
+
className="prose prose-sm dark:prose-invert mx-auto max-w-3xl px-4 py-8 sm:px-6 lg:px-8"
|
|
61
|
+
/>
|
|
62
|
+
<HomeClient />
|
|
63
|
+
</>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
@@ -1,92 +1,98 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Static page route — fully driven by the merchant's dashboard.
|
|
3
|
-
*
|
|
4
|
-
* The merchant creates pages in the Brainerce dashboard under
|
|
5
|
-
* Sell → Content → Page (one row per page, each with its own `slug`)
|
|
6
|
-
*
|
|
7
|
-
* This catch-all renders any PAGE by its `data.slug`. `/pages/about` →
|
|
8
|
-
* fetches PAGE with slug='about'. Mounting under `/pages/` (rather than
|
|
9
|
-
* the root) keeps the route from clashing with existing static routes
|
|
10
|
-
* like /cart, /login, /products.
|
|
11
|
-
*
|
|
12
|
-
* Security: page HTML is sanitized via `sanitizeHtml` before injecting.
|
|
13
|
-
*/
|
|
14
|
-
import * as React from 'react';
|
|
15
|
-
import type { Metadata } from 'next';
|
|
16
|
-
import { notFound } from 'next/navigation';
|
|
17
|
-
|
|
18
|
-
import { getServerClient } from '@/core/lib/brainerce';
|
|
19
|
-
import { sanitizeHtml } from '@/core/lib/sanitize';
|
|
20
|
-
import { decodeSlug } from '@/core/lib/utils';
|
|
21
|
-
|
|
22
|
-
<% if (i18nEnabled) { %>
|
|
23
|
-
type PageProps = {
|
|
24
|
-
params: Promise<{ locale: string; slug: string }>;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
|
|
28
|
-
const { locale, slug: rawSlug } = await params;
|
|
29
|
-
const slug = decodeSlug(rawSlug);
|
|
30
|
-
const page = await getServerClient(locale).content.page.getBySlug(slug, locale).catch(() => null);
|
|
31
|
-
if (!page) return { title: slug };
|
|
32
|
-
const seo = page.data.seo ?? {};
|
|
33
|
-
return {
|
|
34
|
-
title: seo.title ?? page.data.title,
|
|
35
|
-
description: seo.description,
|
|
36
|
-
openGraph: seo.ogImage ? { images: [{ url: seo.ogImage }] } : undefined,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export default async function StaticPage({ params }: PageProps) {
|
|
41
|
-
const { locale, slug: rawSlug } = await params;
|
|
42
|
-
const slug = decodeSlug(rawSlug);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const
|
|
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
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Static page route — fully driven by the merchant's dashboard.
|
|
3
|
+
*
|
|
4
|
+
* The merchant creates pages in the Brainerce dashboard under
|
|
5
|
+
* Sell → Content → Page (one row per page, each with its own `slug`)
|
|
6
|
+
*
|
|
7
|
+
* This catch-all renders any PAGE by its `data.slug`. `/pages/about` →
|
|
8
|
+
* fetches PAGE with slug='about'. Mounting under `/pages/` (rather than
|
|
9
|
+
* the root) keeps the route from clashing with existing static routes
|
|
10
|
+
* like /cart, /login, /products.
|
|
11
|
+
*
|
|
12
|
+
* Security: page HTML is sanitized via `sanitizeHtml` before injecting.
|
|
13
|
+
*/
|
|
14
|
+
import * as React from 'react';
|
|
15
|
+
import type { Metadata } from 'next';
|
|
16
|
+
import { notFound } from 'next/navigation';
|
|
17
|
+
|
|
18
|
+
import { getServerClient } from '@/core/lib/brainerce.server';
|
|
19
|
+
import { sanitizeHtml } from '@/core/lib/sanitize';
|
|
20
|
+
import { decodeSlug } from '@/core/lib/utils';
|
|
21
|
+
|
|
22
|
+
<% if (i18nEnabled) { %>
|
|
23
|
+
type PageProps = {
|
|
24
|
+
params: Promise<{ locale: string; slug: string }>;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
|
|
28
|
+
const { locale, slug: rawSlug } = await params;
|
|
29
|
+
const slug = decodeSlug(rawSlug);
|
|
30
|
+
const page = await (await getServerClient(locale)).content.page.getBySlug(slug, locale).catch(() => null);
|
|
31
|
+
if (!page) return { title: slug };
|
|
32
|
+
const seo = page.data.seo ?? {};
|
|
33
|
+
return {
|
|
34
|
+
title: seo.title ?? page.data.title,
|
|
35
|
+
description: seo.description,
|
|
36
|
+
openGraph: seo.ogImage ? { images: [{ url: seo.ogImage }] } : undefined,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default async function StaticPage({ params }: PageProps) {
|
|
41
|
+
const { locale, slug: rawSlug } = await params;
|
|
42
|
+
const slug = decodeSlug(rawSlug);
|
|
43
|
+
// getBySlug returns null only on a genuine 404; any other API failure (a
|
|
44
|
+
// 403 from the channel's origin check, a network error) throws and surfaces
|
|
45
|
+
// as the error it is instead of masquerading as a missing page.
|
|
46
|
+
const page = await (await getServerClient(locale)).content.page.getBySlug(slug, locale);
|
|
47
|
+
if (!page) notFound();
|
|
48
|
+
return (
|
|
49
|
+
<article className="mx-auto max-w-3xl px-4 py-10 sm:px-6 lg:px-8">
|
|
50
|
+
<h1 className="text-foreground mb-6 text-2xl font-semibold sm:text-3xl">
|
|
51
|
+
{page.data.title}
|
|
52
|
+
</h1>
|
|
53
|
+
<div
|
|
54
|
+
className="prose prose-sm dark:prose-invert max-w-none"
|
|
55
|
+
dangerouslySetInnerHTML={{ __html: sanitizeHtml(page.data.html) }}
|
|
56
|
+
/>
|
|
57
|
+
</article>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
<% } else { %>
|
|
61
|
+
type PageProps = {
|
|
62
|
+
params: Promise<{ slug: string }>;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
|
|
66
|
+
const { slug: rawSlug } = await params;
|
|
67
|
+
const slug = decodeSlug(rawSlug);
|
|
68
|
+
const page = await (await getServerClient()).content.page.getBySlug(slug).catch(() => null);
|
|
69
|
+
if (!page) return { title: slug };
|
|
70
|
+
const seo = page.data.seo ?? {};
|
|
71
|
+
return {
|
|
72
|
+
title: seo.title ?? page.data.title,
|
|
73
|
+
description: seo.description,
|
|
74
|
+
openGraph: seo.ogImage ? { images: [{ url: seo.ogImage }] } : undefined,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export default async function StaticPage({ params }: PageProps) {
|
|
79
|
+
const { slug: rawSlug } = await params;
|
|
80
|
+
const slug = decodeSlug(rawSlug);
|
|
81
|
+
// getBySlug returns null only on a genuine 404; any other API failure (a
|
|
82
|
+
// 403 from the channel's origin check, a network error) throws and surfaces
|
|
83
|
+
// as the error it is instead of masquerading as a missing page.
|
|
84
|
+
const page = await (await getServerClient()).content.page.getBySlug(slug);
|
|
85
|
+
if (!page) notFound();
|
|
86
|
+
return (
|
|
87
|
+
<article className="mx-auto max-w-3xl px-4 py-10 sm:px-6 lg:px-8">
|
|
88
|
+
<h1 className="text-foreground mb-6 text-2xl font-semibold sm:text-3xl">
|
|
89
|
+
{page.data.title}
|
|
90
|
+
</h1>
|
|
91
|
+
<div
|
|
92
|
+
className="prose prose-sm dark:prose-invert max-w-none"
|
|
93
|
+
dangerouslySetInnerHTML={{ __html: sanitizeHtml(page.data.html) }}
|
|
94
|
+
/>
|
|
95
|
+
</article>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
<% } %>
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { Metadata } from 'next';
|
|
2
2
|
import { notFound, permanentRedirect } from 'next/navigation';
|
|
3
|
-
import { getProductPriceInfo } from 'brainerce';
|
|
4
|
-
import { getServerClient, fetchStoreInfo } from '@/core/lib/brainerce';
|
|
3
|
+
import { BrainerceError, getProductPriceInfo } from 'brainerce';
|
|
4
|
+
import { getServerClient, fetchStoreInfo } from '@/core/lib/brainerce.server';
|
|
5
5
|
import { resolveCurrency } from '@/core/lib/resolve-currency';
|
|
6
6
|
import { buildMetaDescription } from '@/core/lib/seo';
|
|
7
7
|
import { decodeSlug } from '@/core/lib/utils';
|
|
8
8
|
import { ProductJsonLd } from '@/components/seo/product-json-ld';
|
|
9
|
+
import { getCanonicalSiteUrl } from '@/core/lib/site-url';
|
|
9
10
|
import { Breadcrumbs } from '@/components/seo/breadcrumbs';
|
|
10
11
|
import { ReviewsSection } from '@/ui/product/reviews-section';
|
|
11
12
|
import { ProductFaqSection } from '@/ui/product/faq-section';
|
|
@@ -38,7 +39,7 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
|
|
38
39
|
const slug = decodeSlug(rawSlug);
|
|
39
40
|
|
|
40
41
|
try {
|
|
41
|
-
const client = getServerClient(locale);
|
|
42
|
+
const client = await getServerClient(locale);
|
|
42
43
|
// Fetch product + store info in parallel; storeInfo gives us the live
|
|
43
44
|
// currency for OG price tags (with env-var + USD fallbacks so we never
|
|
44
45
|
// silently emit a wrong currency when the API is briefly unreachable).
|
|
@@ -70,7 +71,7 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
|
|
70
71
|
// Locales come from storeInfo.i18n — already fetched above, zero extra cost.
|
|
71
72
|
const supportedLocales = storeInfo?.i18n?.supportedLocales ?? [];
|
|
72
73
|
const defaultLoc = storeInfo?.i18n?.defaultLocale ?? storeInfo?.language ?? '';
|
|
73
|
-
const baseUrl =
|
|
74
|
+
const baseUrl = await getCanonicalSiteUrl();
|
|
74
75
|
const baseSlug = product.slug || slug;
|
|
75
76
|
const localeSlugs = product.localeSlugs ?? {};
|
|
76
77
|
|
|
@@ -122,10 +123,13 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
|
|
122
123
|
...(brandName ? { 'product:brand': brandName } : {}),
|
|
123
124
|
},
|
|
124
125
|
};
|
|
125
|
-
} catch {
|
|
126
|
-
|
|
127
|
-
title: 'Product not found'
|
|
128
|
-
}
|
|
126
|
+
} catch (err) {
|
|
127
|
+
if (err instanceof BrainerceError && err.statusCode === 404) {
|
|
128
|
+
return { title: 'Product not found' };
|
|
129
|
+
}
|
|
130
|
+
// Anything else (channel origin rejection, network failure) hits the page
|
|
131
|
+
// render below too and surfaces there — don't stamp a misleading title.
|
|
132
|
+
return {};
|
|
129
133
|
}
|
|
130
134
|
}
|
|
131
135
|
|
|
@@ -133,11 +137,18 @@ export default async function ProductDetailPage({ params }: Props) {
|
|
|
133
137
|
const { slug: rawSlug, locale } = await params;
|
|
134
138
|
const slug = decodeSlug(rawSlug);
|
|
135
139
|
|
|
136
|
-
const client = getServerClient(locale);
|
|
140
|
+
const client = await getServerClient(locale);
|
|
137
141
|
let product;
|
|
138
142
|
try {
|
|
139
143
|
product = await client.getProductBySlug(slug);
|
|
140
|
-
} catch {
|
|
144
|
+
} catch (err) {
|
|
145
|
+
// Only a genuine 404 may become a storefront 404. Anything else — a 403
|
|
146
|
+
// from the channel's origin check, a network failure, a 5xx — is a
|
|
147
|
+
// configuration or availability problem; disguising it as "product not
|
|
148
|
+
// found" sends the merchant hunting for a slug bug that isn't there.
|
|
149
|
+
if (!(err instanceof BrainerceError && err.statusCode === 404)) {
|
|
150
|
+
throw err;
|
|
151
|
+
}
|
|
141
152
|
// The slug may have been RENAMED — the platform records every rename.
|
|
142
153
|
// 301 the old URL to the current slug so its ranking and inbound links
|
|
143
154
|
// carry over; genuine unknowns fall through to the 404 page. Default
|
|
@@ -153,7 +164,7 @@ export default async function ProductDetailPage({ params }: Props) {
|
|
|
153
164
|
notFound();
|
|
154
165
|
}
|
|
155
166
|
|
|
156
|
-
const baseUrl =
|
|
167
|
+
const baseUrl = await getCanonicalSiteUrl();
|
|
157
168
|
const productUrl = `${baseUrl}/products/${slug}`;
|
|
158
169
|
// Reuse the cached storeInfo from generateMetadata's call — React cache()
|
|
159
170
|
// collapses both into one backend request per render. resolveCurrency owns
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { MetadataRoute } from 'next';
|
|
2
|
+
import { getCanonicalSiteUrl } from '@/core/lib/site-url';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* robots.txt with an explicit AI-crawler policy.
|
|
@@ -47,8 +48,8 @@ const AI_TRAINING_BOTS = [
|
|
|
47
48
|
'Applebot-Extended',
|
|
48
49
|
];
|
|
49
50
|
|
|
50
|
-
export default function robots(): MetadataRoute.Robots {
|
|
51
|
-
const baseUrl =
|
|
51
|
+
export default async function robots(): Promise<MetadataRoute.Robots> {
|
|
52
|
+
const baseUrl = await getCanonicalSiteUrl();
|
|
52
53
|
|
|
53
54
|
return {
|
|
54
55
|
rules: [
|
|
@@ -4,7 +4,8 @@ import {
|
|
|
4
4
|
getCategorySitemapEntries,
|
|
5
5
|
getProductSitemapEntries,
|
|
6
6
|
} from 'brainerce';
|
|
7
|
-
import { getServerClient } from '@/core/lib/brainerce';
|
|
7
|
+
import { getServerClient } from '@/core/lib/brainerce.server';
|
|
8
|
+
import { getCanonicalSiteUrl } from '@/core/lib/site-url';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Storefront sitemap: static pages + every published product, category,
|
|
@@ -16,9 +17,9 @@ import { getServerClient } from '@/core/lib/brainerce';
|
|
|
16
17
|
* The helper uses a dedicated lightweight endpoint with a 5000-product cap.
|
|
17
18
|
*/
|
|
18
19
|
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|
19
|
-
const baseUrl =
|
|
20
|
+
const baseUrl = await getCanonicalSiteUrl();
|
|
20
21
|
|
|
21
|
-
const client = getServerClient();
|
|
22
|
+
const client = await getServerClient();
|
|
22
23
|
const storeInfo = await client.getStoreInfo().catch(() => null);
|
|
23
24
|
const supportedLocales = storeInfo?.i18n?.supportedLocales ?? [];
|
|
24
25
|
const defaultLoc = storeInfo?.i18n?.defaultLocale ?? storeInfo?.language ?? '';
|