create-brainerce-store 1.52.5 → 1.54.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/LICENSE +21 -0
- package/dist/index.js +4 -2
- package/messages/en.json +2 -0
- package/messages/he.json +2 -0
- package/package.json +1 -1
- package/templates/nextjs/base/next.config.ts +86 -86
- package/templates/nextjs/base/src/app/blog/[slug]/page.tsx.ejs +17 -31
- package/templates/nextjs/base/src/app/category/[slug]/page.tsx +92 -0
- package/templates/nextjs/base/src/app/indexnow-key.txt/route.ts +26 -0
- package/templates/nextjs/base/src/app/layout.tsx.ejs +227 -227
- package/templates/nextjs/base/src/app/llms.txt/route.ts +44 -0
- package/templates/nextjs/base/src/app/sitemap.ts +18 -1
- package/templates/nextjs/base/src/components/brainerce-bot.tsx +7 -0
- package/templates/nextjs/base/src/components/checkout/checkout-form.tsx +564 -437
- package/templates/nextjs/base/src/components/seo/article-json-ld.tsx +59 -0
- package/templates/nextjs/base/src/components/seo/category-json-ld.tsx +61 -0
- package/templates/nextjs/base/src/components/seo/organization-json-ld.tsx +4 -1
- package/templates/nextjs/base/src/components/seo/product-json-ld.tsx +7 -0
- package/templates/nextjs/designs/atelier/app-overlay/layout.tsx.ejs +255 -255
- package/templates/nextjs/designs/atelier/ui/shared/icons.tsx +269 -269
|
@@ -1,227 +1,227 @@
|
|
|
1
|
-
<% if (i18nEnabled) { %>
|
|
2
|
-
import type { Metadata } from 'next';
|
|
3
|
-
<%- fontImport %>
|
|
4
|
-
import { StoreProvider } from '@/core/providers/store-provider';
|
|
5
|
-
import { AnnouncementBar } from '@/ui/layout/announcement-bar';
|
|
6
|
-
import { SiteHeader } from '@/ui/layout/site-header';
|
|
7
|
-
import { SiteFooter } from '@/ui/layout/site-footer';
|
|
8
|
-
import { BrainerceBotWidget } from '@/components/brainerce-bot';
|
|
9
|
-
import { getServerClient, fetchStoreInfo } from '@/core/lib/brainerce';
|
|
10
|
-
import { getDirection, getMessages, supportedLocales } from '@/i18n';
|
|
11
|
-
import { getNonce } from '@/core/lib/nonce';
|
|
12
|
-
import '../globals.css';
|
|
13
|
-
|
|
14
|
-
<%- fontVariable %>
|
|
15
|
-
|
|
16
|
-
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://example.com';
|
|
17
|
-
|
|
18
|
-
export const metadata: Metadata = {
|
|
19
|
-
metadataBase: new URL(baseUrl),
|
|
20
|
-
title: {
|
|
21
|
-
default: <%- storeNameJs %>,
|
|
22
|
-
template: <%- titleTemplateJs %>,
|
|
23
|
-
},
|
|
24
|
-
description: <%- storeNameJs %>,
|
|
25
|
-
alternates: {
|
|
26
|
-
canonical: '/',
|
|
27
|
-
},
|
|
28
|
-
openGraph: {
|
|
29
|
-
siteName: <%- storeNameJs %>,
|
|
30
|
-
type: 'website',
|
|
31
|
-
},
|
|
32
|
-
robots: {
|
|
33
|
-
index: true,
|
|
34
|
-
follow: true,
|
|
35
|
-
},
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const organizationJsonLd = {
|
|
39
|
-
'@context': 'https://schema.org',
|
|
40
|
-
'@type': 'Organization',
|
|
41
|
-
name: <%- storeNameJs %>,
|
|
42
|
-
url: baseUrl,
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
export function generateStaticParams() {
|
|
46
|
-
return supportedLocales.map((locale) => ({ locale }));
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export default async function RootLayout({
|
|
50
|
-
children,
|
|
51
|
-
params,
|
|
52
|
-
}: {
|
|
53
|
-
children: React.ReactNode;
|
|
54
|
-
params: Promise<{ locale: string }>;
|
|
55
|
-
}) {
|
|
56
|
-
const { locale } = await params;
|
|
57
|
-
const dir = getDirection(locale);
|
|
58
|
-
const nonce = await getNonce();
|
|
59
|
-
|
|
60
|
-
// Merchant-driven layout chrome — fetched server-side. Each call falls back
|
|
61
|
-
// to null/[] on 404 so the layout never crashes when the merchant hasn't
|
|
62
|
-
// seeded a particular content type yet. New stores ship with default rows
|
|
63
|
-
// seeded by the backend (StoresService.seedDefaultContent).
|
|
64
|
-
const client = getServerClient(locale);
|
|
65
|
-
const [announcements, siteHeader, siteFooter, storeInfo, messages] = await Promise.all([
|
|
66
|
-
client.content.announcement.list(locale).catch(() => []),
|
|
67
|
-
client.content.header.get('main', locale).catch(() => null),
|
|
68
|
-
client.content.footer.get('main', locale).catch(() => null),
|
|
69
|
-
// SSR-fetch store config so PriceDisplay / FreeShippingBar / upsell UI
|
|
70
|
-
// render with the real currency, feature flags, and i18n config at frame 0
|
|
71
|
-
// — without this, Googlebot sees USD/defaults baked into the HTML.
|
|
72
|
-
fetchStoreInfo(locale),
|
|
73
|
-
// SSR-fetch UI message strings so translated text renders at frame 0 —
|
|
74
|
-
// without this, every page (and every locale switch) shows raw
|
|
75
|
-
// "namespace.key" strings until the client-only fetch resolves.
|
|
76
|
-
getMessages(locale),
|
|
77
|
-
]);
|
|
78
|
-
|
|
79
|
-
return (
|
|
80
|
-
<html lang={locale} dir={dir}>
|
|
81
|
-
<head>
|
|
82
|
-
<script
|
|
83
|
-
type="application/ld+json"
|
|
84
|
-
nonce={nonce}
|
|
85
|
-
suppressHydrationWarning
|
|
86
|
-
dangerouslySetInnerHTML={{
|
|
87
|
-
__html: JSON.stringify(organizationJsonLd)
|
|
88
|
-
.replace(/</g, '\\u003c')
|
|
89
|
-
.replace(/>/g, '\\u003e')
|
|
90
|
-
.replace(/&/g, '\\u0026'),
|
|
91
|
-
}}
|
|
92
|
-
/>
|
|
93
|
-
{/* Brainerce cookieless traffic analytics — auto-tracks pageviews +
|
|
94
|
-
SPA route changes. Cookieless (no consent banner needed); the
|
|
95
|
-
merchant can toggle it per sales channel in the dashboard. */}
|
|
96
|
-
<script
|
|
97
|
-
defer
|
|
98
|
-
src={`${process.env.NEXT_PUBLIC_BRAINERCE_PIXEL_URL || 'https://api.brainerce.com'}/t.js`}
|
|
99
|
-
data-channel={
|
|
100
|
-
process.env.NEXT_PUBLIC_BRAINERCE_SALES_CHANNEL_ID || '<%= connectionId %>'
|
|
101
|
-
}
|
|
102
|
-
nonce={nonce}
|
|
103
|
-
suppressHydrationWarning
|
|
104
|
-
/>
|
|
105
|
-
</head>
|
|
106
|
-
<body className={font.className}>
|
|
107
|
-
<StoreProvider locale={locale} initialStoreInfo={storeInfo} initialMessages={messages}>
|
|
108
|
-
<div className="min-h-screen flex flex-col">
|
|
109
|
-
<AnnouncementBar announcements={announcements} />
|
|
110
|
-
<SiteHeader header={siteHeader} storeName={<%- storeNameJs %>} />
|
|
111
|
-
<main className="flex-1">{children}</main>
|
|
112
|
-
<SiteFooter footer={siteFooter} storeName={<%- storeNameJs %>} />
|
|
113
|
-
</div>
|
|
114
|
-
<BrainerceBotWidget />
|
|
115
|
-
</StoreProvider>
|
|
116
|
-
</body>
|
|
117
|
-
</html>
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
<% } else { %>
|
|
121
|
-
import type { Metadata } from 'next';
|
|
122
|
-
<%- fontImport %>
|
|
123
|
-
import { StoreProvider } from '@/core/providers/store-provider';
|
|
124
|
-
import { AnnouncementBar } from '@/ui/layout/announcement-bar';
|
|
125
|
-
import { SiteHeader } from '@/ui/layout/site-header';
|
|
126
|
-
import { SiteFooter } from '@/ui/layout/site-footer';
|
|
127
|
-
import { BrainerceBotWidget } from '@/components/brainerce-bot';
|
|
128
|
-
import { getServerClient, fetchStoreInfo } from '@/core/lib/brainerce';
|
|
129
|
-
import { getNonce } from '@/core/lib/nonce';
|
|
130
|
-
import './globals.css';
|
|
131
|
-
|
|
132
|
-
<%- fontVariable %>
|
|
133
|
-
|
|
134
|
-
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://example.com';
|
|
135
|
-
|
|
136
|
-
export const metadata: Metadata = {
|
|
137
|
-
metadataBase: new URL(baseUrl),
|
|
138
|
-
title: {
|
|
139
|
-
default: <%- storeNameJs %>,
|
|
140
|
-
template: <%- titleTemplateJs %>,
|
|
141
|
-
},
|
|
142
|
-
description: <%- storeNameJs %>,
|
|
143
|
-
alternates: {
|
|
144
|
-
canonical: '/',
|
|
145
|
-
},
|
|
146
|
-
openGraph: {
|
|
147
|
-
siteName: <%- storeNameJs %>,
|
|
148
|
-
locale: '<%= ogLocale %>',
|
|
149
|
-
type: 'website',
|
|
150
|
-
},
|
|
151
|
-
robots: {
|
|
152
|
-
index: true,
|
|
153
|
-
follow: true,
|
|
154
|
-
},
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
const organizationJsonLd = {
|
|
158
|
-
'@context': 'https://schema.org',
|
|
159
|
-
'@type': 'Organization',
|
|
160
|
-
name: <%- storeNameJs %>,
|
|
161
|
-
url: baseUrl,
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
export default async function RootLayout({
|
|
165
|
-
children,
|
|
166
|
-
}: {
|
|
167
|
-
children: React.ReactNode;
|
|
168
|
-
}) {
|
|
169
|
-
const nonce = await getNonce();
|
|
170
|
-
|
|
171
|
-
// Merchant-driven layout chrome — fetched server-side. Each call falls back
|
|
172
|
-
// to null/[] on 404 so the layout never crashes when the merchant hasn't
|
|
173
|
-
// seeded a particular content type yet. New stores ship with default rows
|
|
174
|
-
// seeded by the backend (StoresService.seedDefaultContent).
|
|
175
|
-
const client = getServerClient();
|
|
176
|
-
const [announcements, siteHeader, siteFooter, storeInfo] = await Promise.all([
|
|
177
|
-
client.content.announcement.list().catch(() => []),
|
|
178
|
-
client.content.header.get('main').catch(() => null),
|
|
179
|
-
client.content.footer.get('main').catch(() => null),
|
|
180
|
-
// SSR-fetch store config so PriceDisplay / FreeShippingBar / upsell UI
|
|
181
|
-
// render with the real currency, feature flags, and i18n config at frame 0
|
|
182
|
-
// — without this, Googlebot sees USD/defaults baked into the HTML.
|
|
183
|
-
fetchStoreInfo(),
|
|
184
|
-
]);
|
|
185
|
-
|
|
186
|
-
return (
|
|
187
|
-
<html lang="<%= language %>" dir="<%= direction %>">
|
|
188
|
-
<head>
|
|
189
|
-
<script
|
|
190
|
-
type="application/ld+json"
|
|
191
|
-
nonce={nonce}
|
|
192
|
-
suppressHydrationWarning
|
|
193
|
-
dangerouslySetInnerHTML={{
|
|
194
|
-
__html: JSON.stringify(organizationJsonLd)
|
|
195
|
-
.replace(/</g, '\\u003c')
|
|
196
|
-
.replace(/>/g, '\\u003e')
|
|
197
|
-
.replace(/&/g, '\\u0026'),
|
|
198
|
-
}}
|
|
199
|
-
/>
|
|
200
|
-
{/* Brainerce cookieless traffic analytics — auto-tracks pageviews +
|
|
201
|
-
SPA route changes. Cookieless (no consent banner needed); the
|
|
202
|
-
merchant can toggle it per sales channel in the dashboard. */}
|
|
203
|
-
<script
|
|
204
|
-
defer
|
|
205
|
-
src={`${process.env.NEXT_PUBLIC_BRAINERCE_PIXEL_URL || 'https://api.brainerce.com'}/t.js`}
|
|
206
|
-
data-channel={
|
|
207
|
-
process.env.NEXT_PUBLIC_BRAINERCE_SALES_CHANNEL_ID || '<%= connectionId %>'
|
|
208
|
-
}
|
|
209
|
-
nonce={nonce}
|
|
210
|
-
suppressHydrationWarning
|
|
211
|
-
/>
|
|
212
|
-
</head>
|
|
213
|
-
<body className={font.className}>
|
|
214
|
-
<StoreProvider initialStoreInfo={storeInfo}>
|
|
215
|
-
<div className="min-h-screen flex flex-col">
|
|
216
|
-
<AnnouncementBar announcements={announcements} />
|
|
217
|
-
<SiteHeader header={siteHeader} storeName={<%- storeNameJs %>} />
|
|
218
|
-
<main className="flex-1">{children}</main>
|
|
219
|
-
<SiteFooter footer={siteFooter} storeName={<%- storeNameJs %>} />
|
|
220
|
-
</div>
|
|
221
|
-
<BrainerceBotWidget />
|
|
222
|
-
</StoreProvider>
|
|
223
|
-
</body>
|
|
224
|
-
</html>
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
<% } %>
|
|
1
|
+
<% if (i18nEnabled) { %>
|
|
2
|
+
import type { Metadata } from 'next';
|
|
3
|
+
<%- fontImport %>
|
|
4
|
+
import { StoreProvider } from '@/core/providers/store-provider';
|
|
5
|
+
import { AnnouncementBar } from '@/ui/layout/announcement-bar';
|
|
6
|
+
import { SiteHeader } from '@/ui/layout/site-header';
|
|
7
|
+
import { SiteFooter } from '@/ui/layout/site-footer';
|
|
8
|
+
import { BrainerceBotWidget } from '@/components/brainerce-bot';
|
|
9
|
+
import { getServerClient, fetchStoreInfo } from '@/core/lib/brainerce';
|
|
10
|
+
import { getDirection, getMessages, supportedLocales } from '@/i18n';
|
|
11
|
+
import { getNonce } from '@/core/lib/nonce';
|
|
12
|
+
import '../globals.css';
|
|
13
|
+
|
|
14
|
+
<%- fontVariable %>
|
|
15
|
+
|
|
16
|
+
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://example.com';
|
|
17
|
+
|
|
18
|
+
export const metadata: Metadata = {
|
|
19
|
+
metadataBase: new URL(baseUrl),
|
|
20
|
+
title: {
|
|
21
|
+
default: <%- storeNameJs %>,
|
|
22
|
+
template: <%- titleTemplateJs %>,
|
|
23
|
+
},
|
|
24
|
+
description: <%- storeNameJs %>,
|
|
25
|
+
alternates: {
|
|
26
|
+
canonical: '/',
|
|
27
|
+
},
|
|
28
|
+
openGraph: {
|
|
29
|
+
siteName: <%- storeNameJs %>,
|
|
30
|
+
type: 'website',
|
|
31
|
+
},
|
|
32
|
+
robots: {
|
|
33
|
+
index: true,
|
|
34
|
+
follow: true,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const organizationJsonLd = {
|
|
39
|
+
'@context': 'https://schema.org',
|
|
40
|
+
'@type': 'Organization',
|
|
41
|
+
name: <%- storeNameJs %>,
|
|
42
|
+
url: baseUrl,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export function generateStaticParams() {
|
|
46
|
+
return supportedLocales.map((locale) => ({ locale }));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export default async function RootLayout({
|
|
50
|
+
children,
|
|
51
|
+
params,
|
|
52
|
+
}: {
|
|
53
|
+
children: React.ReactNode;
|
|
54
|
+
params: Promise<{ locale: string }>;
|
|
55
|
+
}) {
|
|
56
|
+
const { locale } = await params;
|
|
57
|
+
const dir = getDirection(locale);
|
|
58
|
+
const nonce = await getNonce();
|
|
59
|
+
|
|
60
|
+
// Merchant-driven layout chrome — fetched server-side. Each call falls back
|
|
61
|
+
// to null/[] on 404 so the layout never crashes when the merchant hasn't
|
|
62
|
+
// seeded a particular content type yet. New stores ship with default rows
|
|
63
|
+
// seeded by the backend (StoresService.seedDefaultContent).
|
|
64
|
+
const client = getServerClient(locale);
|
|
65
|
+
const [announcements, siteHeader, siteFooter, storeInfo, messages] = await Promise.all([
|
|
66
|
+
client.content.announcement.list(locale).catch(() => []),
|
|
67
|
+
client.content.header.get('main', locale).catch(() => null),
|
|
68
|
+
client.content.footer.get('main', locale).catch(() => null),
|
|
69
|
+
// SSR-fetch store config so PriceDisplay / FreeShippingBar / upsell UI
|
|
70
|
+
// render with the real currency, feature flags, and i18n config at frame 0
|
|
71
|
+
// — without this, Googlebot sees USD/defaults baked into the HTML.
|
|
72
|
+
fetchStoreInfo(locale),
|
|
73
|
+
// SSR-fetch UI message strings so translated text renders at frame 0 —
|
|
74
|
+
// without this, every page (and every locale switch) shows raw
|
|
75
|
+
// "namespace.key" strings until the client-only fetch resolves.
|
|
76
|
+
getMessages(locale),
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<html lang={locale} dir={dir}>
|
|
81
|
+
<head>
|
|
82
|
+
<script
|
|
83
|
+
type="application/ld+json"
|
|
84
|
+
nonce={nonce}
|
|
85
|
+
suppressHydrationWarning
|
|
86
|
+
dangerouslySetInnerHTML={{
|
|
87
|
+
__html: JSON.stringify(organizationJsonLd)
|
|
88
|
+
.replace(/</g, '\\u003c')
|
|
89
|
+
.replace(/>/g, '\\u003e')
|
|
90
|
+
.replace(/&/g, '\\u0026'),
|
|
91
|
+
}}
|
|
92
|
+
/>
|
|
93
|
+
{/* Brainerce cookieless traffic analytics — auto-tracks pageviews +
|
|
94
|
+
SPA route changes. Cookieless (no consent banner needed); the
|
|
95
|
+
merchant can toggle it per sales channel in the dashboard. */}
|
|
96
|
+
<script
|
|
97
|
+
defer
|
|
98
|
+
src={`${process.env.NEXT_PUBLIC_BRAINERCE_PIXEL_URL || 'https://api.brainerce.com'}/t.js`}
|
|
99
|
+
data-channel={
|
|
100
|
+
process.env.NEXT_PUBLIC_BRAINERCE_SALES_CHANNEL_ID || '<%= connectionId %>'
|
|
101
|
+
}
|
|
102
|
+
nonce={nonce}
|
|
103
|
+
suppressHydrationWarning
|
|
104
|
+
/>
|
|
105
|
+
</head>
|
|
106
|
+
<body className={font.className}>
|
|
107
|
+
<StoreProvider locale={locale} initialStoreInfo={storeInfo} initialMessages={messages}>
|
|
108
|
+
<div className="min-h-screen flex flex-col">
|
|
109
|
+
<AnnouncementBar announcements={announcements} />
|
|
110
|
+
<SiteHeader header={siteHeader} storeName={<%- storeNameJs %>} />
|
|
111
|
+
<main className="flex-1">{children}</main>
|
|
112
|
+
<SiteFooter footer={siteFooter} storeName={<%- storeNameJs %>} />
|
|
113
|
+
</div>
|
|
114
|
+
<BrainerceBotWidget />
|
|
115
|
+
</StoreProvider>
|
|
116
|
+
</body>
|
|
117
|
+
</html>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
<% } else { %>
|
|
121
|
+
import type { Metadata } from 'next';
|
|
122
|
+
<%- fontImport %>
|
|
123
|
+
import { StoreProvider } from '@/core/providers/store-provider';
|
|
124
|
+
import { AnnouncementBar } from '@/ui/layout/announcement-bar';
|
|
125
|
+
import { SiteHeader } from '@/ui/layout/site-header';
|
|
126
|
+
import { SiteFooter } from '@/ui/layout/site-footer';
|
|
127
|
+
import { BrainerceBotWidget } from '@/components/brainerce-bot';
|
|
128
|
+
import { getServerClient, fetchStoreInfo } from '@/core/lib/brainerce';
|
|
129
|
+
import { getNonce } from '@/core/lib/nonce';
|
|
130
|
+
import './globals.css';
|
|
131
|
+
|
|
132
|
+
<%- fontVariable %>
|
|
133
|
+
|
|
134
|
+
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://example.com';
|
|
135
|
+
|
|
136
|
+
export const metadata: Metadata = {
|
|
137
|
+
metadataBase: new URL(baseUrl),
|
|
138
|
+
title: {
|
|
139
|
+
default: <%- storeNameJs %>,
|
|
140
|
+
template: <%- titleTemplateJs %>,
|
|
141
|
+
},
|
|
142
|
+
description: <%- storeNameJs %>,
|
|
143
|
+
alternates: {
|
|
144
|
+
canonical: '/',
|
|
145
|
+
},
|
|
146
|
+
openGraph: {
|
|
147
|
+
siteName: <%- storeNameJs %>,
|
|
148
|
+
locale: '<%= ogLocale %>',
|
|
149
|
+
type: 'website',
|
|
150
|
+
},
|
|
151
|
+
robots: {
|
|
152
|
+
index: true,
|
|
153
|
+
follow: true,
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const organizationJsonLd = {
|
|
158
|
+
'@context': 'https://schema.org',
|
|
159
|
+
'@type': 'Organization',
|
|
160
|
+
name: <%- storeNameJs %>,
|
|
161
|
+
url: baseUrl,
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
export default async function RootLayout({
|
|
165
|
+
children,
|
|
166
|
+
}: {
|
|
167
|
+
children: React.ReactNode;
|
|
168
|
+
}) {
|
|
169
|
+
const nonce = await getNonce();
|
|
170
|
+
|
|
171
|
+
// Merchant-driven layout chrome — fetched server-side. Each call falls back
|
|
172
|
+
// to null/[] on 404 so the layout never crashes when the merchant hasn't
|
|
173
|
+
// seeded a particular content type yet. New stores ship with default rows
|
|
174
|
+
// seeded by the backend (StoresService.seedDefaultContent).
|
|
175
|
+
const client = getServerClient();
|
|
176
|
+
const [announcements, siteHeader, siteFooter, storeInfo] = await Promise.all([
|
|
177
|
+
client.content.announcement.list().catch(() => []),
|
|
178
|
+
client.content.header.get('main').catch(() => null),
|
|
179
|
+
client.content.footer.get('main').catch(() => null),
|
|
180
|
+
// SSR-fetch store config so PriceDisplay / FreeShippingBar / upsell UI
|
|
181
|
+
// render with the real currency, feature flags, and i18n config at frame 0
|
|
182
|
+
// — without this, Googlebot sees USD/defaults baked into the HTML.
|
|
183
|
+
fetchStoreInfo(),
|
|
184
|
+
]);
|
|
185
|
+
|
|
186
|
+
return (
|
|
187
|
+
<html lang="<%= language %>" dir="<%= direction %>">
|
|
188
|
+
<head>
|
|
189
|
+
<script
|
|
190
|
+
type="application/ld+json"
|
|
191
|
+
nonce={nonce}
|
|
192
|
+
suppressHydrationWarning
|
|
193
|
+
dangerouslySetInnerHTML={{
|
|
194
|
+
__html: JSON.stringify(organizationJsonLd)
|
|
195
|
+
.replace(/</g, '\\u003c')
|
|
196
|
+
.replace(/>/g, '\\u003e')
|
|
197
|
+
.replace(/&/g, '\\u0026'),
|
|
198
|
+
}}
|
|
199
|
+
/>
|
|
200
|
+
{/* Brainerce cookieless traffic analytics — auto-tracks pageviews +
|
|
201
|
+
SPA route changes. Cookieless (no consent banner needed); the
|
|
202
|
+
merchant can toggle it per sales channel in the dashboard. */}
|
|
203
|
+
<script
|
|
204
|
+
defer
|
|
205
|
+
src={`${process.env.NEXT_PUBLIC_BRAINERCE_PIXEL_URL || 'https://api.brainerce.com'}/t.js`}
|
|
206
|
+
data-channel={
|
|
207
|
+
process.env.NEXT_PUBLIC_BRAINERCE_SALES_CHANNEL_ID || '<%= connectionId %>'
|
|
208
|
+
}
|
|
209
|
+
nonce={nonce}
|
|
210
|
+
suppressHydrationWarning
|
|
211
|
+
/>
|
|
212
|
+
</head>
|
|
213
|
+
<body className={font.className}>
|
|
214
|
+
<StoreProvider initialStoreInfo={storeInfo}>
|
|
215
|
+
<div className="min-h-screen flex flex-col">
|
|
216
|
+
<AnnouncementBar announcements={announcements} />
|
|
217
|
+
<SiteHeader header={siteHeader} storeName={<%- storeNameJs %>} />
|
|
218
|
+
<main className="flex-1">{children}</main>
|
|
219
|
+
<SiteFooter footer={siteFooter} storeName={<%- storeNameJs %>} />
|
|
220
|
+
</div>
|
|
221
|
+
<BrainerceBotWidget />
|
|
222
|
+
</StoreProvider>
|
|
223
|
+
</body>
|
|
224
|
+
</html>
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
<% } %>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* llms.txt — GET /llms.txt
|
|
3
|
+
*
|
|
4
|
+
* A machine-readable site summary for AI answer engines (ChatGPT, Perplexity,
|
|
5
|
+
* Claude, AI Overviews). Emerging convention: a concise markdown map of what
|
|
6
|
+
* the site is and where its key content lives, so AI crawlers ground answers
|
|
7
|
+
* in (and cite) the right pages.
|
|
8
|
+
*/
|
|
9
|
+
import { getServerClient } from '@/core/lib/brainerce';
|
|
10
|
+
|
|
11
|
+
export const revalidate = 3600;
|
|
12
|
+
|
|
13
|
+
export async function GET() {
|
|
14
|
+
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://example.com';
|
|
15
|
+
const client = getServerClient();
|
|
16
|
+
|
|
17
|
+
const [info, posts] = await Promise.all([
|
|
18
|
+
client.getStoreInfo().catch(() => null),
|
|
19
|
+
client.blog.getPosts({ limit: 20 }).catch(() => null),
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
const lines: string[] = [
|
|
23
|
+
`# ${info?.name ?? 'Store'}`,
|
|
24
|
+
'',
|
|
25
|
+
...(info?.metaDescription ? [`> ${info.metaDescription}`, ''] : []),
|
|
26
|
+
'## Key pages',
|
|
27
|
+
'',
|
|
28
|
+
`- [Products](${baseUrl}/products): full catalog`,
|
|
29
|
+
`- [Blog](${baseUrl}/blog): guides and articles`,
|
|
30
|
+
...(info?.contactEmail ? [`- Contact: ${info.contactEmail}`] : []),
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
if (posts && posts.data.length > 0) {
|
|
34
|
+
lines.push('', '## Recent articles', '');
|
|
35
|
+
for (const post of posts.data) {
|
|
36
|
+
const summary = post.seoDescription ?? post.excerpt ?? '';
|
|
37
|
+
lines.push(`- [${post.title}](${baseUrl}/blog/${post.slug})${summary ? `: ${summary}` : ''}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return new Response(lines.join('\n') + '\n', {
|
|
42
|
+
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
43
|
+
});
|
|
44
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { MetadataRoute } from 'next';
|
|
2
|
+
import { getBlogSitemapEntries, getCategorySitemapEntries } from 'brainerce';
|
|
2
3
|
import { getServerClient } from '@/core/lib/brainerce';
|
|
3
4
|
|
|
4
5
|
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|
@@ -42,7 +43,23 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|
|
42
43
|
});
|
|
43
44
|
});
|
|
44
45
|
|
|
45
|
-
|
|
46
|
+
// Category (collection) pages — the highest-value organic-SEO surface;
|
|
47
|
+
// must be crawlable so they can rank for broad research-intent queries.
|
|
48
|
+
const categoryPages: MetadataRoute.Sitemap = await getCategorySitemapEntries(client, {
|
|
49
|
+
siteUrl: baseUrl,
|
|
50
|
+
locales: isMultiLocale ? supportedLocales : undefined,
|
|
51
|
+
defaultLocale: defaultLoc,
|
|
52
|
+
}).catch(() => []);
|
|
53
|
+
|
|
54
|
+
// Blog posts — the SEO Autopilot (and manual publishes) must be
|
|
55
|
+
// discoverable; missing blog entries = articles never get crawled.
|
|
56
|
+
const blogPages: MetadataRoute.Sitemap = await getBlogSitemapEntries(client, {
|
|
57
|
+
siteUrl: baseUrl,
|
|
58
|
+
locales: isMultiLocale ? supportedLocales : undefined,
|
|
59
|
+
defaultLocale: defaultLoc,
|
|
60
|
+
}).catch(() => []);
|
|
61
|
+
|
|
62
|
+
return [...staticPages, ...productPages, ...categoryPages, ...blogPages];
|
|
46
63
|
} catch {
|
|
47
64
|
return staticPages;
|
|
48
65
|
}
|
|
@@ -34,6 +34,13 @@ export function BrainerceBotWidget() {
|
|
|
34
34
|
BrainerceBot.mount({
|
|
35
35
|
connectionId,
|
|
36
36
|
baseUrl: process.env.NEXT_PUBLIC_BRAINERCE_API_URL || undefined,
|
|
37
|
+
// Order-lookup login gating: this template's own same-origin BFF proxy
|
|
38
|
+
// (see src/app/api/store/[...path]/route.ts) already forwards the
|
|
39
|
+
// shopper's httpOnly session cookie as a Bearer header — the same
|
|
40
|
+
// mechanism @/core/lib/brainerce's own client uses. Opting the bot in
|
|
41
|
+
// here lets it detect login state without the widget ever touching the
|
|
42
|
+
// cookie itself.
|
|
43
|
+
customerSessionProxyPath: '/api/store',
|
|
37
44
|
onAddToCart: async ({ productId, variantId, quantity }) => {
|
|
38
45
|
try {
|
|
39
46
|
const { getClient } = await import('@/core/lib/brainerce');
|