How to Design Content-Site Sections So They Scale

Practical taxonomy + URL design for content sites — section schemas, hub-page templates, and sitemap config that scales from 50 to 1000 articles.

A good section structure is invisible. A bad one shows up at article 200 when half your articles do not fit anywhere. Plan the taxonomy now — encode it in your content collection schema, your URL pattern, and your sitemap — and save the rewrite later.

Background

Section structure is the contract between your articles and search engines. It tells Google “these 30 articles are about the same thing”, and tells readers “you came for X, here are 5 more X articles”. For indie content sites in 2026, taxonomy mistakes compound faster than any other planning mistake because they touch URLs, breadcrumbs, sitemaps, and internal links simultaneously.

How to tell

  • You expect 200+ articles within 18 months.
  • Your topics naturally cluster into 4-8 distinct sub-areas.
  • You can imagine a reader wanting to filter or browse, not just search.
  • Your monetization or conversion depends on people landing on a section page, not only on individual articles.
  • You want category pages to rank for medium-volume keywords on their own.

Quick verdict

Use 4-8 top-level sections (hubs) plus a flat tag system. Avoid sub-sub-sections. Treat the section list as semi-permanent and the tag list as fluid.

Before you start

  • Decide if you want flat URLs (/articles/slug/) or single-level nesting (/hub/slug/) — this affects every downstream decision.
  • Make sure your framework supports collection schemas (Astro Content Collections, Next.js MDX + Zod, etc.).
  • Have an existing list of 50-100 candidate topics so you can do a real bucket exercise, not a theoretical one.

Step by step

  1. Anchor on 4-8 nouns. Write your “what this site is about” sentence, identify 4-8 anchor nouns. Those are your hubs. For an AI-productivity site, that might be: ai-applications, ai-tools, indie-dev, prompt-library, troubleshooting.

  2. Stress-test each hub. For each hub, list 20-30 long-tail topic candidates. A hub that cannot fill 20 candidates is too narrow — fold it into another hub or drop it.

  3. Encode the taxonomy in your content schema. In Astro Content Collections (src/content/config.ts):

import { defineCollection, z } from 'astro:content';

const HUBS = [
  'ai-applications',
  'ai-tools',
  'indie-dev',
  'prompt-library',
  'troubleshooting',
] as const;

export const collections = {
  articles: defineCollection({
    type: 'content',
    schema: z.object({
      title: z.string().min(8).max(80),
      description: z.string().min(80).max(170),
      urlSlug: z.string().regex(/^[a-z0-9-]+$/),
      category: z.enum(HUBS),                 // exactly one hub
      subcategory: z.string().optional(),     // free-form, fluid
      tags: z.array(z.string()).max(8),
      publishedAt: z.date(),
      lang: z.enum(['en', 'zh']),
      translationKey: z.string(),
    }),
  }),
};

The z.enum(HUBS) constraint is what prevents drift — adding a 9th hub becomes an explicit decision, not a silent typo.

  1. Pick a URL pattern and lock it in astro.config.mjs. Recommended flat-with-language layout:
/en/articles/<slug>/            # article
/en/category/<hub>/             # hub index
/en/category/<hub>/page/2/      # paginated
/en/tag/<tag>/                  # tag index (optional noindex)

The Astro routes:

src/pages/[lang]/articles/[...slug].astro
src/pages/[lang]/category/[hub]/index.astro
src/pages/[lang]/category/[hub]/page/[page].astro
src/pages/[lang]/tag/[tag].astro
  1. Build hub index pages from day one — even if empty. Hub templates must produce real content, not just a list. Template skeleton:
---
import { getCollection } from 'astro:content';
const { hub, lang } = Astro.params;
const all = await getCollection('articles',
  (a) => a.data.category === hub && a.data.lang === lang);
const top = all.sort((a, b) => +b.data.publishedAt - +a.data.publishedAt);
---
<h1>{hubTitle(hub, lang)}</h1>
<p>{hubIntro(hub, lang)}</p>           <!-- ~120 words of real context -->
<ul>
  {top.map((a) => (
    <li><a href={`/${lang}/articles/${a.data.urlSlug}/`}>{a.data.title}</a></li>
  ))}
</ul>
<section class="hub-faq">…</section>   <!-- 3-5 hub-level Q&A -->

Empty hub pages get flagged as thin — always include an intro paragraph and an FAQ.

  1. Reflect hubs in the sitemap with priority hints. Hub pages should be priority 0.8, articles 0.6:
<url>
  <loc>https://yourdomain.com/en/category/indie-dev/</loc>
  <priority>0.8</priority>
  <changefreq>weekly</changefreq>
</url>
<url>
  <loc>https://yourdomain.com/en/articles/some-slug/</loc>
  <priority>0.6</priority>
  <changefreq>monthly</changefreq>
</url>
  1. Add tags only after you feel the gap. Start with 8-12 tags maximum, and keep them noindex until they each have at least 8 articles:
{tagArticleCount < 8 && <meta name="robots" content="noindex,follow" />}
  1. Enforce single-hub assignment in CI. A quick prebuild check:
# scripts/check-single-hub.mjs (excerpt)
for (const article of all) {
  if (!HUBS.includes(article.data.category)) fail(article, 'invalid hub');
  if (Array.isArray(article.data.category)) fail(article, 'multi-hub forbidden');
}
  1. Audit every 100 articles. Merge tags that have fewer than 5 articles; split tags above ~40. Record the audit date in your content-ops log.

Implementation checklist

  • Content schema enums the allowed hubs — typo-resistant.
  • Each hub has its own index page with intro + FAQ + paginated list.
  • Sitemap distinguishes hub vs article priority.
  • Tag pages stay noindex,follow until they pass a minimum article threshold.
  • Prebuild script fails the build on invalid hub assignment.

After-launch verification

  • Search Console → Performance → Pages: hub URLs should accumulate impressions on hub-level keywords within 4-8 weeks.
  • Crawl a hub URL with URL Inspection — confirm it is indexed and rendered with the article list.
  • Run a sitemap probe: curl -s https://yourdomain.com/sitemap.xml | grep -c '<loc>' against your expected count.

Common pitfalls

  • Nesting sub-sub-sections like /hub/subhub/subsubhub/slug/ — refactoring becomes painful within a year. Detection: any URL with 3+ path segments after the language prefix.
  • Letting hubs grow organically; you end up with 15 hubs each with 5 articles. Detection: prebuild script that counts articles per hub and warns at < 12.
  • Using tags as a substitute for hubs — search engines value hubs more.
  • Renaming a hub mid-flight without 301s in place; you lose all accumulated authority. If you must rename, put redirects in firebase.json or vercel.json before the rename ships.
  • Allowing articles to live in multiple hubs — duplicate URLs or weak canonicals follow.
  • Indexing tag pages with < 5 articles each — Google sees them as thin.

FAQ

  • How many hubs is too many?: For an indie site, more than 8 hubs is almost always too many in year 1. You will under-fill every hub.
  • Should hub pages have content or just be lists?: They should have a real introduction (~120 words), the article list, and an FAQ. Empty list pages get flagged as thin.
  • Do tags help SEO?: They help readers more than SEO. Many sites keep tag pages noindex,follow to avoid thin-page issues. Hubs are what carries SEO weight.
  • What if my niche genuinely needs sub-sub-sections?: Use a flat URL pattern and represent the hierarchy in breadcrumbs and metadata. Do not encode it in the URL.
  • Should I add subcategory to the URL?: No. Keep subcategory in metadata and breadcrumbs, but keep URLs single-level. URL changes are the most painful refactor; metadata changes are cheap.

Tags: #Indie dev #Website planning #Pillar / Cluster #SEO #Content ops #Technical SEO