@xenterprises/nuxt-x-marketing 1.2.2 → 1.4.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/CHANGELOG.md ADDED
@@ -0,0 +1,64 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@xenterprises/nuxt-x-marketing` are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.4.0] - 2026-08-02
9
+
10
+ ### Removed
11
+
12
+ > **Released as a minor despite removing public components.** Semver would call
13
+ > this a major. Sites on `^1.3.0` will therefore pick this up automatically —
14
+ > if you import any `XMarkAffiliate*` component, pin to `1.3.0` or migrate to
15
+ > the `XAF*` equivalents below before upgrading.
16
+
17
+ - **Removed the `XMarkAffiliate*` component family** — `XMarkAffiliateDisclosure`,
18
+ `XMarkAffiliateProductCard`, `XMarkAffiliateProductGrid`, `XMarkAffiliateProductDetail`,
19
+ and `XMarkAffiliateComparisonTable` (1,462 lines).
20
+
21
+ They duplicated components `@xenterprises/nuxt-x-affiliate` already ships, without its
22
+ per-merchant tagging (`xAffiliateContent.merchants`), impression tracking, or
23
+ Product/Offer structured data — so a site using them shipped untagged outbound links
24
+ and no product schema.
25
+
26
+ Migrate to `<XAFDisclosure>`, `<XAFProductCard>`, `<XAFRelatedProducts>`,
27
+ `<XAFComparisonTable>`, and (for the detail page) `<XAFHero>` + `<XAFProsCons>` +
28
+ `<XAFVerdict>` + `<XAFSpecsTable>`. Affiliate sites should extend `nuxt-x-affiliate`
29
+ directly — it is now self-contained and ships the site chrome (header, hero, sections,
30
+ cards, footer) that previously required this layer alongside it.
31
+
32
+ ### Changed
33
+
34
+ - **`app/pages/index.vue`** no longer showcases the removed affiliate components; the
35
+ section and its sample product data were dropped with them. Every other section of the
36
+ demo page is unchanged.
37
+
38
+ ## [1.3.0] - 2026-07-24
39
+
40
+ ### BREAKING
41
+
42
+ - **Blog migrated from Builder.io to Nuxt Content.** Consumers that relied on `NUXT_PUBLIC_BUILDERIO_KEY` and Builder models must migrate posts to `content/blog/*.md` and install the Content peer dependencies.
43
+ - **Default app shell rewritten to a config-driven shell.** The layer's `app/app.vue` (previously a hardcoded demo referencing non-existent `XMarkNavbar`/`XMarkFooter`, rendering no navbar/footer) now renders `XHeaderNav` + `XFooter` from `xMarketing.header`/`footer` config plus the consent banner. Consumers without their own `app/app.vue` will see the rendered shell change; the hardcoded newsletter section, dead demo links, and `/logo-white.svg`/`/logo-dark.svg` fallbacks are gone.
44
+ - **`xMarketing.name`/`url` defaults changed from placeholders to `undefined`** and both keys are now live: `name` drives the shell title template and `og:site_name` (page titles gain a `| <name>` suffix when set), `url` drives the home page `og:url`. Consumers reading the old placeholder defaults from `appConfig` now get `undefined`.
45
+
46
+ ### Added
47
+
48
+ - Config-driven default shell with `xMarketing` opt-out switches for header, footer, and consent banner.
49
+ - `xMarketing.blog.active` opt-out on the shipped blog pages.
50
+ - `xMarketing.name`/`url` wired into shell SEO defaults (title template, `og:site_name`, `og:url`).
51
+ - Playground e2e smoke suite (`test:e2e`, plan 17) and lint quality gate.
52
+ - Consumer-minimal quickstart docs and plan-18 batteries-included audit.
53
+
54
+ ### Changed
55
+
56
+ - Exported named `XMarketingConfig` interface for typed `app.config.ts` (plan-21 config-typing convention).
57
+ - Peer dependency ranges tightened to caret bounds.
58
+
59
+ ### Fixed
60
+
61
+ - Widened `better-sqlite3` peer range to `^11.0.0 || ^12.0.0`.
62
+ - Default home page references real component names; `UToggle` replaced with `USwitch`.
63
+ - Repaired red gates: added `test:run`/`test:coverage` aliases and pinned `vue`.
64
+ - Test suite: fixed stale hero title assertion and scoped `h1` queries to main content.
package/README.md CHANGED
@@ -31,86 +31,56 @@ export default defineNuxtConfig({
31
31
 
32
32
  ## Quick Start
33
33
 
34
- ### Basic App Layout
34
+ ### What the consumer writes
35
35
 
36
- Create your `app.vue` with Navbar, Newsletter, and Footer:
36
+ The layer is batteries-included: it ships a default app shell (navbar + footer + cookie-consent banner) and default pages (`/`, `/blog`, `/blog/[...slug]`). You do **not** write an `app.vue` configure the shell in `app/app.config.ts` under the `xMarketing` namespace (it must live in `app/`, not the project root):
37
37
 
38
- ```vue
39
- <template>
40
- <div class="min-h-screen flex flex-col">
41
- <XMarkNavbar
42
- :links="navLinks"
43
- :logo-light="'/logo-white.svg'"
44
- :logo-dark="'/logo-dark.svg'"
45
- logo-alt="Company"
46
- >
47
- <template #actions>
48
- <UButton variant="ghost" color="neutral">Sign In</UButton>
49
- <UButton color="primary">Get Started</UButton>
50
- </template>
51
- </XMarkNavbar>
52
-
53
- <main class="flex-grow">
54
- <NuxtPage />
55
- </main>
56
-
57
- <XMarkSection bg="subtle" padding="lg">
58
- <XMarkNewsletter
59
- title="Stay in the loop"
60
- description="Get the latest updates delivered to your inbox."
61
- @submit="handleNewsletterSubmit"
62
- />
63
- </XMarkSection>
38
+ ```javascript
39
+ // app/app.config.ts
40
+ export default defineAppConfig({
41
+ xMarketing: {
42
+ name: "Acme Inc",
43
+ url: "https://acme.com",
44
+ header: {
45
+ logo: { src: "/logo.svg", srcDark: "/logo-dark.svg", alt: "Acme" },
46
+ nav: {
47
+ links: [
48
+ { label: "Pricing", to: "/#pricing" },
49
+ { label: "Blog", to: "/blog" },
50
+ ],
51
+ buttons: [{ label: "Get Started", to: "/signup", color: "primary" }],
52
+ },
53
+ },
54
+ footer: {
55
+ logo: { src: "/logo.svg", alt: "Acme" },
56
+ body: "Building the future of modern software.",
57
+ socials: [
58
+ { name: "GitHub", url: "https://github.com/acme", icon: "i-lucide-github" },
59
+ ],
60
+ columns: [
61
+ {
62
+ headerLabel: "Product",
63
+ links: [{ label: "Blog", to: "/blog" }],
64
+ },
65
+ ],
66
+ },
67
+ blog: { active: true, title: "Blog" },
68
+ tracking: { gtmId: "GTM-XXXXXXX" }, // optional; fires only after consent
69
+ },
70
+ });
71
+ ```
64
72
 
65
- <XMarkFooter
66
- :logo="'/logo-dark.svg'"
67
- description="Building the future of modern software."
68
- :social="socialLinks"
69
- :columns="footerColumns"
70
- :legal-links="legalLinks"
71
- />
72
- </div>
73
- </template>
73
+ Blog posts are markdown in `content/blog/*.md` — re-declare the `blog` collection in your own `content.config.ts` so Nuxt Content binds it to your content dir (the layer's `.playground/content.config.ts` is the reference).
74
74
 
75
- <script setup>
76
- const navLinks = [
77
- { label: "Features", to: "/#features" },
78
- { label: "Pricing", to: "/pricing" },
79
- { label: "Blog", to: "/blog" },
80
- ];
81
-
82
- const socialLinks = [
83
- { name: "Twitter", href: "https://twitter.com", icon: "i-lucide-twitter" },
84
- { name: "GitHub", href: "https://github.com", icon: "i-lucide-github" },
85
- ];
86
-
87
- const footerColumns = [
88
- {
89
- title: "Product",
90
- links: [
91
- { label: "Features", to: "/#features" },
92
- { label: "Pricing", to: "/pricing" },
93
- ],
94
- },
95
- {
96
- title: "Company",
97
- links: [
98
- { label: "About", to: "/about" },
99
- { label: "Blog", to: "/blog" },
100
- ],
101
- },
102
- ];
75
+ ### Opting out of the defaults
103
76
 
104
- const legalLinks = [
105
- { label: "Privacy Policy", to: "/privacy" },
106
- { label: "Terms of Service", to: "/terms" },
107
- ];
77
+ Every shipped default has a switch or a standard Nuxt override:
108
78
 
109
- const handleNewsletterSubmit = (email) => {
110
- console.log("Newsletter signup:", email);
111
- };
112
- </script>
113
- ```
79
+ - `xMarketing.header.active: false` removes the default navbar from the shell
80
+ - `xMarketing.footer.active: false` — removes the default footer
81
+ - `xMarketing.consent.active: false` — removes the cookie-consent banner (tracking scripts then never fire)
82
+ - `xMarketing.blog.active: false` — the shipped `/blog` pages 404
83
+ - Replace the whole shell by shipping your own `app/app.vue`; replace any page by shipping the same path under `app/pages/` (standard Nuxt layer overriding)
114
84
 
115
85
  ### Example Landing Page
116
86
 
@@ -177,32 +147,28 @@ const pricingPlans = [
177
147
 
178
148
  ### Core Layout
179
149
 
180
- #### XMarkNavbar
150
+ #### XMarkLayoutNavbar
181
151
 
182
- Fixed navigation header with transparent-to-solid scroll transition.
152
+ Fixed navigation header with transparent-to-solid scroll transition. Falls back to `xMarketing.header` (logo, nav links, nav buttons) when props are omitted.
183
153
 
184
154
  ```vue
185
- <XMarkNavbar
155
+ <XMarkLayoutNavbar
186
156
  :links="[{ label: 'Features', to: '/#features' }]"
187
- logo-light="/logo-white.svg"
188
- logo-dark="/logo-dark.svg"
189
- logo-alt="Company"
157
+ :logo="{ src: '/logo.svg', srcDark: '/logo-dark.svg', alt: 'Company' }"
158
+ :buttons="[
159
+ { label: 'Sign In', to: '/login', variant: 'ghost' },
160
+ { label: 'Get Started', to: '/signup', color: 'primary' },
161
+ ]"
190
162
  :transparent="true"
191
163
  :scroll-threshold="100"
192
- >
193
- <template #actions>
194
- <UButton variant="ghost">Sign In</UButton>
195
- <UButton color="primary">Get Started</UButton>
196
- </template>
197
- </XMarkNavbar>
164
+ />
198
165
  ```
199
166
 
200
167
  | Prop | Type | Default | Description |
201
168
  |------|------|---------|-------------|
202
- | `links` | `Array` | `[]` | Navigation links `{ label, to }` |
203
- | `logoLight` | `String` | `''` | Logo for transparent/dark state |
204
- | `logoDark` | `String` | `''` | Logo for solid/light state |
205
- | `logoAlt` | `String` | `'Logo'` | Logo alt text |
169
+ | `links` | `Array` | `xMarketing.header.nav.links` | Navigation links `{ label, to }` |
170
+ | `logo` | `Object` | `xMarketing.header.logo` | Logo `{ src, srcDark?, alt? }` |
171
+ | `buttons` | `Array` | `xMarketing.header.nav.buttons` | Action buttons `{ label, to, color?, variant?, icon? }` |
206
172
  | `transparent` | `Boolean` | `true` | Start transparent over hero |
207
173
  | `scrollThreshold` | `Number` | `100` | Pixels before transition |
208
174
 
@@ -226,40 +192,39 @@ Section wrapper with background variants and optional patterns.
226
192
  | `bgImage` | `String` | `''` | Background image URL |
227
193
  | `parallax` | `Boolean` | `false` | Enable parallax on bg image |
228
194
 
229
- #### XMarkFooter
195
+ #### XMarkLayoutFooter
230
196
 
231
197
  Full footer with brand, link columns, social icons, and newsletter.
232
198
 
233
199
  ```vue
234
- <XMarkFooter
235
- logo="/logo.svg"
236
- logo-alt="Company"
200
+ <XMarkLayoutFooter
201
+ :logo="{ src: '/logo.svg', alt: 'Company' }"
237
202
  description="Building the future of modern software."
238
203
  :social="[{ name: 'Twitter', href: 'https://twitter.com', icon: 'i-lucide-twitter' }]"
239
204
  :columns="[{ title: 'Product', links: [{ label: 'Features', to: '/features' }] }]"
240
205
  :legal-links="[{ label: 'Privacy', to: '/privacy' }]"
241
- :show-newsletter="true"
206
+ :has-newsletter="true"
242
207
  @newsletter-submit="handleSubmit"
243
208
  />
244
209
  ```
245
210
 
246
211
  | Prop | Type | Default | Description |
247
212
  |------|------|---------|-------------|
248
- | `logo` | `String` | `''` | Logo image URL |
213
+ | `logo` | `Object` | `null` | Logo `{ src, alt? }` |
249
214
  | `description` | `String` | `''` | Brand description |
250
215
  | `social` | `Array` | `[]` | Social links `{ name, href, icon }` |
251
216
  | `columns` | `Array` | `[]` | Link columns `{ title, links: [{ label, to }] }` |
252
217
  | `legalLinks` | `Array` | `[]` | Legal links `{ label, to }` |
253
218
  | `copyright` | `String` | auto | Copyright text |
254
- | `showNewsletter` | `Boolean` | `false` | Show newsletter section |
219
+ | `hasNewsletter` | `Boolean` | `false` | Show newsletter section |
255
220
 
256
- #### XMarkLegalFooter
221
+ #### XMarkLayoutFooterLegal
257
222
 
258
223
  Minimal footer with copyright and legal links only.
259
224
 
260
225
  ```vue
261
- <XMarkLegalFooter
262
- company-name="X Enterprises"
226
+ <XMarkLayoutFooterLegal
227
+ copyright="© 2026 Acme Inc. All rights reserved."
263
228
  :links="[{ label: 'Privacy', to: '/privacy' }]"
264
229
  />
265
230
  ```
@@ -846,186 +811,14 @@ Chat widget trigger.
846
811
 
847
812
  ### Affiliate & Review Components
848
813
 
849
- For affiliate marketing, product review sites, and "best X" roundup posts.
850
-
851
- #### XMarkAffiliateDisclosure
852
-
853
- FTC/Amazon-required affiliate disclosure banner. Three variants for different placements.
854
-
855
- ```vue
856
- <!-- Subtle bar (default) -->
857
- <XMarkAffiliateDisclosure />
858
-
859
- <!-- Full-width banner at top of page -->
860
- <XMarkAffiliateDisclosure variant="banner" :dismissible="true" />
861
-
862
- <!-- Inline inside blog post prose -->
863
- <XMarkAffiliateDisclosure variant="inline" />
864
- ```
865
-
866
- | Prop | Type | Default | Description |
867
- |------|------|---------|-------------|
868
- | `text` | `String` | Amazon associate disclosure | Disclosure text |
869
- | `variant` | `String` | `'subtle'` | `subtle`, `banner`, `inline` |
870
- | `icon` | `String` | `'i-lucide-info'` | Icon name |
871
- | `dismissible` | `Boolean` | `false` | Show dismiss button |
872
- | `storageKey` | `String` | `'affiliate-disclosure-dismissed'` | localStorage key |
873
-
874
- #### XMarkAffiliateProductCard
875
-
876
- Product card with affiliate buy button, star rating, price, pros/cons. Three layout variants.
877
-
878
- ```vue
879
- <XMarkAffiliateProductCard
880
- :product="{
881
- name: 'Sony WH-1000XM5',
882
- image: '/products/sony-wh1000xm5.jpg',
883
- price: '$279.99',
884
- originalPrice: '$349.99',
885
- rating: 4.8,
886
- reviewCount: 12400,
887
- pros: ['Best-in-class ANC', '30hr battery', 'Foldable design'],
888
- cons: ['No IP rating', 'Expensive'],
889
- affiliateUrl: 'https://amazon.com/dp/B09XS7JWHH',
890
- affiliateTag: 'mysite-20',
891
- badge: '#1 Pick',
892
- }"
893
- layout="card"
894
- button-label="Check Price on Amazon"
895
- />
896
- ```
897
-
898
- | Prop | Type | Default | Description |
899
- |------|------|---------|-------------|
900
- | `product` | `Object` | sample product | Product data `{ name, image?, price?, originalPrice?, rating?, reviewCount?, pros?, cons?, affiliateUrl?, affiliateTag?, badge? }` |
901
- | `layout` | `String` | `'card'` | `card` (vertical), `row` (horizontal), `compact` (one-line) |
902
- | `showPros` | `Boolean` | `true` | Show pros list |
903
- | `showCons` | `Boolean` | `false` | Show cons list |
904
- | `showRating` | `Boolean` | `true` | Show star rating |
905
- | `showPrice` | `Boolean` | `true` | Show price |
906
- | `buttonLabel` | `String` | `'Check Price on Amazon'` | CTA button text |
907
- | `buttonIcon` | `String` | `'i-lucide-external-link'` | CTA button icon |
908
- | `disclosure` | `String` | `''` | Small disclosure text below button |
909
-
910
- #### XMarkAffiliateProductGrid
814
+ Removed in favour of [`@xenterprises/nuxt-x-affiliate`](../nuxt-x-affiliate).
911
815
 
912
- Grid of product cards for "best X" roundup posts. Automatically injects rank badges.
913
-
914
- ```vue
915
- <XMarkAffiliateProductGrid
916
- :products="topHeadphones"
917
- layout="grid"
918
- :columns="3"
919
- :show-ranking="true"
920
- disclosure="This page contains affiliate links."
921
- button-label="Check Price on Amazon"
922
- />
923
- ```
924
-
925
- | Prop | Type | Default | Description |
926
- |------|------|---------|-------------|
927
- | `products` | `Array` | `[]` | Array of product objects |
928
- | `layout` | `String` | `'grid'` | `grid` or `list` |
929
- | `columns` | `Number` | `3` | Grid columns: `2` or `3` |
930
- | `showRanking` | `Boolean` | `true` | Inject #1/#2/#3 badges on first 3 products |
931
- | `disclosure` | `String` | `''` | Disclosure shown once at top |
932
- | `buttonLabel` | `String` | `'Check Price on Amazon'` | CTA label for all cards |
933
- | `cardLayout` | `String` | `'card'` | Card layout variant |
934
-
935
- #### XMarkAffiliateProductDetail
936
-
937
- Full product review page layout — image gallery, editorial score, pros/cons table, specs, sticky buy box.
938
-
939
- ```vue
940
- <XMarkAffiliateProductDetail
941
- :product="{
942
- name: 'Sony WH-1000XM5 Review',
943
- images: ['/img/sony-1.jpg', '/img/sony-2.jpg'],
944
- price: '$279.99',
945
- originalPrice: '$349.99',
946
- rating: 4.8,
947
- reviewCount: 12400,
948
- pros: ['Best-in-class ANC', '30hr battery'],
949
- cons: ['No IP rating', 'Expensive'],
950
- specs: { 'Driver Size': '30mm', 'Battery': '30 hours', 'Weight': '250g' },
951
- description: 'The WH-1000XM5 sets the bar for noise-cancelling headphones.',
952
- verdict: 'The best ANC headphones money can buy in 2024.',
953
- affiliateUrl: 'https://amazon.com/dp/B09XS7JWHH',
954
- affiliateTag: 'mysite-20',
955
- }"
956
- :score="9.2"
957
- :has-buy-box="true"
958
- :has-specs="true"
959
- :has-pros-cons-table="true"
960
- disclosure="This page contains affiliate links."
961
- >
962
- <template #after-verdict>
963
- <!-- Additional content after verdict -->
964
- </template>
965
- <template #sidebar>
966
- <!-- Additional sidebar widgets -->
967
- </template>
968
- </XMarkAffiliateProductDetail>
969
- ```
970
-
971
- | Prop | Type | Default | Description |
972
- |------|------|---------|-------------|
973
- | `product` | `Object` | sample product | Full product data including `images[]`, `specs{}`, `verdict` |
974
- | `score` | `Number` | `null` | Editorial score 0–10 (shown as circular badge) |
975
- | `hasBuyBox` | `Boolean` | `true` | Show sticky buy box sidebar |
976
- | `hasSpecs` | `Boolean` | `true` | Show specs table |
977
- | `hasProsConsTable` | `Boolean` | `true` | Show pros/cons two-column table |
978
- | `disclosure` | `String` | `''` | Affiliate disclosure text |
979
-
980
- **Slots:** `buy-box`, `after-verdict`, `sidebar`
981
-
982
- #### XMarkAffiliateComparisonTable
983
-
984
- Side-by-side product comparison table for "X vs Y" posts. Highlights best values per row.
985
-
986
- ```vue
987
- <XMarkAffiliateComparisonTable
988
- :products="[
989
- {
990
- name: 'Sony WH-1000XM5',
991
- image: '/img/sony.jpg',
992
- price: '$279.99',
993
- affiliateUrl: 'https://amazon.com/dp/B09XS7JWHH',
994
- affiliateTag: 'mysite-20',
995
- specs: { price: '$279.99', rating: '4.8/5', battery: '30 hours', weight: '250g' },
996
- },
997
- {
998
- name: 'Bose QC45',
999
- image: '/img/bose.jpg',
1000
- price: '$249.99',
1001
- affiliateUrl: 'https://amazon.com/dp/B098FKXT8L',
1002
- affiliateTag: 'mysite-20',
1003
- specs: { price: '$249.99', rating: '4.6/5', battery: '24 hours', weight: '238g' },
1004
- },
1005
- ]"
1006
- :specs="[
1007
- { label: 'Price', key: 'price' },
1008
- { label: 'Rating', key: 'rating' },
1009
- { label: 'Battery Life', key: 'battery' },
1010
- { label: 'Weight', key: 'weight' },
1011
- ]"
1012
- :show-buy-buttons="true"
1013
- :highlight-best="true"
1014
- button-label="Buy on Amazon"
1015
- disclosure="This page contains affiliate links."
1016
- />
1017
- ```
1018
-
1019
- | Prop | Type | Default | Description |
1020
- |------|------|---------|-------------|
1021
- | `products` | `Array` | 2 sample products | 2–4 products with `specs` object |
1022
- | `specs` | `Array` | price/rating/battery | `[{ label, key }]` spec rows |
1023
- | `showBuyButtons` | `Boolean` | `true` | Show buy buttons in last row |
1024
- | `highlightBest` | `Boolean` | `true` | Highlight best value per row (lowest price, highest rating) |
1025
- | `buttonLabel` | `String` | `'Buy on Amazon'` | Buy button label |
1026
- | `disclosure` | `String` | `''` | Disclosure shown above table |
1027
-
1028
- ---
816
+ `XMarkAffiliateDisclosure`, `XMarkAffiliateProductCard`, `XMarkAffiliateProductGrid`,
817
+ `XMarkAffiliateProductDetail`, and `XMarkAffiliateComparisonTable` duplicated components
818
+ the affiliate layer already ships — without its per-merchant tagging
819
+ (`xAffiliateContent.merchants`), impression tracking, or Product/Offer structured data.
820
+ Use `<XAFDisclosure>`, `<XAFProductCard>`, `<XAFRelatedProducts>`, `<XAFBuyButton>`,
821
+ and `<XAFComparisonTable>` instead.
1029
822
 
1030
823
  ## Configuration
1031
824
 
@@ -1072,7 +865,7 @@ npm run test:e2e
1072
865
  | `useStaggerReveal(selector, delay?)` | Adds incremental `transition-delay` to `[data-reveal]` children inside a container. |
1073
866
  | `useParallax(options?)` | Parallax scroll effect for `.xParallax[data-parallax-speed]` elements. Uses `requestAnimationFrame` for performance. |
1074
867
  | `useElementParallax(speed?)` | Individual element parallax via template ref. Returns `{ elementRef }`. |
1075
- | `useXBlog()` | Blog data fetching composable. Reads `runtimeConfig.public.apiURL`. |
868
+ | `useXBlog()` | Nuxt Content blog helpers (`getPosts`, `getPostByPath`, `getSurround`, `normalizeBlogPost`). Expects a `blog` collection and markdown under `content/blog/`. |
1076
869
 
1077
870
  All composables are SSR-safe — they guard lifecycle hooks with `getCurrentInstance()` and `import.meta.client` checks.
1078
871
 
@@ -1082,11 +875,10 @@ All composables are SSR-safe — they guard lifecycle hooks with `getCurrentInst
1082
875
 
1083
876
  | Name | Required | Description |
1084
877
  |------|----------|-------------|
1085
- | `NUXT_PUBLIC_API_URL` | No | Base API URL for blog composable (used by `useXBlog`) |
1086
878
  | Marker.io Project ID | No | Set via `appConfig.xMarketing.config.markerProjectId` (not env var) |
1087
879
  | Newsletter Org ID | No | Set via `appConfig.xMarketing.config.emailMarketingNewsletters.organizationId` |
1088
880
 
1089
- The layer itself has no required environment variables. All configuration is done via `app.config.ts`.
881
+ The layer itself has no required environment variables. Blog content is **file-based via Nuxt Content** (not Builder.io). All site config is done via `app.config.ts`.
1090
882
 
1091
883
  ---
1092
884
 
@@ -1179,7 +971,8 @@ The layer provides a complete marketing website toolkit built on Nuxt UI v4:
1179
971
 
1180
972
  | Path | Purpose |
1181
973
  |------|---------|
1182
- | `nuxt.config.ts` | Registers `@nuxt/ui`, loads CSS, enables SSR and devtools |
974
+ | `nuxt.config.ts` | Registers `@nuxt/ui` + `@nuxt/content`, loads CSS, enables SSR |
975
+ | `content.config.ts` | Default Nuxt Content `blog` collection schema |
1183
976
  | `app/app.config.ts` | Default xMarketing config + Nuxt UI theme overrides + type augmentation |
1184
977
  | `app/app.vue` | Default app shell with navbar, newsletter, footer (override in consumer app) |
1185
978
  | `app/assets/css/x-marketing.css` | Full design system: colors, typography, animations, effects |
@@ -1190,7 +983,36 @@ The layer provides a complete marketing website toolkit built on Nuxt UI v4:
1190
983
  | `app/composables/` | Composables: useScrollReveal, useParallax, useXBlog |
1191
984
  | `app/plugins/marketing.client.ts` | Client plugin: auto-initializes scroll/parallax globally |
1192
985
  | `app/types/marketing.d.ts` | TypeScript interfaces for all data structures |
1193
- | `app/pages/` | Default blog pages (index + detail with slug routing) |
986
+ | `app/pages/blog/` | Default blog index + slug detail (Nuxt Content, not Builder.io) |
987
+
988
+ ### Blog (Nuxt Content)
989
+
990
+ **Breaking (Builder.io removed):** default `/blog` pages no longer call the Builder CDN.
991
+ Posts come from markdown in the consumer app.
992
+
993
+ 1. Peer-install `@nuxt/content` and `better-sqlite3` (already required by Content v3).
994
+ 2. Ensure the layer is extended so `content.config.ts` defines the `blog` collection (or copy/adapt it).
995
+ 3. Add posts under `content/blog/*.md`:
996
+
997
+ ```md
998
+ ---
999
+ title: Getting Started
1000
+ description: First post on the marketing site.
1001
+ date: 2025-01-15
1002
+ author: Tim
1003
+ image: /blog/cover.jpg
1004
+ category: Tutorial
1005
+ tags: [nuxt, marketing]
1006
+ published: true
1007
+ readingTime: 5
1008
+ ---
1009
+
1010
+ ## Hello
1011
+
1012
+ Markdown body rendered via `ContentRenderer`.
1013
+ ```
1014
+
1015
+ 4. `useXBlog().getPosts()` / `getPostByPath()` feed `XMarkBlogList` and `XMarkBlogDetail`.
1194
1016
 
1195
1017
  ### Overriding in Consumer Apps
1196
1018
 
@@ -1198,6 +1020,7 @@ The layer provides a complete marketing website toolkit built on Nuxt UI v4:
1198
1020
  - **app.config.ts**: Deep-merges with layer defaults. Set `xMarketing.header`, `xMarketing.footer`, etc.
1199
1021
  - **app.vue**: Override entirely by creating your own `app.vue` in the consumer app.
1200
1022
  - **Pages**: Consumer pages take precedence. Override `/blog` by creating `pages/blog/index.vue`.
1023
+ - **Content**: Own the markdown under `content/blog/`; do not put secrets in frontmatter.
1201
1024
 
1202
1025
  ---
1203
1026