create-nextblock 0.16.3 → 0.16.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-nextblock",
3
- "version": "0.16.3",
3
+ "version": "0.16.4",
4
4
  "description": "Scaffold a production-ready NextBlock CMS project — the open-source, full-stack AI-native CMS for Next.js 16, Supabase, and Tailwind CSS.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -9853,6 +9853,179 @@ BEGIN
9853
9853
  END $$;
9854
9854
 
9855
9855
 
9856
+ -- >>> FROM: 00000000000035_digital_products_seo_optimizations.sql <<<
9857
+ -- Migration: 00000000000035_digital_products_seo_optimizations.sql
9858
+ -- Purpose: Set meta_title and meta_description for the 2 real digital products (EN + FR)
9859
+ -- and optimize their block heading levels and copy readability to achieve 100/100 SEO scores.
9860
+ -- Safety: Append-only, forward-only, content-guarded updates.
9861
+
9862
+
9863
+ -- 1. Digital Products SEO Metadata (Titles & Descriptions)
9864
+ -- NextBlock Cortex AI - Cortex AI License (EN)
9865
+ UPDATE public.products
9866
+ SET meta_title = 'NextBlock Cortex AI License | Native AI Content Engine',
9867
+ meta_description = 'Add native AI copywriting, real-time block translation, and structure refactoring to your NextBlock editor with our BYOK Cortex AI license.',
9868
+ updated_at = NOW()
9869
+ WHERE id = 'd48a4bb4-5119-4576-9c1d-32c404dd84f8';
9870
+
9871
+ -- Licence NextBlock™ Cortex AI (FR)
9872
+ UPDATE public.products
9873
+ SET meta_title = 'Licence NextBlock Cortex AI | Moteur IA pour Éditeur',
9874
+ meta_description = 'Intégrez la rédaction assistée par IA, la traduction de blocs et l''optimisation de structure dans NextBlock grâce à la licence Cortex AI.',
9875
+ updated_at = NOW()
9876
+ WHERE id = '69848224-a6d6-43c9-9748-f41a8e8e424b';
9877
+
9878
+ -- NextBlock Commerce Pro - Commerce License (EN)
9879
+ UPDATE public.products
9880
+ SET meta_title = 'NextBlock Commerce Pro | Global Next.js Storefronts',
9881
+ meta_description = 'Launch high-performance digital and physical stores with headless Stripe checkout, Freemius licensing, and multi-currency pricing in NextBlock.',
9882
+ updated_at = NOW()
9883
+ WHERE id = 'ed86bd77-da61-4e6f-826c-956f9a55b7aa';
9884
+
9885
+ -- Licence NextBlock™ Commerce Pro (FR)
9886
+ UPDATE public.products
9887
+ SET meta_title = 'Licence NextBlock Commerce Pro | E-Commerce Next.js',
9888
+ meta_description = 'Créez des boutiques Next.js performantes avec paiement Stripe headless, licences logicielles Freemius et gestion multi-devises dans NextBlock.',
9889
+ updated_at = NOW()
9890
+ WHERE id = '5c9e1b99-eeed-4fe0-adfa-31e81004c2f9';
9891
+
9892
+ -- 2. Digital Products Block Content & Heading Optimizations
9893
+ -- Block ID 39
9894
+ UPDATE public.blocks
9895
+ SET content = '{"padding":{"top":"xl","bottom":"xl"},"background":{"type":"gradient","gradient":{"type":"linear","stops":[{"color":"#064e3b","position":0},{"color":"#0f172a","position":100}],"direction":"135deg"}},"column_gap":"xl","column_blocks":[[{"block_type":"text","content":{"html_content":"<p class=\\"text-xs uppercase tracking-[0.3em] text-emerald-400 font-semibold mb-4\\">Moteur E-Commerce d''Entreprise</p><h2 class=\\"text-3xl sm:text-4xl font-extrabold text-white tracking-tight mb-4\\">Faites de Next.js une Boutique Mondiale.</h2><p class=\\"text-base text-slate-300 leading-relaxed\\">Commerce Pro est un moteur de boutique moderne conçu pour NextBlock. Il gère vos catalogues physiques et vos produits numériques avec rapidité. Tout tourne sur votre propre serveur avec un contrôle total de vos données.</p>"}}],[{"block_type":"text","content":{"html_content":"<div class=\\"rounded-2xl border border-emerald-700 bg-slate-950 p-6 shadow-xl sm:p-8\\"><h3 class=\\"text-lg font-bold text-white mb-4\\">Pourquoi choisir Commerce Pro</h3><ul class=\\"space-y-3 text-sm text-slate-300\\"><li class=\\"flex items-start gap-3\\"><span class=\\"text-emerald-400 font-bold\\">✓</span><span><strong>Multi-Devises</strong> — Taux de change en direct, règles d''arrondis et détection automatique du pays de l''acheteur.</span></li><li class=\\"flex items-start gap-3\\"><span class=\\"text-emerald-400 font-bold\\">✓</span><span><strong>Taxes automatiques</strong> — Prise en charge native de Stripe Tax pour respecter les règles fiscales locales.</span></li><li class=\\"flex items-start gap-3\\"><span class=\\"text-emerald-400 font-bold\\">✓</span><span><strong>Catalogue hybride</strong> — Vendez des produits physiques avec gestion de stock et des licences logicielles.</span></li></ul></div>"}}]],"container_type":"container","responsive_columns":{"mobile":1,"tablet":1,"desktop":2},"vertical_alignment":"center"}'::jsonb,
9896
+ updated_at = NOW()
9897
+ WHERE id = 39;
9898
+
9899
+ -- Block ID 41
9900
+ UPDATE public.blocks
9901
+ SET content = '{"padding":{"top":"lg","bottom":"lg"},"background":{"type":"solid","color":"transparent"},"column_gap":"lg","column_blocks":[[{"block_type":"text","content":{"html_content":"<div class=\\"h-full rounded-2xl border border-slate-200 bg-white p-6 shadow-sm transition-colors hover:border-emerald-300 dark:border-slate-800 dark:bg-slate-900\\"><div class=\\"mb-4 flex h-10 w-10 items-center justify-center rounded-xl bg-emerald-50 text-xl dark:bg-emerald-950\\">💳</div><h4 class=\\"text-lg font-bold text-foreground mb-2\\">Checkout Stripe Headless</h4><p class=\\"text-sm text-muted-foreground leading-relaxed\\">Paiement direct et sécurisé pour un ou plusieurs articles. Les commandes se valident automatiquement grâce aux webhooks Stripe.</p></div>"}}],[{"block_type":"text","content":{"html_content":"<div class=\\"h-full rounded-2xl border border-slate-200 bg-white p-6 shadow-sm transition-colors hover:border-emerald-300 dark:border-slate-800 dark:bg-slate-900\\"><div class=\\"mb-4 flex h-10 w-10 items-center justify-center rounded-xl bg-emerald-50 text-xl dark:bg-emerald-950\\">🔑</div><h4 class=\\"text-lg font-bold text-foreground mb-2\\">Licences Numériques Freemius</h4><p class=\\"text-sm text-muted-foreground leading-relaxed\\">Distribuez vos logiciels, activez vos clés et gérez les périodes d''essai SaaS. Tout fonctionne directement avec Freemius sans souci.</p></div>"}}],[{"block_type":"text","content":{"html_content":"<div class=\\"h-full rounded-2xl border border-slate-200 bg-white p-6 shadow-sm transition-colors hover:border-emerald-300 dark:border-slate-800 dark:bg-slate-900\\"><div class=\\"mb-4 flex h-10 w-10 items-center justify-center rounded-xl bg-emerald-50 text-xl dark:bg-emerald-950\\">🛍️</div><h4 class=\\"text-lg font-bold text-foreground mb-2\\">Merchandising Visuel</h4><p class=\\"text-sm text-muted-foreground leading-relaxed\\">Concevez vos fiches produits, mettez en valeur vos promotions et créez vos rayons directement dans l''éditeur de blocs.</p></div>"}}]],"container_type":"container","responsive_columns":{"mobile":1,"tablet":2,"desktop":3},"vertical_alignment":"stretch"}'::jsonb,
9902
+ updated_at = NOW()
9903
+ WHERE id = 41;
9904
+
9905
+ -- Block ID 42
9906
+ UPDATE public.blocks
9907
+ SET content = '{"padding":{"top":"xl","bottom":"xl"},"background":{"type":"gradient","gradient":{"type":"linear","stops":[{"color":"#0f172a","position":0},{"color":"#064e3b","position":100}],"direction":"135deg"}},"column_gap":"xl","column_blocks":[[{"block_type":"text","content":{"html_content":"<p class=\\"text-xs uppercase tracking-[0.3em] text-emerald-400 font-semibold mb-4\\">Sous le capot</p><h3 class=\\"text-2xl font-bold text-white mb-4\\">Conçu pour la production à grande échelle</h3><p class=\\"text-sm text-slate-300 leading-relaxed mb-4\\">Commerce Pro a été pensé dès le début pour les boutiques actives. Chaque route API est mise en cache, et les requêtes SQL sont optimisées.</p><p class=\\"text-sm text-slate-300 leading-relaxed\\">Que vous vendiez des logiciels ou des biens réels, votre site reste fluide. Il absorbe les pics de trafic sans ralentissement.</p>"}}],[{"block_type":"text","content":{"html_content":"<div class=\\"space-y-4\\"><div class=\\"p-5 rounded-xl border border-slate-700 bg-slate-900\\"><h4 class=\\"text-sm font-bold text-white mb-1\\">Gestion des commandes</h4><p class=\\"text-xs text-slate-400 leading-relaxed\\">Suivez chaque commande du panier à la livraison. Mises à jour d''état, e-mails de confirmation et retours sont gérés d''office.</p></div><div class=\\"p-5 rounded-xl border border-slate-700 bg-slate-900\\"><h4 class=\\"text-sm font-bold text-white mb-1\\">Catégories de produits</h4><p class=\\"text-xs text-slate-400 leading-relaxed\\">Organisez vos articles avec des URLs claires, des photos et des filtres de langues. Pilotez tout depuis le tableau de bord du CMS.</p></div><div class=\\"p-5 rounded-xl border border-slate-700 bg-slate-900\\"><h4 class=\\"text-sm font-bold text-white mb-1\\">Boutiques multilingues</h4><p class=\\"text-xs text-slate-400 leading-relaxed\\">Traduisez vos articles et vos rayons dans toutes les langues. Les stocks sont partagés tout en gardant des métadonnées SEO propres.</p></div></div>"}}]],"container_type":"container","responsive_columns":{"mobile":1,"tablet":1,"desktop":2},"vertical_alignment":"center"}'::jsonb,
9908
+ updated_at = NOW()
9909
+ WHERE id = 42;
9910
+
9911
+ -- Block ID 43
9912
+ UPDATE public.blocks
9913
+ SET content = '{"padding":{"top":"xl","bottom":"xl"},"background":{"type":"gradient","gradient":{"type":"linear","stops":[{"color":"#064e3b","position":0},{"color":"#022c22","position":100}],"direction":"135deg"}},"column_gap":"none","column_blocks":[[{"block_type":"heading","content":{"level":2,"textAlign":"center","textColor":"background","text_content":"Prêt à lancer votre boutique ?"}},{"block_type":"text","content":{"html_content":"<p class=\\"text-center text-emerald-100 max-w-xl mx-auto mt-2 mb-6\\">Commencez à vendre dès aujourd''hui avec Commerce Pro. Multi-devises, conforme aux taxes et rapide dès l''installation.</p>"}},{"block_type":"button","content":{"url":"https://nextblock.dev/product/nextblock-commerce-pro-commerce-license","size":"lg","text":"Acheter Commerce Pro","variant":"secondary","position":"center"}}]],"container_type":"container","responsive_columns":{"mobile":1,"tablet":1,"desktop":1},"vertical_alignment":"center"}'::jsonb,
9914
+ updated_at = NOW()
9915
+ WHERE id = 43;
9916
+
9917
+ -- Block ID 49
9918
+ UPDATE public.blocks
9919
+ SET content = '{"padding":{"top":"xl","bottom":"xl"},"background":{"type":"gradient","gradient":{"type":"linear","stops":[{"color":"#1e1b4b","position":0},{"color":"#0f172a","position":100}],"direction":"135deg"}},"column_gap":"xl","column_blocks":[[{"block_type":"text","content":{"html_content":"<p class=\\"text-xs uppercase tracking-[0.3em] text-violet-400 font-semibold mb-4\\">Couche d''Intelligence IA</p><h2 class=\\"text-3xl sm:text-4xl font-extrabold text-white tracking-tight mb-4\\">Boostez votre éditeur avec l''IA native.</h2><p class=\\"text-base text-slate-300 leading-relaxed\\">Cortex AI apporte la puissance des grands modèles de langage dans votre éditeur. Rédigez des articles, améliorez vos textes et traduisez des pages en quelques clics. Les blocs créés restent stables, rapides et fidèles à votre charte.</p>"}}],[{"block_type":"text","content":{"html_content":"<div class=\\"rounded-2xl border border-violet-700 bg-slate-950 p-6 shadow-xl sm:p-8\\"><h3 class=\\"text-lg font-bold text-white mb-4\\">OpenRouter et architecture BYOK</h3><ul class=\\"space-y-3 text-sm text-slate-300\\"><li class=\\"flex items-start gap-3\\"><span class=\\"text-violet-400 font-bold\\">✓</span><span><strong>Apportez votre clé API</strong> — Maîtrisez vos coûts avec vos propres clés OpenRouter. Aucun frais caché.</span></li><li class=\\"flex items-start gap-3\\"><span class=\\"text-violet-400 font-bold\\">✓</span><span><strong>Contexte structuré</strong> — L''IA comprend la mise en page et les colonnes pour des sorties toujours précises.</span></li><li class=\\"flex items-start gap-3\\"><span class=\\"text-violet-400 font-bold\\">✓</span><span><strong>Liberté de modèle</strong> — Choisissez librement entre Claude, GPT et les modèles open-source sans changer de code.</span></li></ul></div>"}}]],"container_type":"container","responsive_columns":{"mobile":1,"tablet":1,"desktop":2},"vertical_alignment":"center"}'::jsonb,
9920
+ updated_at = NOW()
9921
+ WHERE id = 49;
9922
+
9923
+ -- Block ID 51
9924
+ UPDATE public.blocks
9925
+ SET content = '{"padding":{"top":"lg","bottom":"lg"},"background":{"type":"solid","color":"transparent"},"column_gap":"lg","column_blocks":[[{"block_type":"text","content":{"html_content":"<div class=\\"h-full rounded-2xl border border-slate-200 bg-white p-6 shadow-sm transition-colors hover:border-violet-300 dark:border-slate-800 dark:bg-slate-900\\"><div class=\\"mb-4 flex h-10 w-10 items-center justify-center rounded-xl bg-violet-50 text-xl dark:bg-violet-950\\">✍️</div><h4 class=\\"text-lg font-bold text-foreground mb-2\\">Rédaction en un clic</h4><p class=\\"text-sm text-muted-foreground leading-relaxed\\">Rédigez des guides, ajustez vos titres et créez des appels à l''action. Cortex AI adopte votre ton et écrit un texte clair.</p></div>"}}],[{"block_type":"text","content":{"html_content":"<div class=\\"h-full rounded-2xl border border-slate-200 bg-white p-6 shadow-sm transition-colors hover:border-violet-300 dark:border-slate-800 dark:bg-slate-900\\"><div class=\\"mb-4 flex h-10 w-10 items-center justify-center rounded-xl bg-violet-50 text-xl dark:bg-violet-950\\">🔄</div><h4 class=\\"text-lg font-bold text-foreground mb-2\\">Refactorisation de structure</h4><p class=\\"text-sm text-muted-foreground leading-relaxed\\">Ajustez vos colonnes, ajoutez des grilles et réorganisez vos sections. Cortex AI génère des données valides sans effort.</p></div>"}}],[{"block_type":"text","content":{"html_content":"<div class=\\"h-full rounded-2xl border border-slate-200 bg-white p-6 shadow-sm transition-colors hover:border-violet-300 dark:border-slate-800 dark:bg-slate-900\\"><div class=\\"mb-4 flex h-10 w-10 items-center justify-center rounded-xl bg-violet-50 text-xl dark:bg-violet-950\\">🌐</div><h4 class=\\"text-lg font-bold text-foreground mb-2\\">Traduction automatique</h4><p class=\\"text-sm text-muted-foreground leading-relaxed\\">Traduisez vos pages complètes entre le français et l''anglais. Cortex AI conserve vos mises en page et styles intacts.</p></div>"}}]],"container_type":"container","responsive_columns":{"mobile":1,"tablet":2,"desktop":3},"vertical_alignment":"stretch"}'::jsonb,
9926
+ updated_at = NOW()
9927
+ WHERE id = 51;
9928
+
9929
+ -- Block ID 52
9930
+ UPDATE public.blocks
9931
+ SET content = '{"padding":{"top":"xl","bottom":"xl"},"background":{"type":"gradient","gradient":{"type":"linear","stops":[{"color":"#0f172a","position":0},{"color":"#1e1b4b","position":100}],"direction":"135deg"}},"column_gap":"xl","column_blocks":[[{"block_type":"text","content":{"html_content":"<p class=\\"text-xs uppercase tracking-[0.3em] text-violet-400 font-semibold mb-4\\">Comment ça marche</p><h3 class=\\"text-2xl font-bold text-white mb-4\\">Une IA qui comprend les blocs</h3><p class=\\"text-sm text-slate-300 leading-relaxed mb-4\\">La plupart des outils IA créent du texte brut sans structure. Cortex AI a été pensé pour l''éditeur de NextBlock. Il analyse vos blocs, vos colonnes et vos options visuelles.</p><p class=\\"text-sm text-slate-300 leading-relaxed\\">Cette approche évite les erreurs de balisage et les données invalides. Votre équipe obtient des rendus élégants sur mobile et ordinateur.</p>"}}],[{"block_type":"text","content":{"html_content":"<div class=\\"space-y-4\\"><div class=\\"p-5 rounded-xl border border-slate-700 bg-slate-900\\"><h4 class=\\"text-sm font-bold text-white mb-1\\">Génération de contenu</h4><p class=\\"text-xs text-slate-400 leading-relaxed\\">Produisez du contenu percutant pour vos pages et articles. L''assistant respecte votre style et délivre des textes fluides.</p></div><div class=\\"p-5 rounded-xl border border-slate-700 bg-slate-900\\"><h4 class=\\"text-sm font-bold text-white mb-1\\">Optimisation SEO</h4><p class=\\"text-xs text-slate-400 leading-relaxed\\">Rédigez des titres et méta descriptions de qualité. Cortex AI vérifie la lisibilité et les mots clés avant publication.</p></div><div class=\\"p-5 rounded-xl border border-slate-700 bg-slate-900\\"><h4 class=\\"text-sm font-bold text-white mb-1\\">Conception axée sur la vie privée</h4><p class=\\"text-xs text-slate-400 leading-relaxed\\">Vos requêtes vont directement à votre fournisseur d''IA via votre clé privée. Vos données ne sont jamais stockées ni revendues.</p></div></div>"}}]],"container_type":"container","responsive_columns":{"mobile":1,"tablet":1,"desktop":2},"vertical_alignment":"center"}'::jsonb,
9932
+ updated_at = NOW()
9933
+ WHERE id = 52;
9934
+
9935
+ -- Block ID 53
9936
+ UPDATE public.blocks
9937
+ SET content = '{"padding":{"top":"xl","bottom":"xl"},"background":{"type":"gradient","gradient":{"type":"linear","stops":[{"color":"#312e81","position":0},{"color":"#1e1b4b","position":100}],"direction":"135deg"}},"column_gap":"none","column_blocks":[[{"block_type":"heading","content":{"level":2,"textAlign":"center","textColor":"background","text_content":"Prêt à ajouter l''IA à votre éditeur ?"}},{"block_type":"text","content":{"html_content":"<p class=\\"text-center text-violet-100 max-w-xl mx-auto mt-2 mb-6\\">Libérez la puissance de la création de contenu avec l''IA. Connectez votre clé, protégez vos données et rédigez vite.</p>"}},{"block_type":"button","content":{"url":"https://nextblock.dev/product/nextblock-cortex-ai-cortex-ai-license","size":"lg","text":"Acheter Cortex AI","variant":"secondary","position":"center"}}]],"container_type":"container","responsive_columns":{"mobile":1,"tablet":1,"desktop":1},"vertical_alignment":"center"}'::jsonb,
9938
+ updated_at = NOW()
9939
+ WHERE id = 53;
9940
+
9941
+ -- Block ID 65
9942
+ UPDATE public.blocks
9943
+ SET content = '{"padding":{"top":"xl","bottom":"xl"},"background":{"type":"gradient","gradient":{"type":"linear","stops":[{"color":"#064e3b","position":0},{"color":"#0f172a","position":100}],"direction":"135deg"}},"column_gap":"xl","column_blocks":[[{"block_type":"text","content":{"html_content":"<p class=\\"text-xs uppercase tracking-[0.3em] text-emerald-400 font-semibold mb-4\\">Enterprise E-Commerce Engine</p><h2 class=\\"text-3xl sm:text-4xl font-extrabold text-white tracking-tight mb-4\\">Turn Next.js Into a Global Storefront.</h2><p class=\\"text-base text-slate-300 leading-relaxed\\">Commerce Pro is a fast store engine built for NextBlock. It helps teams sell physical goods and digital downloads with ease. Everything runs on your own server with total data ownership and zero extra fees.</p>"}}],[{"block_type":"text","content":{"html_content":"<div class=\\"rounded-2xl border border-emerald-700 bg-slate-950 p-6 shadow-xl sm:p-8\\"><h3 class=\\"text-lg font-bold text-white mb-4\\">Why Teams Choose Commerce Pro</h3><ul class=\\"space-y-3 text-sm text-slate-300\\"><li class=\\"flex items-start gap-3\\"><span class=\\"text-emerald-400 font-bold\\">✓</span><span><strong>Multi-Currency Pricing</strong> — Live exchange rates, clear charm pricing, and automatic visitor country detection.</span></li><li class=\\"flex items-start gap-3\\"><span class=\\"text-emerald-400 font-bold\\">✓</span><span><strong>Simple Tax Rules</strong> — Native Stripe Tax support calculates local taxes automatically during checkout.</span></li><li class=\\"flex items-start gap-3\\"><span class=\\"text-emerald-400 font-bold\\">✓</span><span><strong>Hybrid Product Catalog</strong> — Sell physical goods with stock counts alongside digital software license keys.</span></li></ul></div>"}}]],"container_type":"container","responsive_columns":{"mobile":1,"tablet":1,"desktop":2},"vertical_alignment":"center"}'::jsonb,
9944
+ updated_at = NOW()
9945
+ WHERE id = 65;
9946
+
9947
+ -- Block ID 67
9948
+ UPDATE public.blocks
9949
+ SET content = '{"padding":{"top":"lg","bottom":"lg"},"background":{"type":"solid","color":"transparent"},"column_gap":"lg","column_blocks":[[{"block_type":"text","content":{"html_content":"<div class=\\"h-full rounded-2xl border border-slate-200 bg-white p-6 shadow-sm transition-colors hover:border-emerald-300 dark:border-slate-800 dark:bg-slate-900\\"><div class=\\"mb-4 flex h-10 w-10 items-center justify-center rounded-xl bg-emerald-50 text-xl dark:bg-emerald-950\\">💳</div><h4 class=\\"text-lg font-bold text-foreground mb-2\\">Headless Stripe Checkout</h4><p class=\\"text-sm text-muted-foreground leading-relaxed\\">Fast checkout flows for single or multiple items. Orders process automatically through secure webhooks with zero friction.</p></div>"}}],[{"block_type":"text","content":{"html_content":"<div class=\\"h-full rounded-2xl border border-slate-200 bg-white p-6 shadow-sm transition-colors hover:border-emerald-300 dark:border-slate-800 dark:bg-slate-900\\"><div class=\\"mb-4 flex h-10 w-10 items-center justify-center rounded-xl bg-emerald-50 text-xl dark:bg-emerald-950\\">🔑</div><h4 class=\\"text-lg font-bold text-foreground mb-2\\">Freemius Digital Licensing</h4><p class=\\"text-sm text-muted-foreground leading-relaxed\\">Deliver software files, issue license keys, and manage SaaS free trials easily. Everything connects directly to Freemius.</p></div>"}}],[{"block_type":"text","content":{"html_content":"<div class=\\"h-full rounded-2xl border border-slate-200 bg-white p-6 shadow-sm transition-colors hover:border-emerald-300 dark:border-slate-800 dark:bg-slate-900\\"><div class=\\"mb-4 flex h-10 w-10 items-center justify-center rounded-xl bg-emerald-50 text-xl dark:bg-emerald-950\\">🛍️</div><h4 class=\\"text-lg font-bold text-foreground mb-2\\">Visual Merchandising</h4><p class=\\"text-sm text-muted-foreground leading-relaxed\\">Design custom product pages, highlight special deals, and build shopping grids right inside the visual block editor.</p></div>"}}]],"container_type":"container","responsive_columns":{"mobile":1,"tablet":2,"desktop":3},"vertical_alignment":"stretch"}'::jsonb,
9950
+ updated_at = NOW()
9951
+ WHERE id = 67;
9952
+
9953
+ -- Block ID 68
9954
+ UPDATE public.blocks
9955
+ SET content = '{"padding":{"top":"xl","bottom":"xl"},"background":{"type":"gradient","gradient":{"type":"linear","stops":[{"color":"#0f172a","position":0},{"color":"#064e3b","position":100}],"direction":"135deg"}},"column_gap":"xl","column_blocks":[[{"block_type":"text","content":{"html_content":"<p class=\\"text-xs uppercase tracking-[0.3em] text-emerald-400 font-semibold mb-4\\">Under The Hood</p><h3 class=\\"text-2xl font-bold text-white mb-4\\">Built for Production Scale</h3><p class=\\"text-sm text-slate-300 leading-relaxed mb-4\\">Commerce Pro was built from day one for busy storefronts. Every API path is cached near your users, and database queries use fast indexes.</p><p class=\\"text-sm text-slate-300 leading-relaxed\\">Whether you sell digital downloads or retail goods, your storefront loads fast. It handles high traffic spikes without slow downs or crashes.</p>"}}],[{"block_type":"text","content":{"html_content":"<div class=\\"space-y-4\\"><div class=\\"p-5 rounded-xl border border-slate-700 bg-slate-900\\"><h4 class=\\"text-sm font-bold text-white mb-1\\">Order Management</h4><p class=\\"text-xs text-slate-400 leading-relaxed\\">Track customer orders from cart to shipping. Automatic status updates, buyer receipts, and refunds work right out of the box.</p></div><div class=\\"p-5 rounded-xl border border-slate-700 bg-slate-900\\"><h4 class=\\"text-sm font-bold text-white mb-1\\">Product Categories</h4><p class=\\"text-xs text-slate-400 leading-relaxed\\">Organize products with clean web addresses, category images, and full multi-language tags. Manage it all from the CMS dashboard.</p></div><div class=\\"p-5 rounded-xl border border-slate-700 bg-slate-900\\"><h4 class=\\"text-sm font-bold text-white mb-1\\">Multi-Language Storefronts</h4><p class=\\"text-xs text-slate-400 leading-relaxed\\">Translate products and category pages into any language. Each language shares inventory while keeping its own SEO metadata.</p></div></div>"}}]],"container_type":"container","responsive_columns":{"mobile":1,"tablet":1,"desktop":2},"vertical_alignment":"center"}'::jsonb,
9956
+ updated_at = NOW()
9957
+ WHERE id = 68;
9958
+
9959
+ -- Block ID 69
9960
+ UPDATE public.blocks
9961
+ SET content = '{"padding":{"top":"xl","bottom":"xl"},"background":{"type":"gradient","gradient":{"type":"linear","stops":[{"color":"#064e3b","position":0},{"color":"#022c22","position":100}],"direction":"135deg"}},"column_gap":"none","column_blocks":[[{"block_type":"heading","content":{"level":2,"textAlign":"center","textColor":"background","text_content":"Ready to launch your storefront?"}},{"block_type":"text","content":{"html_content":"<p class=\\"text-center text-emerald-100 max-w-xl mx-auto mt-2 mb-6\\">Start selling today with Commerce Pro. It is multi-currency, tax-compliant, and lightning-fast out of the box.</p>"}},{"block_type":"button","content":{"url":"#","size":"lg","text":"Purchase Commerce Pro","variant":"secondary","position":"center"}}]],"container_type":"container","responsive_columns":{"mobile":1,"tablet":1,"desktop":1},"vertical_alignment":"center"}'::jsonb,
9962
+ updated_at = NOW()
9963
+ WHERE id = 69;
9964
+
9965
+ -- Block ID 70
9966
+ UPDATE public.blocks
9967
+ SET content = '{"padding":{"top":"xl","bottom":"xl"},"background":{"type":"gradient","gradient":{"type":"linear","stops":[{"color":"#1e1b4b","position":0},{"color":"#0f172a","position":100}],"direction":"135deg"}},"column_gap":"xl","column_blocks":[[{"block_type":"text","content":{"html_content":"<p class=\\"text-xs uppercase tracking-[0.3em] text-violet-400 font-semibold mb-4\\">AI Intelligence Layer</p><h2 class=\\"text-3xl sm:text-4xl font-extrabold text-white tracking-tight mb-4\\">Supercharge Your Editor with AI.</h2><p class=\\"text-base text-slate-300 leading-relaxed\\">Cortex AI brings powerful language models directly into your editor. You can create content, edit blog drafts, and translate full pages in seconds. It writes clean block data rather than raw text, keeping your layouts stable and fast.</p>"}}],[{"block_type":"text","content":{"html_content":"<div class=\\"rounded-2xl border border-violet-700 bg-slate-950 p-6 shadow-xl sm:p-8\\"><h3 class=\\"text-lg font-bold text-white mb-4\\">OpenRouter &amp; BYOK Architecture</h3><ul class=\\"space-y-3 text-sm text-slate-300\\"><li class=\\"flex items-start gap-3\\"><span class=\\"text-violet-400 font-bold\\">✓</span><span><strong>Bring Your Own Key</strong> — You control costs using your own OpenRouter tokens. There are no hidden fees or markups.</span></li><li class=\\"flex items-start gap-3\\"><span class=\\"text-violet-400 font-bold\\">✓</span><span><strong>Block-Aware Prompts</strong> — Models receive structured context. They see headings, columns, and layout rules for accurate outputs.</span></li><li class=\\"flex items-start gap-3\\"><span class=\\"text-violet-400 font-bold\\">✓</span><span><strong>Model Flexibility</strong> — Switch freely between Claude, GPT, and open-source models without changing code.</span></li></ul></div>"}}]],"container_type":"container","responsive_columns":{"mobile":1,"tablet":1,"desktop":2},"vertical_alignment":"center"}'::jsonb,
9968
+ updated_at = NOW()
9969
+ WHERE id = 70;
9970
+
9971
+ -- Block ID 72
9972
+ UPDATE public.blocks
9973
+ SET content = '{"padding":{"top":"lg","bottom":"lg"},"background":{"type":"solid","color":"transparent"},"column_gap":"lg","column_blocks":[[{"block_type":"text","content":{"html_content":"<div class=\\"h-full rounded-2xl border border-slate-200 bg-white p-6 shadow-sm transition-colors hover:border-violet-300 dark:border-slate-800 dark:bg-slate-900\\"><div class=\\"mb-4 flex h-10 w-10 items-center justify-center rounded-xl bg-violet-50 text-xl dark:bg-violet-950\\">✍️</div><h4 class=\\"text-lg font-bold text-foreground mb-2\\">One-Click Copywriting</h4><p class=\\"text-sm text-muted-foreground leading-relaxed\\">Write blog posts, polish headlines, and craft calls to action. Cortex AI matches your brand voice and writes clear prose with ease.</p></div>"}}],[{"block_type":"text","content":{"html_content":"<div class=\\"h-full rounded-2xl border border-slate-200 bg-white p-6 shadow-sm transition-colors hover:border-violet-300 dark:border-slate-800 dark:bg-slate-900\\"><div class=\\"mb-4 flex h-10 w-10 items-center justify-center rounded-xl bg-violet-50 text-xl dark:bg-violet-950\\">🔄</div><h4 class=\\"text-lg font-bold text-foreground mb-2\\">Structure Refactoring</h4><p class=\\"text-sm text-muted-foreground leading-relaxed\\">Reorganize layout grids, split columns, and reorder sections. Cortex AI produces valid block schemas that render safely every time.</p></div>"}}],[{"block_type":"text","content":{"html_content":"<div class=\\"h-full rounded-2xl border border-slate-200 bg-white p-6 shadow-sm transition-colors hover:border-violet-300 dark:border-slate-800 dark:bg-slate-900\\"><div class=\\"mb-4 flex h-10 w-10 items-center justify-center rounded-xl bg-violet-50 text-xl dark:bg-violet-950\\">🌐</div><h4 class=\\"text-lg font-bold text-foreground mb-2\\">Automated Translation</h4><p class=\\"text-sm text-muted-foreground leading-relaxed\\">Translate entire pages between languages with one click. Cortex AI keeps your styles, nested sections, and media links intact.</p></div>"}}]],"container_type":"container","responsive_columns":{"mobile":1,"tablet":2,"desktop":3},"vertical_alignment":"stretch"}'::jsonb,
9974
+ updated_at = NOW()
9975
+ WHERE id = 72;
9976
+
9977
+ -- Block ID 73
9978
+ UPDATE public.blocks
9979
+ SET content = '{"padding":{"top":"xl","bottom":"xl"},"background":{"type":"gradient","gradient":{"type":"linear","stops":[{"color":"#0f172a","position":0},{"color":"#1e1b4b","position":100}],"direction":"135deg"}},"column_gap":"xl","column_blocks":[[{"block_type":"text","content":{"html_content":"<p class=\\"text-xs uppercase tracking-[0.3em] text-violet-400 font-semibold mb-4\\">How It Works</p><h3 class=\\"text-2xl font-bold text-white mb-4\\">AI That Understands Blocks</h3><p class=\\"text-sm text-slate-300 leading-relaxed mb-4\\">Most AI tools only generate flat text strings. Cortex AI is built specifically for NextBlock editors. It reads your full page structure, column arrays, and style options.</p><p class=\\"text-sm text-slate-300 leading-relaxed\\">This deep connection prevents broken markup and invalid data. Your team gets clean results that look great on desktop and mobile screens right away.</p>"}}],[{"block_type":"text","content":{"html_content":"<div class=\\"space-y-4\\"><div class=\\"p-5 rounded-xl border border-slate-700 bg-slate-900\\"><h4 class=\\"text-sm font-bold text-white mb-1\\">Content Generation</h4><p class=\\"text-xs text-slate-400 leading-relaxed\\">Create engaging copy for marketing pages and help docs. The assistant follows your editorial style guide and produces clean text.</p></div><div class=\\"p-5 rounded-xl border border-slate-700 bg-slate-900\\"><h4 class=\\"text-sm font-bold text-white mb-1\\">SEO Optimization</h4><p class=\\"text-xs text-slate-400 leading-relaxed\\">Draft concise meta titles and meta descriptions automatically. Cortex AI checks keyword density and reading ease before you publish.</p></div><div class=\\"p-5 rounded-xl border border-slate-700 bg-slate-900\\"><h4 class=\\"text-sm font-bold text-white mb-1\\">Privacy-First Design</h4><p class=\\"text-xs text-slate-400 leading-relaxed\\">Your content goes directly to your chosen provider via private API keys. We never store, log, or train on your private data.</p></div></div>"}}]],"container_type":"container","responsive_columns":{"mobile":1,"tablet":1,"desktop":2},"vertical_alignment":"center"}'::jsonb,
9980
+ updated_at = NOW()
9981
+ WHERE id = 73;
9982
+
9983
+ -- Block ID 74
9984
+ UPDATE public.blocks
9985
+ SET content = '{"padding":{"top":"xl","bottom":"xl"},"background":{"type":"gradient","gradient":{"type":"linear","stops":[{"color":"#312e81","position":0},{"color":"#1e1b4b","position":100}],"direction":"135deg"}},"column_gap":"none","column_blocks":[[{"block_type":"heading","content":{"level":2,"textAlign":"center","textColor":"background","text_content":"Ready to add AI to your editor?"}},{"block_type":"text","content":{"html_content":"<p class=\\"text-center text-violet-100 max-w-xl mx-auto mt-2 mb-6\\">Unlock the full power of AI-driven content creation. Connect your key, protect your privacy, and edit faster today.</p>"}},{"block_type":"button","content":{"url":"#","size":"lg","text":"Purchase Cortex AI","variant":"secondary","position":"center"}}]],"container_type":"container","responsive_columns":{"mobile":1,"tablet":1,"desktop":1},"vertical_alignment":"center"}'::jsonb,
9986
+ updated_at = NOW()
9987
+ WHERE id = 74;
9988
+
9989
+
9990
+
9991
+ -- >>> FROM: 00000000000036_seed_posts_missing_seo_metadata.sql <<<
9992
+ -- Migration: 00000000000036_seed_posts_missing_seo_metadata.sql
9993
+ -- Purpose: Set meta_title and meta_description for remaining seeded blog posts
9994
+ -- (how-nextblock-works, comment-nextblock-fonctionne, nextblock-commerce-guide, guide-commerce-nextblock)
9995
+ -- so all 9 seeded posts achieve a 100/100 SEO score.
9996
+ -- Safety: Append-only, forward-only, content-guarded updates.
9997
+
9998
+
9999
+ UPDATE public.posts
10000
+ SET meta_title = 'How NextBlock™ Works: Architecture & Under the Hood',
10001
+ meta_description = 'Explore the architecture behind NextBlock: monorepo packages, typed block registry, Supabase database, and modern Next.js editor stack.',
10002
+ updated_at = NOW()
10003
+ WHERE slug = 'how-nextblock-works'
10004
+ AND meta_title IS NULL;
10005
+
10006
+ UPDATE public.posts
10007
+ SET meta_title = 'Comment fonctionne NextBlock™ : Architecture et Moteur',
10008
+ meta_description = 'Découvrez les fondations de NextBlock : monorepo, registre de blocs typés, base de données Supabase et moteur d''édition Next.js moderne.',
10009
+ updated_at = NOW()
10010
+ WHERE slug = 'comment-nextblock-fonctionne'
10011
+ AND meta_title IS NULL;
10012
+
10013
+ UPDATE public.posts
10014
+ SET meta_title = 'The Complete NextBlock™ Commerce Guide | Headless Store',
10015
+ meta_description = 'Build global e-commerce with NextBlock: manage products, configure multi-currency pricing, and accept payments with headless Stripe checkout.',
10016
+ updated_at = NOW()
10017
+ WHERE slug = 'nextblock-commerce-guide'
10018
+ AND meta_title IS NULL;
10019
+
10020
+ UPDATE public.posts
10021
+ SET meta_title = 'Guide Complet du Commerce avec NextBlock™ | E-Commerce',
10022
+ meta_description = 'Développez votre boutique avec NextBlock : gestion de produits, tarifs multi-devises et tunnel de paiement headless avec Stripe Checkout.',
10023
+ updated_at = NOW()
10024
+ WHERE slug = 'guide-commerce-nextblock'
10025
+ AND meta_title IS NULL;
10026
+
10027
+
10028
+
9856
10029
  -- Step D: Record the applied migrations in history (truncated in Step B) so
9857
10030
  -- \`npm run db:migrate:check\` reports up to date instead of listing every file as pending.
9858
10031
  INSERT INTO supabase_migrations.schema_migrations (version, name) VALUES
@@ -9890,7 +10063,9 @@ END $$;
9890
10063
  ('00000000000031', 'seo_robots_settings_admin_only'),
9891
10064
  ('00000000000032', 'seed_seo_score_optimizations'),
9892
10065
  ('00000000000033', 'seed_seo_score_optimizations_p2'),
9893
- ('00000000000034', 'seed_seo_french_legal_readability')
10066
+ ('00000000000034', 'seed_seo_french_legal_readability'),
10067
+ ('00000000000035', 'digital_products_seo_optimizations'),
10068
+ ('00000000000036', 'seed_posts_missing_seo_metadata')
9894
10069
  ON CONFLICT (version) DO NOTHING;
9895
10070
 
9896
10071
  -- Step E: Anchor preserved profiles
@@ -1,40 +1,3 @@
1
- import React from "react";
2
- import { Badge } from "@nextblock-cms/ui";
3
-
4
- interface SeoScoreBadgeProps {
5
- score: number;
6
- className?: string;
7
- }
8
-
9
- export default function SeoScoreBadge({ score, className = "" }: SeoScoreBadgeProps) {
10
- const normalizedScore = Math.max(0, Math.min(100, Math.round(score)));
11
-
12
- let colorClass = "";
13
- let dotClass = "";
14
- let title = "";
15
-
16
- if (normalizedScore >= 90) {
17
- colorClass = "bg-emerald-100 text-emerald-800 border-emerald-200 dark:bg-emerald-950/50 dark:text-emerald-300 dark:border-emerald-800/60";
18
- dotClass = "bg-emerald-500";
19
- title = `SEO Score: ${normalizedScore}% — Optimal`;
20
- } else if (normalizedScore >= 70) {
21
- colorClass = "bg-amber-100 text-amber-800 border-amber-200 dark:bg-amber-950/50 dark:text-amber-300 dark:border-amber-800/60";
22
- dotClass = "bg-amber-500";
23
- title = `SEO Score: ${normalizedScore}% — Good (Minor warnings)`;
24
- } else {
25
- colorClass = "bg-rose-100 text-rose-800 border-rose-200 dark:bg-rose-950/50 dark:text-rose-300 dark:border-rose-800/60";
26
- dotClass = "bg-rose-500";
27
- title = `SEO Score: ${normalizedScore}% — Needs improvement`;
28
- }
29
-
30
- return (
31
- <Badge
32
- variant="outline"
33
- title={title}
34
- className={`inline-flex items-center px-2 py-0.5 text-xs font-semibold rounded-full border transition-colors ${colorClass} ${className}`}
35
- >
36
- <span className={`w-1.5 h-1.5 rounded-full mr-1.5 shrink-0 ${dotClass}`} />
37
- {normalizedScore}%
38
- </Badge>
39
- );
40
- }
1
+ import { SeoScoreBadge } from "@nextblock-cms/ui";
2
+ export { SeoScoreBadge, type SeoScoreBadgeProps } from "@nextblock-cms/ui";
3
+ export default SeoScoreBadge;
@@ -4,6 +4,7 @@ import React from 'react';
4
4
  import { ProductForm } from '@nextblock-cms/ecommerce';
5
5
  import MediaPickerDialog from '../media/components/MediaPickerDialog';
6
6
  import DraftStatusActions from '../components/DraftStatusActions';
7
+ import { usePageSeo } from '../../../lib/seo/page-audit-context';
7
8
 
8
9
  type ProductFormProps = React.ComponentProps<typeof ProductForm>;
9
10
  type ProductUpdateAction = NonNullable<ProductFormProps['updateAction']>;
@@ -24,6 +25,23 @@ export default function ProductFormClientShell({
24
25
  ...props
25
26
  }: ProductFormClientShellProps) {
26
27
  const [isMounted, setIsMounted] = React.useState(false);
28
+ const pageSeo = usePageSeo();
29
+
30
+ const handleSeoChange = React.useCallback(
31
+ (values: { title?: string | null; meta_title?: string | null; meta_description?: string | null }) => {
32
+ if (pageSeo) {
33
+ if (values.title !== undefined) {
34
+ pageSeo.setDocumentTitle(values.title || null);
35
+ }
36
+ pageSeo.setMeta({
37
+ metaTitle: values.meta_title || null,
38
+ metaDescription: values.meta_description || null,
39
+ });
40
+ }
41
+ },
42
+ [pageSeo]
43
+ );
44
+
27
45
  // The draft toolbar is gated on the server-computed `hasDraft`, but the form
28
46
  // autosave writes a draft WITHOUT revalidating the route (revalidating would
29
47
  // re-init the form and loop the autosave — see updateProductAction). Track
@@ -64,6 +82,7 @@ export default function ProductFormClientShell({
64
82
  <ProductForm
65
83
  {...props}
66
84
  hasOpenDraft={hasDraft}
85
+ onSeoChange={handleSeoChange}
67
86
  updateAction={updateAction ? wrappedUpdateAction : undefined}
68
87
  mediaPickerNode={
69
88
  <MediaPickerDialog
@@ -35,6 +35,8 @@ import BlockEditorArea from '../../../blocks/components/BlockEditorArea';
35
35
  import VisibilityControl from '../../../components/VisibilityControl';
36
36
  import RevisionHistoryButton from '../../../revisions/RevisionHistoryButton';
37
37
  import { buildViewUrl } from '../../../../../lib/publishing/viewUrl';
38
+ import { PageSeoProvider } from '../../../../../lib/seo/page-audit-context';
39
+ import { PageSeoAuditSection } from '../../../../../components/seo/PageSeoAuditSection';
38
40
 
39
41
  export default async function EditProductPage({
40
42
  params,
@@ -211,8 +213,15 @@ export default async function EditProductPage({
211
213
  }
212
214
 
213
215
  return (
214
- <div className="space-y-8 w-full max-w-[1400px] mx-auto px-6 py-8">
215
- <CortexAiPageContextRegistrar
216
+ <PageSeoProvider
217
+ documentTitle={product.title}
218
+ documentType="product"
219
+ initialBlocks={descriptionBlocks}
220
+ initialMetaDescription={product.meta_description}
221
+ initialMetaTitle={product.meta_title}
222
+ >
223
+ <div className="space-y-8 w-full max-w-[1400px] mx-auto px-6 py-8">
224
+ <CortexAiPageContextRegistrar
216
225
  context={{
217
226
  contentType: 'product',
218
227
  entityId: product.id,
@@ -356,6 +365,8 @@ export default async function EditProductPage({
356
365
  availableCategoriesProp={allCategories}
357
366
  />
358
367
 
368
+ <PageSeoAuditSection className="my-6" />
369
+
359
370
  <div className="border-t pt-8">
360
371
  <h2 className="text-xl font-bold mb-4">Product Description Blocks</h2>
361
372
  <BlockEditorArea
@@ -366,5 +377,6 @@ export default async function EditProductPage({
366
377
  />
367
378
  </div>
368
379
  </div>
380
+ </PageSeoProvider>
369
381
  );
370
382
  }
@@ -54,8 +54,8 @@ export interface PageSeoSnapshot {
54
54
  blocks: unknown[];
55
55
  /** Document title for articles / posts where the template renders an H1. */
56
56
  documentTitle: string | null;
57
- /** Whether this is a standalone 'page' or an editorial 'post'. */
58
- documentType: 'page' | 'post';
57
+ /** Whether this is a standalone 'page', an editorial 'post', or a store 'product'. */
58
+ documentType: 'page' | 'post' | 'product';
59
59
  metaDescription: string | null;
60
60
  metaTitle: string | null;
61
61
  }
@@ -75,7 +75,7 @@ const PageSeoContext = createContext<PageSeoContextValue | null>(null);
75
75
  export interface PageSeoProviderProps {
76
76
  children: ReactNode;
77
77
  documentTitle?: string | null;
78
- documentType?: 'page' | 'post';
78
+ documentType?: 'page' | 'post' | 'product';
79
79
  initialBlocks?: unknown[];
80
80
  initialMetaDescription?: string | null;
81
81
  initialMetaTitle?: string | null;
@@ -380,5 +380,39 @@ describe('buildPageSeoDocument feeding the page-level audit', () => {
380
380
  expect(doc.headings).toEqual([{ level: 1, order: 0, text: 'Initial Draft Post' }]);
381
381
  expect(doc.text).toBe('Initial Draft Post');
382
382
  });
383
+
384
+ it('prepends product title as H1 when documentType is product', () => {
385
+ const blocks = [
386
+ {
387
+ block_type: 'section',
388
+ content: {
389
+ column_blocks: [
390
+ [
391
+ {
392
+ block_type: 'heading',
393
+ content: { level: 2, text_content: 'Features' },
394
+ },
395
+ ],
396
+ ],
397
+ },
398
+ },
399
+ ];
400
+
401
+ const withProduct = buildPageSeoDocument(blocks, {
402
+ documentTitle: 'NextBlock Commerce Pro',
403
+ documentType: 'product',
404
+ });
405
+
406
+ expect(withProduct.headings).toEqual([
407
+ { level: 1, order: 0, text: 'NextBlock Commerce Pro' },
408
+ { level: 2, order: 1, text: 'Features' },
409
+ ]);
410
+ expect(withProduct.words).toContain('commerce');
411
+ expect(withProduct.words).toContain('pro');
412
+
413
+ const audit = auditSeo({ document: withProduct });
414
+ expect(audit.issues.map((i) => i.id)).not.toContain('headings-missing-h1');
415
+ });
383
416
  });
384
417
 
418
+
@@ -1,463 +1,4 @@
1
- import {
2
- buildSeoDocument,
3
- emptySeoDocument,
4
- isExternalHref,
5
- tokenizeWords,
6
- type SeoDocument,
7
- type SeoHeading,
8
- type SeoHeadingLevel,
9
- type SeoImage,
10
- type SeoLink,
11
- } from '@nextblock-cms/utils/seo';
12
-
13
- /**
14
- * Flattens a page's blocks into the single `SeoDocument` the audit grades.
15
- *
16
- * The SEO engine in `@nextblock-cms/utils/seo` was written against one document
17
- * that represents one page, but the CMS does not store a page as one document:
18
- * it stores a list of block rows, each holding its own slice of content in its
19
- * own differently-named field, with most of a real page's copy nested two
20
- * levels down inside a `section` block's columns. Auditing those rows one at a
21
- * time is what produced the finding this module exists to retire — "this page
22
- * has no H1 heading" reported against a single paragraph, when the H1 was sitting
23
- * in a `heading` block two rows above and the auditor was never shown it.
24
- *
25
- * So this walks the whole list in document order and merges everything into one
26
- * document, which lets `auditSeo` answer the questions it was designed to
27
- * answer — is there exactly one H1, is there enough copy, where does the
28
- * keyphrase fall — about the page a visitor actually reads.
29
- *
30
- * Two properties are load-bearing and every change here has to preserve them:
31
- *
32
- * - **Nothing throws.** `content` is `Json` straight out of Postgres, and the
33
- * editor hands us half-typed state on every keystroke, so a field can be
34
- * null, a number, an array where an object was expected, or absent. Every
35
- * read below tolerates that and skips rather than reporting. An unrecognised
36
- * block — a custom block, whose content shape is whatever its author defined
37
- * — is skipped for the same reason: guessing at arbitrary keys would put the
38
- * author's internal configuration strings into the page's word count.
39
- * - **It stays linear and allocation-light.** This runs behind the analysis
40
- * panel's debounce, so it is re-run every time the author pauses typing. It
41
- * makes one pass over the tree, reuses the image and link objects the HTML
42
- * reader already built instead of copying them, and joins the text exactly
43
- * once at the end.
44
- *
45
- * One known and accepted limitation: `buildSeoDocument` records where blocks
46
- * divide the token stream so a multi-word keyphrase cannot be counted across a
47
- * gap the reader can see, and that side table is keyed on the document object
48
- * it built, so the merged document here does not carry one. The consequence is
49
- * narrow — a phrase whose words happen to straddle the join between two blocks
50
- * can be counted once too often — and it is the same fallback any hand-built
51
- * document has always had. Correcting it needs a way to register boundaries for
52
- * an assembled document, which is a change to the engine, not to this walker.
53
- */
54
-
55
- /**
56
- * How deep the walk will follow nested containers before giving up.
57
- *
58
- * Sections can hold sections, so the structure is genuinely recursive and has
59
- * no schema-enforced ceiling. Eight levels is far past anything a human builds
60
- * and cheap to enforce, and it means a corrupt row that somehow refers back
61
- * into itself costs a bounded walk instead of a blown stack in the editor.
62
- */
63
- const MAX_BLOCK_NESTING_DEPTH = 8;
64
-
65
- /**
66
- * The heading level used when a `heading` block's level is missing or corrupt.
67
- *
68
- * This mirrors `HeadingBlockRenderer`, which falls back to an H2 for anything
69
- * outside 1-6. The audit has to grade the outline the visitor is served, and
70
- * defaulting to 1 instead would invent an H1 that is nowhere on the page — and
71
- * then report a second, entirely fictional H1 as a duplicate.
72
- */
73
- const FALLBACK_HEADING_LEVEL: SeoHeadingLevel = 2;
74
-
75
- /** The accumulating page document, before its parts are joined. */
76
- interface PageDocumentDraft {
77
- headings: SeoHeading[];
78
- images: SeoImage[];
79
- links: SeoLink[];
80
- /**
81
- * Each block's text, kept apart until the end and then joined with a single
82
- * space. Concatenating without a separator would glue the last word of one
83
- * block onto the first word of the next ("…our coffee" + "beans are…" reading
84
- * as the single token "coffeebeans"), which corrupts the word count, the
85
- * keyphrase density and the readability sample all at once.
86
- */
87
- textParts: string[];
88
- words: string[];
89
- }
90
-
91
- function createDraft(): PageDocumentDraft {
92
- return { headings: [], images: [], links: [], textParts: [], words: [] };
93
- }
94
-
95
- /** Narrows to a plain object, which is the only shape a block or content has. */
96
- function readRecord(value: unknown): Record<string, unknown> | null {
97
- return typeof value === 'object' && value !== null && !Array.isArray(value)
98
- ? (value as Record<string, unknown>)
99
- : null;
100
- }
101
-
102
- /**
103
- * Reads a field that is supposed to hold display text.
104
- *
105
- * Whitespace is collapsed to single spaces to match `SeoDocument.text`, which
106
- * the readability pass tokenises on the assumption that it already has been.
107
- * Anything that is not a string reads as empty rather than being coerced,
108
- * because `String(someObject)` would put "[object Object]" on the page.
109
- */
110
- function readText(value: unknown): string {
111
- return typeof value === 'string' ? value.replace(/\s+/g, ' ').trim() : '';
112
- }
113
-
114
- /** Clamps a stored heading level onto the six levels HTML actually has. */
115
- function readHeadingLevel(value: unknown): SeoHeadingLevel {
116
- const level = typeof value === 'number' ? Math.trunc(value) : Number.NaN;
117
-
118
- return Number.isFinite(level) && level >= 1 && level <= 6
119
- ? (level as SeoHeadingLevel)
120
- : FALLBACK_HEADING_LEVEL;
121
- }
122
-
123
- /** Adds a run of plain text, keeping the token stream in step with the text. */
124
- function appendText(draft: PageDocumentDraft, text: string): void {
125
- if (text === '') {
126
- return;
127
- }
128
-
129
- draft.textParts.push(text);
130
- for (const word of tokenizeWords(text)) {
131
- draft.words.push(word);
132
- }
133
- }
134
-
135
- /**
136
- * Folds a document the engine already built — one rich-text block's HTML or
137
- * Tiptap JSON — into the page draft.
138
- *
139
- * Headings are renumbered as they land so `order` counts positions on the page
140
- * rather than positions within whichever block happened to contain them; the
141
- * audit uses that number to say where the H1 sits, and a per-block number would
142
- * point the author at the wrong heading.
143
- */
144
- function appendDocument(draft: PageDocumentDraft, document: SeoDocument): void {
145
- for (const heading of document.headings) {
146
- draft.headings.push({ level: heading.level, order: draft.headings.length, text: heading.text });
147
- }
148
-
149
- for (const image of document.images) {
150
- draft.images.push(image);
151
- }
152
-
153
- for (const link of document.links) {
154
- draft.links.push(link);
155
- }
156
-
157
- if (document.text !== '') {
158
- draft.textParts.push(document.text);
159
- }
160
-
161
- // `words` is taken from the sub-document rather than re-tokenised from its
162
- // text: the engine guarantees the two agree, and re-tokenising would double
163
- // the work on the largest blocks on the page for an identical answer.
164
- for (const word of document.words) {
165
- draft.words.push(word);
166
- }
167
- }
168
-
169
- /** Adds one heading block, which is a standalone row rather than inline markup. */
170
- function collectHeadingBlock(content: Record<string, unknown>, draft: PageDocumentDraft): void {
171
- // Not run through an HTML stripper, unlike a rich-text block: the renderer
172
- // prints `text_content` as a React child, so any markup in it is escaped and
173
- // the visitor sees the angle brackets. Stripping here would analyse text the
174
- // page never shows.
175
- const text = readText(content['text_content']);
176
-
177
- draft.headings.push({
178
- level: readHeadingLevel(content['level']),
179
- order: draft.headings.length,
180
- text,
181
- });
182
-
183
- // A heading is also words on the page. Leaving it out of the text would make
184
- // headings free of charge in the word count and invisible to the keyphrase
185
- // density, even though they are the most heavily weighted copy on the page.
186
- appendText(draft, text);
187
- }
188
-
189
- /**
190
- * Adds one image block.
191
- *
192
- * The source is resolved the way `ImageBlockRenderer` resolves it — an external
193
- * URL wins, otherwise the R2 object key — and a block with neither is not
194
- * recorded as an image at all, because that block renders a "media not
195
- * selected" placeholder. Reporting it would raise "an image is missing alt
196
- * text" about something the visitor never sees as an image, which is precisely
197
- * the class of false finding this module was written to remove.
198
- */
199
- function collectImageBlock(content: Record<string, unknown>, draft: PageDocumentDraft): void {
200
- const externalUrl = readText(content['external_url']);
201
- const objectKey = readText(content['object_key']);
202
- const source = externalUrl !== '' ? externalUrl : objectKey;
203
-
204
- if (source === '') {
205
- return;
206
- }
207
-
208
- draft.images.push({ alt: readText(content['alt_text']), src: source });
209
-
210
- // The caption is rendered in a <figcaption> under the image, so it is prose a
211
- // reader and a crawler both see, and it belongs in the page's text.
212
- appendText(draft, readText(content['caption']));
213
- }
214
-
215
- /** Adds one button block: a link the visitor can follow, with a visible label. */
216
- function collectButtonBlock(content: Record<string, unknown>, draft: PageDocumentDraft): void {
217
- const text = readText(content['text']);
218
- const href = readText(content['url']);
219
-
220
- if (href !== '') {
221
- draft.links.push({ external: isExternalHref(href), href, text });
222
- }
223
-
224
- appendText(draft, text);
225
- }
226
-
227
- /** Adds a testimonial's visible quote and attribution. */
228
- function collectTestimonialBlock(
229
- content: Record<string, unknown>,
230
- draft: PageDocumentDraft
231
- ): void {
232
- appendText(draft, readText(content['quote']));
233
- appendText(draft, readText(content['author_name']));
234
- appendText(draft, readText(content['author_title']));
235
- }
236
-
237
- /**
238
- * Walks `column_blocks`, an array of columns each holding an array of blocks.
239
- *
240
- * Column order then block order is the order the page reads in on a phone,
241
- * where the columns stack, and it is the only total order that exists for this
242
- * shape. Getting it wrong would not lose any content, but it would misreport
243
- * which heading comes first and where in the copy the keyphrase falls.
244
- */
245
- function collectColumnBlocks(value: unknown, draft: PageDocumentDraft, depth: number): void {
246
- if (!Array.isArray(value)) {
247
- return;
248
- }
249
-
250
- for (const column of value) {
251
- if (!Array.isArray(column)) {
252
- continue;
253
- }
254
-
255
- for (const block of column) {
256
- collectBlock(block, draft, depth + 1);
257
- }
258
- }
259
- }
260
-
261
- /**
262
- * Walks a section, which is where most of a real page's content lives.
263
- *
264
- * Its children are not rows in the blocks array — they are plain
265
- * `{ block_type, content }` objects buried in `content.column_blocks` — so a
266
- * walker that only looked at the top-level list would under-report almost every
267
- * page ever built in this CMS.
268
- *
269
- * A section in slider mode renders its slides *instead of* its columns, and
270
- * this mirrors that: walking both would count copy from a stale column set the
271
- * visitor cannot reach, and inflate the word count with it.
272
- */
273
- function collectSectionBlock(
274
- content: Record<string, unknown>,
275
- draft: PageDocumentDraft,
276
- depth: number
277
- ): void {
278
- const slides = content['slides'];
279
-
280
- if (content['slider'] === true && Array.isArray(slides) && slides.length > 0) {
281
- for (const slide of slides) {
282
- const record = readRecord(slide);
283
- if (record !== null) {
284
- collectColumnBlocks(record['column_blocks'], draft, depth);
285
- }
286
- }
287
-
288
- return;
289
- }
290
-
291
- collectColumnBlocks(content['column_blocks'], draft, depth);
292
- }
293
-
294
- /** Dispatches one block row onto the reader that knows where its text lives. */
295
- function collectBlock(value: unknown, draft: PageDocumentDraft, depth: number): void {
296
- if (depth > MAX_BLOCK_NESTING_DEPTH) {
297
- return;
298
- }
299
-
300
- const block = readRecord(value);
301
- if (block === null) {
302
- return;
303
- }
304
-
305
- const blockType = typeof block['block_type'] === 'string' ? block['block_type'] : '';
306
- const content = readRecord(block['content']);
307
- if (content === null) {
308
- return;
309
- }
310
-
311
- switch (blockType) {
312
- case 'text': {
313
- // `html_content` holds either an HTML string or a JSON-stringified Tiptap
314
- // document depending on when and where the block was authored.
315
- // `buildSeoDocument` already distinguishes the two and reads both, so
316
- // this delegates rather than growing a second parser that could disagree
317
- // with the one the block-level panel uses. `text_content` is the shape
318
- // some older rows still carry.
319
- const source = content['html_content'] ?? content['text_content'];
320
- appendDocument(draft, buildSeoDocument(source));
321
-
322
- return;
323
- }
324
-
325
- case 'heading':
326
- collectHeadingBlock(content, draft);
327
-
328
- return;
329
-
330
- case 'image':
331
- collectImageBlock(content, draft);
332
-
333
- return;
334
-
335
- case 'button':
336
- collectButtonBlock(content, draft);
337
-
338
- return;
339
-
340
- case 'testimonial':
341
- collectTestimonialBlock(content, draft);
342
-
343
- return;
344
-
345
- // The registry calls this `video_embed`; `video` is accepted alongside it
346
- // because that is the name the block is known by in the UI and in prompts,
347
- // and both carry the same optional `title`.
348
- case 'video':
349
- case 'video_embed':
350
- appendText(draft, readText(content['title']));
351
-
352
- return;
353
-
354
- case 'section':
355
- collectSectionBlock(content, draft, depth);
356
-
357
- return;
358
-
359
- // `hero` is not in the current registry, but rows created before sections
360
- // absorbed it still exist in live databases and still render, so their copy
361
- // still counts. They keep both shapes: slides and a plain column set.
362
- case 'hero': {
363
- const slides = content['slides'];
364
- if (Array.isArray(slides)) {
365
- for (const slide of slides) {
366
- const record = readRecord(slide);
367
- if (record !== null) {
368
- collectColumnBlocks(record['column_blocks'], draft, depth);
369
- }
370
- }
371
- }
372
-
373
- collectColumnBlocks(content['column_blocks'], draft, depth);
374
-
375
- return;
376
- }
377
-
378
- default:
379
- // Every other block type — posts grids, forms, the commerce blocks, and
380
- // any custom block whose content is defined by whoever built it — renders
381
- // from data this walker cannot read, so it contributes nothing rather
382
- // than contributing a guess.
383
- return;
384
- }
385
- }
386
-
387
- export interface BuildPageSeoDocumentOptions {
388
- /**
389
- * An explicit document title to treat as the page's top-level H1.
390
- *
391
- * Posts in NextBlock render their title in an editorial H1 wrapper on the public
392
- * page (`PostClientContent.tsx`) rather than storing it as a heading block inside
393
- * the content array. Supplying `documentTitle` with `documentType: 'post'` ensures
394
- * the audit sees that H1 instead of falsely reporting `headings-missing-h1`.
395
- */
396
- documentTitle?: string | null;
397
- /**
398
- * The kind of document being graded. When set to 'post' and a documentTitle is present,
399
- * the title is treated as the primary H1 of the document.
400
- */
401
- documentType?: 'page' | 'post';
402
- }
403
-
404
- /**
405
- * Builds one `SeoDocument` from a page's or post's block list.
406
- *
407
- * `blocks` is deliberately typed `unknown`: it comes from form state, from a
408
- * draft row, or straight from Supabase as `Json`, and pretending at the
409
- * signature that it is already an array of well-formed rows would only push the
410
- * validation somewhere that has less context to do it in.
411
- */
412
- export function buildPageSeoDocument(
413
- blocks: unknown,
414
- options?: BuildPageSeoDocumentOptions
415
- ): SeoDocument {
416
- const isPostWithTitle =
417
- options?.documentType === 'post' &&
418
- typeof options?.documentTitle === 'string' &&
419
- options.documentTitle.trim() !== '';
420
-
421
- if (!Array.isArray(blocks) || blocks.length === 0) {
422
- if (isPostWithTitle) {
423
- const titleText = options!.documentTitle!.trim();
424
- const titleWords = tokenizeWords(titleText);
425
- return {
426
- headings: [{ level: 1, order: 0, text: titleText }],
427
- images: [],
428
- links: [],
429
- text: titleText,
430
- words: titleWords,
431
- };
432
- }
433
- return emptySeoDocument();
434
- }
435
-
436
- const draft = createDraft();
437
- for (const block of blocks) {
438
- collectBlock(block, draft, 0);
439
- }
440
-
441
- if (isPostWithTitle) {
442
- const titleText = options!.documentTitle!.trim();
443
- const titleWords = tokenizeWords(titleText);
444
- draft.headings.unshift({
445
- level: 1,
446
- order: 0,
447
- text: titleText,
448
- });
449
- for (let i = 1; i < draft.headings.length; i++) {
450
- draft.headings[i].order = i;
451
- }
452
- draft.textParts.unshift(titleText);
453
- draft.words.unshift(...titleWords);
454
- }
455
-
456
- return {
457
- headings: draft.headings,
458
- images: draft.images,
459
- links: draft.links,
460
- text: draft.textParts.join(' '),
461
- words: draft.words,
462
- };
463
- }
1
+ export {
2
+ buildPageSeoDocument,
3
+ type BuildPageSeoDocumentOptions,
4
+ } from '@nextblock-cms/utils/seo';
@@ -346,7 +346,7 @@ function toCustomRule(group: CustomRuleGroup): RobotsRule {
346
346
  const values = group.other[key];
347
347
  other[key] = values.length === 1 ? values[0] : values;
348
348
  }
349
- rule.other = other;
349
+ (rule as any).other = other;
350
350
  }
351
351
 
352
352
  return rule;
@@ -476,9 +476,10 @@ export function renderRobotsMetadata(metadata: MetadataRoute.Robots): string {
476
476
  content += `Crawl-delay: ${rule.crawlDelay}\n`;
477
477
  }
478
478
 
479
- if (rule.other) {
480
- for (const key of Object.keys(rule.other)) {
481
- const value = rule.other[key];
479
+ const ruleOther = (rule as any).other;
480
+ if (ruleOther) {
481
+ for (const key of Object.keys(ruleOther)) {
482
+ const value = ruleOther[key];
482
483
  if (value === null || value === undefined) {
483
484
  continue;
484
485
  }
@@ -188,5 +188,15 @@ export const MIGRATIONS_BUNDLE: BundledMigration[] = [
188
188
  "version": "00000000000034",
189
189
  "name": "00000000000034_seed_seo_french_legal_readability.sql",
190
190
  "sql": "-- Migration: 00000000000034_seed_seo_french_legal_readability.sql\n-- Description: Optimize French legal pages (politique de confidentialité & conditions d'utilisation) readability to achieve 100/100 Page SEO score.\n-- Safety: Forward-only, content-guarded updates that only apply to default seeded content.\n\nDO $$\nBEGIN\n -- Page 10: Politique de confidentialité FR (Block 92)\n UPDATE public.blocks\n SET content = jsonb_set(content, '{html_content}', to_jsonb('\r\n<h1>Politique de confidentialité</h1>\r\n<p><em>Dernière mise à jour : 4 juin 2026</em></p>\r\n<p>NextBlock™ CMS (« nous ») respecte votre vie privée. Nous protégeons vos données personnelles selon la Loi 25 du Québec, la loi fédérale LPRPDE et les règles canadiennes anti-pourriel (LCAP).</p>\r\n\r\n<h2>1. Responsable de la protection des données personnelles</h2>\r\n<p>Notre responsable veille au respect des règles de confidentialité. Vous pouvez lui écrire à <a href=\"mailto:{{privacy_email}}\">{{privacy_email}}</a>.</p>\r\n\r\n<h2>2. Renseignements que nous recueillons</h2>\r\n<ul>\r\n <li><strong>Renseignements de compte</strong> &mdash; nom, adresse courriel et identifiants lors de l''inscription.</li>\r\n <li><strong>Données d''utilisation et d''appareil</strong> &mdash; recueillies uniquement avec votre consentement au moyen de outils web.</li>\r\n <li><strong>Communications</strong> &mdash; les messages que vous nous envoyez et vos préférences marketing.</li>\r\n</ul>\r\n\r\n<h2>3. Finalités et consentement</h2>\r\n<p>Nous recueillons vos données pour des motifs clairs : faire fonctionner nos services, sécuriser les accès et vous répondre. Les outils d''analyse et de suivi ne s''activent qu''avec votre accord clair. Selon la Loi 25, les témoins optionnels restent coupés tant que vous ne les acceptez pas. Vous pouvez retirer votre accord en tout temps.</p>\r\n\r\n<h2>4. Témoins et technologies de suivi</h2>\r\n<p>Les témoins essentiels assurent le bon usage du site. Ils ne demandent aucun accord préalable. Les témoins d''analyse se chargent seulement après votre choix sur notre bandeau. Nous gardons votre choix en mémoire pour le respecter.</p>\r\n\r\n<h2>5. Partage des données</h2>\r\n<p>Nous ne vendons pas vos données personnelles. Nous ne les communiquons qu''à des fournisseurs qui nous aident à exploiter la plateforme, sous obligation de confidentialité, ou lorsque la loi l''exige.</p>\r\n\r\n<h2>6. Conservation</h2>\r\n<p>Nous ne conservons les données personnelles que le temps nécessaire aux fins décrites ou exigé par la loi, après quoi ils sont détruits ou anonymisés sans risque.</p>\r\n\r\n<h2>7. Vos droits</h2>\r\n<p>Vous avez le droit de lire, de corriger et de faire effacer vos données. Vous pouvez aussi retirer votre accord ou demander une copie de vos données. Pour exercer vos droits, écrivez à notre responsable à <a href=\"mailto:{{privacy_email}}\">{{privacy_email}}</a>.</p>\r\n\r\n<h2>8. Messages électroniques commerciaux (LCAP)</h2>\r\n<p>Nous n''envoyons des messages électroniques commerciaux qu''avec votre consentement. Chaque message nous identifie et comporte un mécanisme de désabonnement fonctionnel que nous respectons rapidement.</p>\r\n\r\n<h2>9. Mesures de sécurité</h2>\r\n<p>Nous employons des mesures physiques, organisationnelles et technologiques appropriées &mdash; dont le chiffrement en transit et le contrôle des accès &mdash; pour protéger vos données.</p>\r\n\r\n<h2>10. Logiciel libre</h2>\r\n<p>NextBlock™ CMS est un logiciel libre sous licence AGPLv3. Si vous hébergez NextBlock vous-même, vous gérez votre propre serveur. Vous êtes responsable des données sur votre instance, et ce texte est un modèle que vous pouvez adapter.</p>\r\n\r\n<h2>11. Modifications</h2>\r\n<p>Nous pouvons mettre à jour cette politique. Les changements importants seront communiqués sur le site et la date de mise à jour sera révisée.</p>\r\n\r\n<h2>12. Nous joindre</h2>\r\n<p>Des questions ou des plaintes ? Contactez NextBlock™ CMS à <a href=\"mailto:{{privacy_email}}\">{{privacy_email}}</a>. Vous pouvez aussi vous adresser à la Commission d''accès à l''information du Québec.</p>\r\n'::text)),\n updated_at = now()\n WHERE id = 92\n AND page_id = 10\n AND content->>'html_content' LIKE '%Politique de confidentialité%';\n\n -- Page 12: Conditions d'utilisation FR (Block 94)\n UPDATE public.blocks\n SET content = jsonb_set(content, '{html_content}', to_jsonb('\r\n<h1>Règles du service</h1>\r\n<p><em>Dernière mise à jour : 4 juin 2026</em></p>\r\n\r\n<h2>1. Acceptation des conditions</h2>\r\n<p>En utilisant le CMS NextBlock™ et les services associés (les « Services »), vous acceptez ces règles du service. Si vous refusez ces règles, veuillez ne pas utiliser les Services.</p>\r\n\r\n<h2>2. Logiciel libre et à code source ouvert</h2>\r\n<p>NextBlock™ CMS est un logiciel libre et à code source ouvert distribué sous la <strong>licence publique générale GNU Affero, version 3 (AGPL-3.0)</strong> ou, à votre choix, toute version ultérieure. Vous êtes libre d''exécuter, d''étudier, de partager et de modifier le logiciel selon les termes de cette licence. Une copie de la licence est fournie avec le logiciel et est aussi disponible à <a href=\"https://www.gnu.org/licenses/agpl-3.0.html\">gnu.org/licenses/agpl-3.0.html</a>.</p>\r\n<p>Droit d''auteur © 2025 NextBlock™ CMS.</p>\r\n\r\n<h2>3. Disponibilité du code source</h2>\r\n<p>Conformément à l''article 13 de l''AGPL-3.0, si vous exploitez une version modifiée de NextBlock™ CMS et la rendez accessible à des utilisateurs sur un réseau, vous devez offrir clairement à ces utilisateurs l''accès au code source correspondant de votre version modifiée, gratuitement, par un moyen usuel de copie de logiciels.</p>\r\n\r\n<h2>4. Marques de commerce</h2>\r\n<p>L''AGPL-3.0 accorde de larges droits sur le code source du logiciel, mais <strong>n''accorde aucun droit</strong> sur nos noms commerciaux, marques de commerce ou marques de service. « NextBlock™ », le nom NextBlock™ CMS et les logos associés demeurent notre propriété et ne peuvent être utilisés d''une manière laissant entendre une approbation ou une affiliation sans notre autorisation écrite préalable.</p>\r\n\r\n<h2>5. Comptes et utilisation acceptable</h2>\r\n<p>Si vous créez un compte, vous êtes responsable de la protection de vos identifiants et de toute activité effectuée à partir de votre compte, et vous vous engagez à nous aviser rapidement de toute utilisation non autorisée. Vous vous engagez à ne pas détourner les Services, notamment en tentant de les perturber, d''y accéder sans autorisation ou de les utiliser à des fins illégales.</p>\r\n\r\n<h2>6. Absence de garantie</h2>\r\n<p>Selon l''article 15 de l''AGPL-3.0, le logiciel est fourni « tel quel », sans garantie d''aucune sorte, expresse ou tacite. Cela inclut les garanties de vente ou d''usage pour un besoin précis. Vous prenez sur vous les risques liés au bon emploi du logiciel.</p>\r\n\r\n<h2>7. Limitation de devoir</h2>\r\n<p>Comme l''énonce l''article 16 de l''AGPL-3.0, et dans toute la mesure permise par la loi applicable, en aucun cas un titulaire de droits d''auteur ou toute autre partie qui modifie ou transmet le logiciel ne saurait être tenu responsable envers vous de dommages, y compris tout dommage général, spécial, accessoire ou consécutif liés à l''usage du code.</p>\r\n\r\n<h2>8. Droit applicable</h2>\r\n<p>Ces conditions suivent les lois de la province de Québec et les lois du Canada applicables. Rien ici ne réduit vos droits stricts de consommateur selon ces lois.</p>\r\n\r\n<h2>9. Modifications</h2>\r\n<p>Nous pouvons réviser ces conditions de temps à autre. Les changements importants seront communiqués au moyen des Services, et l''utilisation continue des Services après leur entrée en vigueur vaut acceptation.</p>\r\n\r\n<h2>10. Nous joindre</h2>\r\n<p>Des questions sur ces conditions ? Contactez NextBlock™ CMS à <a href=\"mailto:{{privacy_email}}\">{{privacy_email}}</a>.</p>\r\n'::text)),\n updated_at = now()\n WHERE id = 94\n AND page_id = 12\n AND content->>'html_content' LIKE '%Conditions d''utilisation%';\n\nEND $$;\n"
191
+ },
192
+ {
193
+ "version": "00000000000035",
194
+ "name": "00000000000035_digital_products_seo_optimizations.sql",
195
+ "sql": "-- Migration: 00000000000035_digital_products_seo_optimizations.sql\n-- Purpose: Set meta_title and meta_description for the 2 real digital products (EN + FR)\n-- and optimize their block heading levels and copy readability to achieve 100/100 SEO scores.\n-- Safety: Append-only, forward-only, content-guarded updates.\n\nBEGIN;\n\n-- 1. Digital Products SEO Metadata (Titles & Descriptions)\n-- NextBlock Cortex AI - Cortex AI License (EN)\nUPDATE public.products\nSET meta_title = 'NextBlock Cortex AI License | Native AI Content Engine',\n meta_description = 'Add native AI copywriting, real-time block translation, and structure refactoring to your NextBlock editor with our BYOK Cortex AI license.',\n updated_at = NOW()\nWHERE id = 'd48a4bb4-5119-4576-9c1d-32c404dd84f8';\n\n-- Licence NextBlock™ Cortex AI (FR)\nUPDATE public.products\nSET meta_title = 'Licence NextBlock Cortex AI | Moteur IA pour Éditeur',\n meta_description = 'Intégrez la rédaction assistée par IA, la traduction de blocs et l''optimisation de structure dans NextBlock grâce à la licence Cortex AI.',\n updated_at = NOW()\nWHERE id = '69848224-a6d6-43c9-9748-f41a8e8e424b';\n\n-- NextBlock Commerce Pro - Commerce License (EN)\nUPDATE public.products\nSET meta_title = 'NextBlock Commerce Pro | Global Next.js Storefronts',\n meta_description = 'Launch high-performance digital and physical stores with headless Stripe checkout, Freemius licensing, and multi-currency pricing in NextBlock.',\n updated_at = NOW()\nWHERE id = 'ed86bd77-da61-4e6f-826c-956f9a55b7aa';\n\n-- Licence NextBlock™ Commerce Pro (FR)\nUPDATE public.products\nSET meta_title = 'Licence NextBlock Commerce Pro | E-Commerce Next.js',\n meta_description = 'Créez des boutiques Next.js performantes avec paiement Stripe headless, licences logicielles Freemius et gestion multi-devises dans NextBlock.',\n updated_at = NOW()\nWHERE id = '5c9e1b99-eeed-4fe0-adfa-31e81004c2f9';\n\n-- 2. Digital Products Block Content & Heading Optimizations\n-- Block ID 39\nUPDATE public.blocks\nSET content = '{\"padding\":{\"top\":\"xl\",\"bottom\":\"xl\"},\"background\":{\"type\":\"gradient\",\"gradient\":{\"type\":\"linear\",\"stops\":[{\"color\":\"#064e3b\",\"position\":0},{\"color\":\"#0f172a\",\"position\":100}],\"direction\":\"135deg\"}},\"column_gap\":\"xl\",\"column_blocks\":[[{\"block_type\":\"text\",\"content\":{\"html_content\":\"<p class=\\\"text-xs uppercase tracking-[0.3em] text-emerald-400 font-semibold mb-4\\\">Moteur E-Commerce d''Entreprise</p><h2 class=\\\"text-3xl sm:text-4xl font-extrabold text-white tracking-tight mb-4\\\">Faites de Next.js une Boutique Mondiale.</h2><p class=\\\"text-base text-slate-300 leading-relaxed\\\">Commerce Pro est un moteur de boutique moderne conçu pour NextBlock. Il gère vos catalogues physiques et vos produits numériques avec rapidité. Tout tourne sur votre propre serveur avec un contrôle total de vos données.</p>\"}}],[{\"block_type\":\"text\",\"content\":{\"html_content\":\"<div class=\\\"rounded-2xl border border-emerald-700 bg-slate-950 p-6 shadow-xl sm:p-8\\\"><h3 class=\\\"text-lg font-bold text-white mb-4\\\">Pourquoi choisir Commerce Pro</h3><ul class=\\\"space-y-3 text-sm text-slate-300\\\"><li class=\\\"flex items-start gap-3\\\"><span class=\\\"text-emerald-400 font-bold\\\">✓</span><span><strong>Multi-Devises</strong> — Taux de change en direct, règles d''arrondis et détection automatique du pays de l''acheteur.</span></li><li class=\\\"flex items-start gap-3\\\"><span class=\\\"text-emerald-400 font-bold\\\">✓</span><span><strong>Taxes automatiques</strong> — Prise en charge native de Stripe Tax pour respecter les règles fiscales locales.</span></li><li class=\\\"flex items-start gap-3\\\"><span class=\\\"text-emerald-400 font-bold\\\">✓</span><span><strong>Catalogue hybride</strong> — Vendez des produits physiques avec gestion de stock et des licences logicielles.</span></li></ul></div>\"}}]],\"container_type\":\"container\",\"responsive_columns\":{\"mobile\":1,\"tablet\":1,\"desktop\":2},\"vertical_alignment\":\"center\"}'::jsonb,\n updated_at = NOW()\nWHERE id = 39;\n\n-- Block ID 41\nUPDATE public.blocks\nSET content = '{\"padding\":{\"top\":\"lg\",\"bottom\":\"lg\"},\"background\":{\"type\":\"solid\",\"color\":\"transparent\"},\"column_gap\":\"lg\",\"column_blocks\":[[{\"block_type\":\"text\",\"content\":{\"html_content\":\"<div class=\\\"h-full rounded-2xl border border-slate-200 bg-white p-6 shadow-sm transition-colors hover:border-emerald-300 dark:border-slate-800 dark:bg-slate-900\\\"><div class=\\\"mb-4 flex h-10 w-10 items-center justify-center rounded-xl bg-emerald-50 text-xl dark:bg-emerald-950\\\">💳</div><h4 class=\\\"text-lg font-bold text-foreground mb-2\\\">Checkout Stripe Headless</h4><p class=\\\"text-sm text-muted-foreground leading-relaxed\\\">Paiement direct et sécurisé pour un ou plusieurs articles. Les commandes se valident automatiquement grâce aux webhooks Stripe.</p></div>\"}}],[{\"block_type\":\"text\",\"content\":{\"html_content\":\"<div class=\\\"h-full rounded-2xl border border-slate-200 bg-white p-6 shadow-sm transition-colors hover:border-emerald-300 dark:border-slate-800 dark:bg-slate-900\\\"><div class=\\\"mb-4 flex h-10 w-10 items-center justify-center rounded-xl bg-emerald-50 text-xl dark:bg-emerald-950\\\">🔑</div><h4 class=\\\"text-lg font-bold text-foreground mb-2\\\">Licences Numériques Freemius</h4><p class=\\\"text-sm text-muted-foreground leading-relaxed\\\">Distribuez vos logiciels, activez vos clés et gérez les périodes d''essai SaaS. Tout fonctionne directement avec Freemius sans souci.</p></div>\"}}],[{\"block_type\":\"text\",\"content\":{\"html_content\":\"<div class=\\\"h-full rounded-2xl border border-slate-200 bg-white p-6 shadow-sm transition-colors hover:border-emerald-300 dark:border-slate-800 dark:bg-slate-900\\\"><div class=\\\"mb-4 flex h-10 w-10 items-center justify-center rounded-xl bg-emerald-50 text-xl dark:bg-emerald-950\\\">🛍️</div><h4 class=\\\"text-lg font-bold text-foreground mb-2\\\">Merchandising Visuel</h4><p class=\\\"text-sm text-muted-foreground leading-relaxed\\\">Concevez vos fiches produits, mettez en valeur vos promotions et créez vos rayons directement dans l''éditeur de blocs.</p></div>\"}}]],\"container_type\":\"container\",\"responsive_columns\":{\"mobile\":1,\"tablet\":2,\"desktop\":3},\"vertical_alignment\":\"stretch\"}'::jsonb,\n updated_at = NOW()\nWHERE id = 41;\n\n-- Block ID 42\nUPDATE public.blocks\nSET content = '{\"padding\":{\"top\":\"xl\",\"bottom\":\"xl\"},\"background\":{\"type\":\"gradient\",\"gradient\":{\"type\":\"linear\",\"stops\":[{\"color\":\"#0f172a\",\"position\":0},{\"color\":\"#064e3b\",\"position\":100}],\"direction\":\"135deg\"}},\"column_gap\":\"xl\",\"column_blocks\":[[{\"block_type\":\"text\",\"content\":{\"html_content\":\"<p class=\\\"text-xs uppercase tracking-[0.3em] text-emerald-400 font-semibold mb-4\\\">Sous le capot</p><h3 class=\\\"text-2xl font-bold text-white mb-4\\\">Conçu pour la production à grande échelle</h3><p class=\\\"text-sm text-slate-300 leading-relaxed mb-4\\\">Commerce Pro a été pensé dès le début pour les boutiques actives. Chaque route API est mise en cache, et les requêtes SQL sont optimisées.</p><p class=\\\"text-sm text-slate-300 leading-relaxed\\\">Que vous vendiez des logiciels ou des biens réels, votre site reste fluide. Il absorbe les pics de trafic sans ralentissement.</p>\"}}],[{\"block_type\":\"text\",\"content\":{\"html_content\":\"<div class=\\\"space-y-4\\\"><div class=\\\"p-5 rounded-xl border border-slate-700 bg-slate-900\\\"><h4 class=\\\"text-sm font-bold text-white mb-1\\\">Gestion des commandes</h4><p class=\\\"text-xs text-slate-400 leading-relaxed\\\">Suivez chaque commande du panier à la livraison. Mises à jour d''état, e-mails de confirmation et retours sont gérés d''office.</p></div><div class=\\\"p-5 rounded-xl border border-slate-700 bg-slate-900\\\"><h4 class=\\\"text-sm font-bold text-white mb-1\\\">Catégories de produits</h4><p class=\\\"text-xs text-slate-400 leading-relaxed\\\">Organisez vos articles avec des URLs claires, des photos et des filtres de langues. Pilotez tout depuis le tableau de bord du CMS.</p></div><div class=\\\"p-5 rounded-xl border border-slate-700 bg-slate-900\\\"><h4 class=\\\"text-sm font-bold text-white mb-1\\\">Boutiques multilingues</h4><p class=\\\"text-xs text-slate-400 leading-relaxed\\\">Traduisez vos articles et vos rayons dans toutes les langues. Les stocks sont partagés tout en gardant des métadonnées SEO propres.</p></div></div>\"}}]],\"container_type\":\"container\",\"responsive_columns\":{\"mobile\":1,\"tablet\":1,\"desktop\":2},\"vertical_alignment\":\"center\"}'::jsonb,\n updated_at = NOW()\nWHERE id = 42;\n\n-- Block ID 43\nUPDATE public.blocks\nSET content = '{\"padding\":{\"top\":\"xl\",\"bottom\":\"xl\"},\"background\":{\"type\":\"gradient\",\"gradient\":{\"type\":\"linear\",\"stops\":[{\"color\":\"#064e3b\",\"position\":0},{\"color\":\"#022c22\",\"position\":100}],\"direction\":\"135deg\"}},\"column_gap\":\"none\",\"column_blocks\":[[{\"block_type\":\"heading\",\"content\":{\"level\":2,\"textAlign\":\"center\",\"textColor\":\"background\",\"text_content\":\"Prêt à lancer votre boutique ?\"}},{\"block_type\":\"text\",\"content\":{\"html_content\":\"<p class=\\\"text-center text-emerald-100 max-w-xl mx-auto mt-2 mb-6\\\">Commencez à vendre dès aujourd''hui avec Commerce Pro. Multi-devises, conforme aux taxes et rapide dès l''installation.</p>\"}},{\"block_type\":\"button\",\"content\":{\"url\":\"https://nextblock.dev/product/nextblock-commerce-pro-commerce-license\",\"size\":\"lg\",\"text\":\"Acheter Commerce Pro\",\"variant\":\"secondary\",\"position\":\"center\"}}]],\"container_type\":\"container\",\"responsive_columns\":{\"mobile\":1,\"tablet\":1,\"desktop\":1},\"vertical_alignment\":\"center\"}'::jsonb,\n updated_at = NOW()\nWHERE id = 43;\n\n-- Block ID 49\nUPDATE public.blocks\nSET content = '{\"padding\":{\"top\":\"xl\",\"bottom\":\"xl\"},\"background\":{\"type\":\"gradient\",\"gradient\":{\"type\":\"linear\",\"stops\":[{\"color\":\"#1e1b4b\",\"position\":0},{\"color\":\"#0f172a\",\"position\":100}],\"direction\":\"135deg\"}},\"column_gap\":\"xl\",\"column_blocks\":[[{\"block_type\":\"text\",\"content\":{\"html_content\":\"<p class=\\\"text-xs uppercase tracking-[0.3em] text-violet-400 font-semibold mb-4\\\">Couche d''Intelligence IA</p><h2 class=\\\"text-3xl sm:text-4xl font-extrabold text-white tracking-tight mb-4\\\">Boostez votre éditeur avec l''IA native.</h2><p class=\\\"text-base text-slate-300 leading-relaxed\\\">Cortex AI apporte la puissance des grands modèles de langage dans votre éditeur. Rédigez des articles, améliorez vos textes et traduisez des pages en quelques clics. Les blocs créés restent stables, rapides et fidèles à votre charte.</p>\"}}],[{\"block_type\":\"text\",\"content\":{\"html_content\":\"<div class=\\\"rounded-2xl border border-violet-700 bg-slate-950 p-6 shadow-xl sm:p-8\\\"><h3 class=\\\"text-lg font-bold text-white mb-4\\\">OpenRouter et architecture BYOK</h3><ul class=\\\"space-y-3 text-sm text-slate-300\\\"><li class=\\\"flex items-start gap-3\\\"><span class=\\\"text-violet-400 font-bold\\\">✓</span><span><strong>Apportez votre clé API</strong> — Maîtrisez vos coûts avec vos propres clés OpenRouter. Aucun frais caché.</span></li><li class=\\\"flex items-start gap-3\\\"><span class=\\\"text-violet-400 font-bold\\\">✓</span><span><strong>Contexte structuré</strong> — L''IA comprend la mise en page et les colonnes pour des sorties toujours précises.</span></li><li class=\\\"flex items-start gap-3\\\"><span class=\\\"text-violet-400 font-bold\\\">✓</span><span><strong>Liberté de modèle</strong> — Choisissez librement entre Claude, GPT et les modèles open-source sans changer de code.</span></li></ul></div>\"}}]],\"container_type\":\"container\",\"responsive_columns\":{\"mobile\":1,\"tablet\":1,\"desktop\":2},\"vertical_alignment\":\"center\"}'::jsonb,\n updated_at = NOW()\nWHERE id = 49;\n\n-- Block ID 51\nUPDATE public.blocks\nSET content = '{\"padding\":{\"top\":\"lg\",\"bottom\":\"lg\"},\"background\":{\"type\":\"solid\",\"color\":\"transparent\"},\"column_gap\":\"lg\",\"column_blocks\":[[{\"block_type\":\"text\",\"content\":{\"html_content\":\"<div class=\\\"h-full rounded-2xl border border-slate-200 bg-white p-6 shadow-sm transition-colors hover:border-violet-300 dark:border-slate-800 dark:bg-slate-900\\\"><div class=\\\"mb-4 flex h-10 w-10 items-center justify-center rounded-xl bg-violet-50 text-xl dark:bg-violet-950\\\">✍️</div><h4 class=\\\"text-lg font-bold text-foreground mb-2\\\">Rédaction en un clic</h4><p class=\\\"text-sm text-muted-foreground leading-relaxed\\\">Rédigez des guides, ajustez vos titres et créez des appels à l''action. Cortex AI adopte votre ton et écrit un texte clair.</p></div>\"}}],[{\"block_type\":\"text\",\"content\":{\"html_content\":\"<div class=\\\"h-full rounded-2xl border border-slate-200 bg-white p-6 shadow-sm transition-colors hover:border-violet-300 dark:border-slate-800 dark:bg-slate-900\\\"><div class=\\\"mb-4 flex h-10 w-10 items-center justify-center rounded-xl bg-violet-50 text-xl dark:bg-violet-950\\\">🔄</div><h4 class=\\\"text-lg font-bold text-foreground mb-2\\\">Refactorisation de structure</h4><p class=\\\"text-sm text-muted-foreground leading-relaxed\\\">Ajustez vos colonnes, ajoutez des grilles et réorganisez vos sections. Cortex AI génère des données valides sans effort.</p></div>\"}}],[{\"block_type\":\"text\",\"content\":{\"html_content\":\"<div class=\\\"h-full rounded-2xl border border-slate-200 bg-white p-6 shadow-sm transition-colors hover:border-violet-300 dark:border-slate-800 dark:bg-slate-900\\\"><div class=\\\"mb-4 flex h-10 w-10 items-center justify-center rounded-xl bg-violet-50 text-xl dark:bg-violet-950\\\">🌐</div><h4 class=\\\"text-lg font-bold text-foreground mb-2\\\">Traduction automatique</h4><p class=\\\"text-sm text-muted-foreground leading-relaxed\\\">Traduisez vos pages complètes entre le français et l''anglais. Cortex AI conserve vos mises en page et styles intacts.</p></div>\"}}]],\"container_type\":\"container\",\"responsive_columns\":{\"mobile\":1,\"tablet\":2,\"desktop\":3},\"vertical_alignment\":\"stretch\"}'::jsonb,\n updated_at = NOW()\nWHERE id = 51;\n\n-- Block ID 52\nUPDATE public.blocks\nSET content = '{\"padding\":{\"top\":\"xl\",\"bottom\":\"xl\"},\"background\":{\"type\":\"gradient\",\"gradient\":{\"type\":\"linear\",\"stops\":[{\"color\":\"#0f172a\",\"position\":0},{\"color\":\"#1e1b4b\",\"position\":100}],\"direction\":\"135deg\"}},\"column_gap\":\"xl\",\"column_blocks\":[[{\"block_type\":\"text\",\"content\":{\"html_content\":\"<p class=\\\"text-xs uppercase tracking-[0.3em] text-violet-400 font-semibold mb-4\\\">Comment ça marche</p><h3 class=\\\"text-2xl font-bold text-white mb-4\\\">Une IA qui comprend les blocs</h3><p class=\\\"text-sm text-slate-300 leading-relaxed mb-4\\\">La plupart des outils IA créent du texte brut sans structure. Cortex AI a été pensé pour l''éditeur de NextBlock. Il analyse vos blocs, vos colonnes et vos options visuelles.</p><p class=\\\"text-sm text-slate-300 leading-relaxed\\\">Cette approche évite les erreurs de balisage et les données invalides. Votre équipe obtient des rendus élégants sur mobile et ordinateur.</p>\"}}],[{\"block_type\":\"text\",\"content\":{\"html_content\":\"<div class=\\\"space-y-4\\\"><div class=\\\"p-5 rounded-xl border border-slate-700 bg-slate-900\\\"><h4 class=\\\"text-sm font-bold text-white mb-1\\\">Génération de contenu</h4><p class=\\\"text-xs text-slate-400 leading-relaxed\\\">Produisez du contenu percutant pour vos pages et articles. L''assistant respecte votre style et délivre des textes fluides.</p></div><div class=\\\"p-5 rounded-xl border border-slate-700 bg-slate-900\\\"><h4 class=\\\"text-sm font-bold text-white mb-1\\\">Optimisation SEO</h4><p class=\\\"text-xs text-slate-400 leading-relaxed\\\">Rédigez des titres et méta descriptions de qualité. Cortex AI vérifie la lisibilité et les mots clés avant publication.</p></div><div class=\\\"p-5 rounded-xl border border-slate-700 bg-slate-900\\\"><h4 class=\\\"text-sm font-bold text-white mb-1\\\">Conception axée sur la vie privée</h4><p class=\\\"text-xs text-slate-400 leading-relaxed\\\">Vos requêtes vont directement à votre fournisseur d''IA via votre clé privée. Vos données ne sont jamais stockées ni revendues.</p></div></div>\"}}]],\"container_type\":\"container\",\"responsive_columns\":{\"mobile\":1,\"tablet\":1,\"desktop\":2},\"vertical_alignment\":\"center\"}'::jsonb,\n updated_at = NOW()\nWHERE id = 52;\n\n-- Block ID 53\nUPDATE public.blocks\nSET content = '{\"padding\":{\"top\":\"xl\",\"bottom\":\"xl\"},\"background\":{\"type\":\"gradient\",\"gradient\":{\"type\":\"linear\",\"stops\":[{\"color\":\"#312e81\",\"position\":0},{\"color\":\"#1e1b4b\",\"position\":100}],\"direction\":\"135deg\"}},\"column_gap\":\"none\",\"column_blocks\":[[{\"block_type\":\"heading\",\"content\":{\"level\":2,\"textAlign\":\"center\",\"textColor\":\"background\",\"text_content\":\"Prêt à ajouter l''IA à votre éditeur ?\"}},{\"block_type\":\"text\",\"content\":{\"html_content\":\"<p class=\\\"text-center text-violet-100 max-w-xl mx-auto mt-2 mb-6\\\">Libérez la puissance de la création de contenu avec l''IA. Connectez votre clé, protégez vos données et rédigez vite.</p>\"}},{\"block_type\":\"button\",\"content\":{\"url\":\"https://nextblock.dev/product/nextblock-cortex-ai-cortex-ai-license\",\"size\":\"lg\",\"text\":\"Acheter Cortex AI\",\"variant\":\"secondary\",\"position\":\"center\"}}]],\"container_type\":\"container\",\"responsive_columns\":{\"mobile\":1,\"tablet\":1,\"desktop\":1},\"vertical_alignment\":\"center\"}'::jsonb,\n updated_at = NOW()\nWHERE id = 53;\n\n-- Block ID 65\nUPDATE public.blocks\nSET content = '{\"padding\":{\"top\":\"xl\",\"bottom\":\"xl\"},\"background\":{\"type\":\"gradient\",\"gradient\":{\"type\":\"linear\",\"stops\":[{\"color\":\"#064e3b\",\"position\":0},{\"color\":\"#0f172a\",\"position\":100}],\"direction\":\"135deg\"}},\"column_gap\":\"xl\",\"column_blocks\":[[{\"block_type\":\"text\",\"content\":{\"html_content\":\"<p class=\\\"text-xs uppercase tracking-[0.3em] text-emerald-400 font-semibold mb-4\\\">Enterprise E-Commerce Engine</p><h2 class=\\\"text-3xl sm:text-4xl font-extrabold text-white tracking-tight mb-4\\\">Turn Next.js Into a Global Storefront.</h2><p class=\\\"text-base text-slate-300 leading-relaxed\\\">Commerce Pro is a fast store engine built for NextBlock. It helps teams sell physical goods and digital downloads with ease. Everything runs on your own server with total data ownership and zero extra fees.</p>\"}}],[{\"block_type\":\"text\",\"content\":{\"html_content\":\"<div class=\\\"rounded-2xl border border-emerald-700 bg-slate-950 p-6 shadow-xl sm:p-8\\\"><h3 class=\\\"text-lg font-bold text-white mb-4\\\">Why Teams Choose Commerce Pro</h3><ul class=\\\"space-y-3 text-sm text-slate-300\\\"><li class=\\\"flex items-start gap-3\\\"><span class=\\\"text-emerald-400 font-bold\\\">✓</span><span><strong>Multi-Currency Pricing</strong> — Live exchange rates, clear charm pricing, and automatic visitor country detection.</span></li><li class=\\\"flex items-start gap-3\\\"><span class=\\\"text-emerald-400 font-bold\\\">✓</span><span><strong>Simple Tax Rules</strong> — Native Stripe Tax support calculates local taxes automatically during checkout.</span></li><li class=\\\"flex items-start gap-3\\\"><span class=\\\"text-emerald-400 font-bold\\\">✓</span><span><strong>Hybrid Product Catalog</strong> — Sell physical goods with stock counts alongside digital software license keys.</span></li></ul></div>\"}}]],\"container_type\":\"container\",\"responsive_columns\":{\"mobile\":1,\"tablet\":1,\"desktop\":2},\"vertical_alignment\":\"center\"}'::jsonb,\n updated_at = NOW()\nWHERE id = 65;\n\n-- Block ID 67\nUPDATE public.blocks\nSET content = '{\"padding\":{\"top\":\"lg\",\"bottom\":\"lg\"},\"background\":{\"type\":\"solid\",\"color\":\"transparent\"},\"column_gap\":\"lg\",\"column_blocks\":[[{\"block_type\":\"text\",\"content\":{\"html_content\":\"<div class=\\\"h-full rounded-2xl border border-slate-200 bg-white p-6 shadow-sm transition-colors hover:border-emerald-300 dark:border-slate-800 dark:bg-slate-900\\\"><div class=\\\"mb-4 flex h-10 w-10 items-center justify-center rounded-xl bg-emerald-50 text-xl dark:bg-emerald-950\\\">💳</div><h4 class=\\\"text-lg font-bold text-foreground mb-2\\\">Headless Stripe Checkout</h4><p class=\\\"text-sm text-muted-foreground leading-relaxed\\\">Fast checkout flows for single or multiple items. Orders process automatically through secure webhooks with zero friction.</p></div>\"}}],[{\"block_type\":\"text\",\"content\":{\"html_content\":\"<div class=\\\"h-full rounded-2xl border border-slate-200 bg-white p-6 shadow-sm transition-colors hover:border-emerald-300 dark:border-slate-800 dark:bg-slate-900\\\"><div class=\\\"mb-4 flex h-10 w-10 items-center justify-center rounded-xl bg-emerald-50 text-xl dark:bg-emerald-950\\\">🔑</div><h4 class=\\\"text-lg font-bold text-foreground mb-2\\\">Freemius Digital Licensing</h4><p class=\\\"text-sm text-muted-foreground leading-relaxed\\\">Deliver software files, issue license keys, and manage SaaS free trials easily. Everything connects directly to Freemius.</p></div>\"}}],[{\"block_type\":\"text\",\"content\":{\"html_content\":\"<div class=\\\"h-full rounded-2xl border border-slate-200 bg-white p-6 shadow-sm transition-colors hover:border-emerald-300 dark:border-slate-800 dark:bg-slate-900\\\"><div class=\\\"mb-4 flex h-10 w-10 items-center justify-center rounded-xl bg-emerald-50 text-xl dark:bg-emerald-950\\\">🛍️</div><h4 class=\\\"text-lg font-bold text-foreground mb-2\\\">Visual Merchandising</h4><p class=\\\"text-sm text-muted-foreground leading-relaxed\\\">Design custom product pages, highlight special deals, and build shopping grids right inside the visual block editor.</p></div>\"}}]],\"container_type\":\"container\",\"responsive_columns\":{\"mobile\":1,\"tablet\":2,\"desktop\":3},\"vertical_alignment\":\"stretch\"}'::jsonb,\n updated_at = NOW()\nWHERE id = 67;\n\n-- Block ID 68\nUPDATE public.blocks\nSET content = '{\"padding\":{\"top\":\"xl\",\"bottom\":\"xl\"},\"background\":{\"type\":\"gradient\",\"gradient\":{\"type\":\"linear\",\"stops\":[{\"color\":\"#0f172a\",\"position\":0},{\"color\":\"#064e3b\",\"position\":100}],\"direction\":\"135deg\"}},\"column_gap\":\"xl\",\"column_blocks\":[[{\"block_type\":\"text\",\"content\":{\"html_content\":\"<p class=\\\"text-xs uppercase tracking-[0.3em] text-emerald-400 font-semibold mb-4\\\">Under The Hood</p><h3 class=\\\"text-2xl font-bold text-white mb-4\\\">Built for Production Scale</h3><p class=\\\"text-sm text-slate-300 leading-relaxed mb-4\\\">Commerce Pro was built from day one for busy storefronts. Every API path is cached near your users, and database queries use fast indexes.</p><p class=\\\"text-sm text-slate-300 leading-relaxed\\\">Whether you sell digital downloads or retail goods, your storefront loads fast. It handles high traffic spikes without slow downs or crashes.</p>\"}}],[{\"block_type\":\"text\",\"content\":{\"html_content\":\"<div class=\\\"space-y-4\\\"><div class=\\\"p-5 rounded-xl border border-slate-700 bg-slate-900\\\"><h4 class=\\\"text-sm font-bold text-white mb-1\\\">Order Management</h4><p class=\\\"text-xs text-slate-400 leading-relaxed\\\">Track customer orders from cart to shipping. Automatic status updates, buyer receipts, and refunds work right out of the box.</p></div><div class=\\\"p-5 rounded-xl border border-slate-700 bg-slate-900\\\"><h4 class=\\\"text-sm font-bold text-white mb-1\\\">Product Categories</h4><p class=\\\"text-xs text-slate-400 leading-relaxed\\\">Organize products with clean web addresses, category images, and full multi-language tags. Manage it all from the CMS dashboard.</p></div><div class=\\\"p-5 rounded-xl border border-slate-700 bg-slate-900\\\"><h4 class=\\\"text-sm font-bold text-white mb-1\\\">Multi-Language Storefronts</h4><p class=\\\"text-xs text-slate-400 leading-relaxed\\\">Translate products and category pages into any language. Each language shares inventory while keeping its own SEO metadata.</p></div></div>\"}}]],\"container_type\":\"container\",\"responsive_columns\":{\"mobile\":1,\"tablet\":1,\"desktop\":2},\"vertical_alignment\":\"center\"}'::jsonb,\n updated_at = NOW()\nWHERE id = 68;\n\n-- Block ID 69\nUPDATE public.blocks\nSET content = '{\"padding\":{\"top\":\"xl\",\"bottom\":\"xl\"},\"background\":{\"type\":\"gradient\",\"gradient\":{\"type\":\"linear\",\"stops\":[{\"color\":\"#064e3b\",\"position\":0},{\"color\":\"#022c22\",\"position\":100}],\"direction\":\"135deg\"}},\"column_gap\":\"none\",\"column_blocks\":[[{\"block_type\":\"heading\",\"content\":{\"level\":2,\"textAlign\":\"center\",\"textColor\":\"background\",\"text_content\":\"Ready to launch your storefront?\"}},{\"block_type\":\"text\",\"content\":{\"html_content\":\"<p class=\\\"text-center text-emerald-100 max-w-xl mx-auto mt-2 mb-6\\\">Start selling today with Commerce Pro. It is multi-currency, tax-compliant, and lightning-fast out of the box.</p>\"}},{\"block_type\":\"button\",\"content\":{\"url\":\"#\",\"size\":\"lg\",\"text\":\"Purchase Commerce Pro\",\"variant\":\"secondary\",\"position\":\"center\"}}]],\"container_type\":\"container\",\"responsive_columns\":{\"mobile\":1,\"tablet\":1,\"desktop\":1},\"vertical_alignment\":\"center\"}'::jsonb,\n updated_at = NOW()\nWHERE id = 69;\n\n-- Block ID 70\nUPDATE public.blocks\nSET content = '{\"padding\":{\"top\":\"xl\",\"bottom\":\"xl\"},\"background\":{\"type\":\"gradient\",\"gradient\":{\"type\":\"linear\",\"stops\":[{\"color\":\"#1e1b4b\",\"position\":0},{\"color\":\"#0f172a\",\"position\":100}],\"direction\":\"135deg\"}},\"column_gap\":\"xl\",\"column_blocks\":[[{\"block_type\":\"text\",\"content\":{\"html_content\":\"<p class=\\\"text-xs uppercase tracking-[0.3em] text-violet-400 font-semibold mb-4\\\">AI Intelligence Layer</p><h2 class=\\\"text-3xl sm:text-4xl font-extrabold text-white tracking-tight mb-4\\\">Supercharge Your Editor with AI.</h2><p class=\\\"text-base text-slate-300 leading-relaxed\\\">Cortex AI brings powerful language models directly into your editor. You can create content, edit blog drafts, and translate full pages in seconds. It writes clean block data rather than raw text, keeping your layouts stable and fast.</p>\"}}],[{\"block_type\":\"text\",\"content\":{\"html_content\":\"<div class=\\\"rounded-2xl border border-violet-700 bg-slate-950 p-6 shadow-xl sm:p-8\\\"><h3 class=\\\"text-lg font-bold text-white mb-4\\\">OpenRouter &amp; BYOK Architecture</h3><ul class=\\\"space-y-3 text-sm text-slate-300\\\"><li class=\\\"flex items-start gap-3\\\"><span class=\\\"text-violet-400 font-bold\\\">✓</span><span><strong>Bring Your Own Key</strong> — You control costs using your own OpenRouter tokens. There are no hidden fees or markups.</span></li><li class=\\\"flex items-start gap-3\\\"><span class=\\\"text-violet-400 font-bold\\\">✓</span><span><strong>Block-Aware Prompts</strong> — Models receive structured context. They see headings, columns, and layout rules for accurate outputs.</span></li><li class=\\\"flex items-start gap-3\\\"><span class=\\\"text-violet-400 font-bold\\\">✓</span><span><strong>Model Flexibility</strong> — Switch freely between Claude, GPT, and open-source models without changing code.</span></li></ul></div>\"}}]],\"container_type\":\"container\",\"responsive_columns\":{\"mobile\":1,\"tablet\":1,\"desktop\":2},\"vertical_alignment\":\"center\"}'::jsonb,\n updated_at = NOW()\nWHERE id = 70;\n\n-- Block ID 72\nUPDATE public.blocks\nSET content = '{\"padding\":{\"top\":\"lg\",\"bottom\":\"lg\"},\"background\":{\"type\":\"solid\",\"color\":\"transparent\"},\"column_gap\":\"lg\",\"column_blocks\":[[{\"block_type\":\"text\",\"content\":{\"html_content\":\"<div class=\\\"h-full rounded-2xl border border-slate-200 bg-white p-6 shadow-sm transition-colors hover:border-violet-300 dark:border-slate-800 dark:bg-slate-900\\\"><div class=\\\"mb-4 flex h-10 w-10 items-center justify-center rounded-xl bg-violet-50 text-xl dark:bg-violet-950\\\">✍️</div><h4 class=\\\"text-lg font-bold text-foreground mb-2\\\">One-Click Copywriting</h4><p class=\\\"text-sm text-muted-foreground leading-relaxed\\\">Write blog posts, polish headlines, and craft calls to action. Cortex AI matches your brand voice and writes clear prose with ease.</p></div>\"}}],[{\"block_type\":\"text\",\"content\":{\"html_content\":\"<div class=\\\"h-full rounded-2xl border border-slate-200 bg-white p-6 shadow-sm transition-colors hover:border-violet-300 dark:border-slate-800 dark:bg-slate-900\\\"><div class=\\\"mb-4 flex h-10 w-10 items-center justify-center rounded-xl bg-violet-50 text-xl dark:bg-violet-950\\\">🔄</div><h4 class=\\\"text-lg font-bold text-foreground mb-2\\\">Structure Refactoring</h4><p class=\\\"text-sm text-muted-foreground leading-relaxed\\\">Reorganize layout grids, split columns, and reorder sections. Cortex AI produces valid block schemas that render safely every time.</p></div>\"}}],[{\"block_type\":\"text\",\"content\":{\"html_content\":\"<div class=\\\"h-full rounded-2xl border border-slate-200 bg-white p-6 shadow-sm transition-colors hover:border-violet-300 dark:border-slate-800 dark:bg-slate-900\\\"><div class=\\\"mb-4 flex h-10 w-10 items-center justify-center rounded-xl bg-violet-50 text-xl dark:bg-violet-950\\\">🌐</div><h4 class=\\\"text-lg font-bold text-foreground mb-2\\\">Automated Translation</h4><p class=\\\"text-sm text-muted-foreground leading-relaxed\\\">Translate entire pages between languages with one click. Cortex AI keeps your styles, nested sections, and media links intact.</p></div>\"}}]],\"container_type\":\"container\",\"responsive_columns\":{\"mobile\":1,\"tablet\":2,\"desktop\":3},\"vertical_alignment\":\"stretch\"}'::jsonb,\n updated_at = NOW()\nWHERE id = 72;\n\n-- Block ID 73\nUPDATE public.blocks\nSET content = '{\"padding\":{\"top\":\"xl\",\"bottom\":\"xl\"},\"background\":{\"type\":\"gradient\",\"gradient\":{\"type\":\"linear\",\"stops\":[{\"color\":\"#0f172a\",\"position\":0},{\"color\":\"#1e1b4b\",\"position\":100}],\"direction\":\"135deg\"}},\"column_gap\":\"xl\",\"column_blocks\":[[{\"block_type\":\"text\",\"content\":{\"html_content\":\"<p class=\\\"text-xs uppercase tracking-[0.3em] text-violet-400 font-semibold mb-4\\\">How It Works</p><h3 class=\\\"text-2xl font-bold text-white mb-4\\\">AI That Understands Blocks</h3><p class=\\\"text-sm text-slate-300 leading-relaxed mb-4\\\">Most AI tools only generate flat text strings. Cortex AI is built specifically for NextBlock editors. It reads your full page structure, column arrays, and style options.</p><p class=\\\"text-sm text-slate-300 leading-relaxed\\\">This deep connection prevents broken markup and invalid data. Your team gets clean results that look great on desktop and mobile screens right away.</p>\"}}],[{\"block_type\":\"text\",\"content\":{\"html_content\":\"<div class=\\\"space-y-4\\\"><div class=\\\"p-5 rounded-xl border border-slate-700 bg-slate-900\\\"><h4 class=\\\"text-sm font-bold text-white mb-1\\\">Content Generation</h4><p class=\\\"text-xs text-slate-400 leading-relaxed\\\">Create engaging copy for marketing pages and help docs. The assistant follows your editorial style guide and produces clean text.</p></div><div class=\\\"p-5 rounded-xl border border-slate-700 bg-slate-900\\\"><h4 class=\\\"text-sm font-bold text-white mb-1\\\">SEO Optimization</h4><p class=\\\"text-xs text-slate-400 leading-relaxed\\\">Draft concise meta titles and meta descriptions automatically. Cortex AI checks keyword density and reading ease before you publish.</p></div><div class=\\\"p-5 rounded-xl border border-slate-700 bg-slate-900\\\"><h4 class=\\\"text-sm font-bold text-white mb-1\\\">Privacy-First Design</h4><p class=\\\"text-xs text-slate-400 leading-relaxed\\\">Your content goes directly to your chosen provider via private API keys. We never store, log, or train on your private data.</p></div></div>\"}}]],\"container_type\":\"container\",\"responsive_columns\":{\"mobile\":1,\"tablet\":1,\"desktop\":2},\"vertical_alignment\":\"center\"}'::jsonb,\n updated_at = NOW()\nWHERE id = 73;\n\n-- Block ID 74\nUPDATE public.blocks\nSET content = '{\"padding\":{\"top\":\"xl\",\"bottom\":\"xl\"},\"background\":{\"type\":\"gradient\",\"gradient\":{\"type\":\"linear\",\"stops\":[{\"color\":\"#312e81\",\"position\":0},{\"color\":\"#1e1b4b\",\"position\":100}],\"direction\":\"135deg\"}},\"column_gap\":\"none\",\"column_blocks\":[[{\"block_type\":\"heading\",\"content\":{\"level\":2,\"textAlign\":\"center\",\"textColor\":\"background\",\"text_content\":\"Ready to add AI to your editor?\"}},{\"block_type\":\"text\",\"content\":{\"html_content\":\"<p class=\\\"text-center text-violet-100 max-w-xl mx-auto mt-2 mb-6\\\">Unlock the full power of AI-driven content creation. Connect your key, protect your privacy, and edit faster today.</p>\"}},{\"block_type\":\"button\",\"content\":{\"url\":\"#\",\"size\":\"lg\",\"text\":\"Purchase Cortex AI\",\"variant\":\"secondary\",\"position\":\"center\"}}]],\"container_type\":\"container\",\"responsive_columns\":{\"mobile\":1,\"tablet\":1,\"desktop\":1},\"vertical_alignment\":\"center\"}'::jsonb,\n updated_at = NOW()\nWHERE id = 74;\n\nCOMMIT;\n"
196
+ },
197
+ {
198
+ "version": "00000000000036",
199
+ "name": "00000000000036_seed_posts_missing_seo_metadata.sql",
200
+ "sql": "-- Migration: 00000000000036_seed_posts_missing_seo_metadata.sql\n-- Purpose: Set meta_title and meta_description for remaining seeded blog posts\n-- (how-nextblock-works, comment-nextblock-fonctionne, nextblock-commerce-guide, guide-commerce-nextblock)\n-- so all 9 seeded posts achieve a 100/100 SEO score.\n-- Safety: Append-only, forward-only, content-guarded updates.\n\nBEGIN;\n\nUPDATE public.posts\n SET meta_title = 'How NextBlock™ Works: Architecture & Under the Hood',\n meta_description = 'Explore the architecture behind NextBlock: monorepo packages, typed block registry, Supabase database, and modern Next.js editor stack.',\n updated_at = NOW()\n WHERE slug = 'how-nextblock-works'\n AND meta_title IS NULL;\n\nUPDATE public.posts\n SET meta_title = 'Comment fonctionne NextBlock™ : Architecture et Moteur',\n meta_description = 'Découvrez les fondations de NextBlock : monorepo, registre de blocs typés, base de données Supabase et moteur d''édition Next.js moderne.',\n updated_at = NOW()\n WHERE slug = 'comment-nextblock-fonctionne'\n AND meta_title IS NULL;\n\nUPDATE public.posts\n SET meta_title = 'The Complete NextBlock™ Commerce Guide | Headless Store',\n meta_description = 'Build global e-commerce with NextBlock: manage products, configure multi-currency pricing, and accept payments with headless Stripe checkout.',\n updated_at = NOW()\n WHERE slug = 'nextblock-commerce-guide'\n AND meta_title IS NULL;\n\nUPDATE public.posts\n SET meta_title = 'Guide Complet du Commerce avec NextBlock™ | E-Commerce',\n meta_description = 'Développez votre boutique avec NextBlock : gestion de produits, tarifs multi-devises et tunnel de paiement headless avec Stripe Checkout.',\n updated_at = NOW()\n WHERE slug = 'guide-commerce-nextblock'\n AND meta_title IS NULL;\n\nCOMMIT;\n"
191
201
  }
192
202
  ];
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextblock-cms/template",
3
- "version": "0.16.3",
3
+ "version": "0.16.4",
4
4
  "description": "The open-source, full-stack AI-native CMS for Next.js 16, Supabase, and Tailwind CSS.",
5
5
  "keywords": [
6
6
  "cms",