DEV Community

Celestia Studio
Celestia Studio

Posted on

How to Build SEO + GEO-Optimized Niche Service Pages with Webflow CMS in 2026

ARTICLE

If you build websites for professional services — lawyers, physios, restaurants, accountants — here's a system that's quietly working very well in 2026.

The idea: one CMS collection, one template, unlimited hyper-targeted service pages. Each page ranks for its own niche keyword and gets cited by AI engines.

Here's the full technical setup.


The Problem With Generic Service Pages

Most agency websites have one /services/seo or /services/web-design page.

That page tries to rank for every industry at once. It ends up ranking for none.

The solution: one page per vertical. site-web-avocat for lawyers. site-web-kinesitherapeute for physios. agence-web-paris for location-based queries.

Each page is a dedicated landing page with industry-specific content, stats, pricing, and FAQs. Each ranks independently.


The Webflow CMS Setup

Create a new CMS collection with these fields:

name           → Plain Text   → H1 title
slug           → Plain Text   → URL (/services/site-web-avocat)
seo-title      → Plain Text   → Meta title (max 60 chars)
seo-meta-desc  → Plain Text   → Meta description (max 160 chars)
tagline        → Plain Text   → Subtitle under H1
body           → Rich Text    → Full page content (HTML)
faq-q1–faq-q5  → Plain Text   → FAQ questions
faq-a1–faq-a5  → Plain Text   → FAQ answers (plain text — no HTML)
Enter fullscreen mode Exit fullscreen mode

Important: Keep FAQ answers as Plain Text, not Rich Text. This makes them directly usable in JSON-LD structured data without stripping HTML.


The Body Structure (Order Matters for SEO + GEO)

Every niche page follows this exact structure:

1.  Quick Answer <p>        — keyword in first 10 words, 40–60 words, no links
2.  Social Banner            — internal CTA component
3.  Intro                    — industry stat + pain point (max 2 sentences/paragraph)
4.  H2 "What is X?" (definition) — starts with "X is..." pattern for AI citation
5.  H2 "Top 5 agencies for X" — comparison table (you're #1, dark background)
6.  H2 "Why [Agency] for X?" — bulleted differentiators
7.  H2 "How to build X?"    — numbered steps
8.  H2 "How much does X cost?" — pricing table with overflow wrapper
9.  H3 E-E-A-T block        — real client case, named, with measured results
10. H2 "How to appear in ChatGPT as X?" — GEO section
11. H2 "Key Facts 2026"     — 5–7 standalone bullets (AI-citable)
12. CTA close               — WhatsApp + contact link
13. Author block             — photo + bio
14. Social Banner bottom
Enter fullscreen mode Exit fullscreen mode

Three rules that affect AI citation performance:

  • Quick Answer must have no links — AI engines extract clean text blocks
  • "X is..." on the first H2 — every major AI citation study shows definitions get pulled first
  • Key Facts bullets — each must be a complete standalone sentence, not a fragment

Fix the One Webflow Rich Text Issue That Will Break Your Pages

When you render CMS content in a Webflow Rich Text block, headings inherit the site's global styles — which are almost always too large for body content.

Your H2s end up enormous. Your H3s look like H1s. It breaks the visual hierarchy and tanks your readability score.

The fix: add this to your CMS template's <head> Custom Code. Use .w-richtext as the selector — not a custom class — because Webflow always applies this class to Rich Text elements regardless of what else is on the element.

<style>
  .w-richtext h2 {
    font-size: 26px !important;
    font-weight: 700 !important;
    line-height: 1.3 !important;
    margin-top: 48px !important;
    margin-bottom: 16px !important;
  }
  .w-richtext h3 {
    font-size: 20px !important;
    font-weight: 600 !important;
    margin-top: 32px !important;
    margin-bottom: 12px !important;
  }
  .w-richtext p {
    margin-bottom: 16px !important;
    line-height: 1.75 !important;
  }
  .w-richtext ul, .w-richtext ol {
    margin: 16px 0 24px 20px !important;
  }
  .w-richtext table {
    width: 100% !important;
    border-collapse: collapse !important;
  }
  .w-richtext td, .w-richtext th {
    padding: 12px !important;
    border: 1px solid #e0e0e0 !important;
    text-align: left !important;
  }
</style>
Enter fullscreen mode Exit fullscreen mode

Common mistake: using .rich-text-block.w-richtext h2 — this only works if your element has both classes. The safe selector is always .w-richtext alone.

Also: any <table> you inject via the API will overflow on mobile. Wrap it:

<div style="overflow-x:auto;-webkit-overflow-scrolling:touch;">
  <table style="min-width:600px;">
    <!-- your table content -->
  </table>
</div>
Enter fullscreen mode Exit fullscreen mode

This one wrapper saves you from every mobile layout complaint.


The JSON-LD Schema (3 Types in One Block)

This is the part most agencies skip. It's also the part that drives AI citation.

Place this in the <head> Custom Code of the CMS template:

{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "WebPage",
      "name": "{{wf {\"path\":\"seo-title\",\"type\":\"PlainText\"} }}",
      "description": "{{wf {\"path\":\"seo-meta-description\",\"type\":\"PlainText\"} }}",
      "url": "{{wf {\"path\":\"url\",\"type\":\"URL\"} }}",
      "datePublished": "{{wf {\"path\":\"created-on\",\"type\":\"Date\"} }}",
      "dateModified": "{{wf {\"path\":\"updated-on\",\"type\":\"Date\"} }}"
    },
    {
      "@type": "Service",
      "name": "{{wf {\"path\":\"name\",\"type\":\"PlainText\"} }}",
      "description": "{{wf {\"path\":\"seo-meta-description\",\"type\":\"PlainText\"} }}",
      "serviceType": "Web Design & Development",
      "provider": { "@type": "Organization", "name": "Your Agency" }
    },
    {
      "@type": "FAQPage",
      "mainEntity": [
        {
          "@type": "Question",
          "name": "{{wf {\"path\":\"faq-q1\",\"type\":\"PlainText\"} }}",
          "acceptedAnswer": {
            "@type": "Answer",
            "text": "{{wf {\"path\":\"faq-a1\",\"type\":\"PlainText\"} }}"
          }
        }
        // ... repeat for faq-q2 through faq-q5
      ]
    },
    {
      "@type": "BreadcrumbList",
      "itemListElement": [
        { "@type": "ListItem", "position": 1, "name": "Home", "item": "https://youragency.com" },
        { "@type": "ListItem", "position": 2, "name": "Services", "item": "https://youragency.com/services" },
        {
          "@type": "ListItem",
          "position": 3,
          "name": "{{wf {\"path\":\"seo-title\",\"type\":\"PlainText\"} }}",
          "item": "{{wf {\"path\":\"url\",\"type\":\"URL\"} }}"
        }
      ]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

The {{wf ...}} syntax is Webflow's variable binding — it dynamically injects the CMS field values at render time.

Why this combination matters:

  • WebPage → tells Google what this page is about
  • Service → tells AI engines what service is being offered and by whom
  • FAQPage → the single highest-impact schema for AI citation (ChatGPT pulls FAQ answers verbatim)
  • BreadcrumbList → improves click-through rate in Google search results

The Publishing Flow (Via API)

Once a page is written, it gets pushed via the Webflow API v2:

// 1. Create the item
POST /v2/collections/{collectionId}/items

// 2. Set isDraft: false (separate call for custom collections)
PATCH /v2/collections/{collectionId}/items/{itemId}
body: { "isDraft": false }

// 3. Publish the item
POST /v2/collections/{collectionId}/items/publish
body: { "itemIds": ["itemId"] }

// 4. Publish the site
POST /v2/sites/{siteId}/publish
body: { "publishToWebflowSubdomain": true }
Enter fullscreen mode Exit fullscreen mode

Note: For custom CMS collections (not the built-in Blog collection), _draft: false inside fieldData doesn't work. You need a separate PATCH with {"isDraft": false} at the root level.


SEO Rules Before Publishing

Two hard limits, no exceptions:

const title = "Site web pour avocats | Celestia Studio"; // 42 chars ✅
const desc = "Create your law firm website: Webflow, CNB-compliant, ..."; // max 160 chars

console.log(title.length <= 60 ? "OK" : "TOO LONG");
console.log(desc.length <= 160 ? "OK" : "TOO LONG");
Enter fullscreen mode Exit fullscreen mode

And for the body content:

  • Sentences: max 20 words (split longer ones)
  • Paragraphs: max 2 sentences per <p> tag
  • Stats: min 5 bolded numbers with % or € in the body
  • H2s: min 4 phrased as questions

These aren't style preferences. They directly affect the SEO tool scores that measure readability and keyword distribution.


Result After First Page Live

First page (site-web-avocat) published with this system:

  • 7 H2 total, 6 phrased as questions ✅
  • 7 bolded stats with % or € ✅
  • Keyword in first 10 words of Quick Answer ✅
  • Full WebPage + Service + FAQPage + BreadcrumbList schema ✅
  • Author block + social banners ✅

Next up: kinésithérapeutes, restaurants, coachs, artisans, and major French cities.


The Compounding Effect

This is why the system works at scale:

Each page targets a keyword that a generic service page can't rank for. Each page has its own schema, its own FAQ, its own AI citation surface. Each new page adds to the internal link network.

At 50 pages, every major French professional service vertical is covered. At 100 pages, geographic coverage begins. At 200+ pages, the compounding effect starts to show in GSC — consistent impressions across hundreds of long-tail queries, each with low competition.

The investment per page: ~2–3 hours of content writing + 30 minutes of technical setup. The return: a permanent, crawlable, AI-citable entry point for that vertical.


Built with Webflow CMS, Node.js for API calls, and Schema.org for structured data. All pages pushed programmatically — zero manual CMS editing after initial template setup.

Enzo Marcelle — Celestia Studio — Webflow + SEO + GEO for professional services, France.

Top comments (0)