好的栏目结构是隐形的。坏的栏目结构会在第 200 篇时跳出来——一半文章都不知道归在哪。早规划——把分类编码进内容 schema、URL 规则、sitemap——少返工。
问题背景
栏目结构是文章与搜索引擎之间的契约。它告诉 Google “这 30 篇是同一个主题”,告诉读者”你找 X,这里还有 5 篇 X”。2026 年对独立内容站,分类错误是复利最快的规划错误——一改就同时影响 URL、面包屑、sitemap 和内链。
判断标准
- 预期 18 个月内能写到 200+ 篇。
- 主题天然能划成 4-8 个明显不同的子方向。
- 读者会需要浏览或筛选,而不仅仅是搜索。
- 变现或转化要靠栏目页落地,而不仅是单篇文章。
- 希望栏目页本身能跑出中等量级关键词排名。
快速结论
用 4-8 个一级栏目(hub)加扁平标签系统,避免子子栏目。栏目当半永久结构维护,标签当弹性维度。
开始前准备
- 先定 URL 是扁平(
/articles/slug/)还是单层嵌套(/hub/slug/)——后续每个决定都受这个影响。 - 框架支持 collection schema(Astro Content Collections、Next.js MDX + Zod 等)。
- 手里有 50-100 个候选题目,能做真实归桶练习,不是纸上推演。
实操步骤
-
锚定 4-8 个核心名词。 写一句”这个站是干什么的”,挑出 4-8 个核心名词,那些就是 hub。比如 AI 生产力站可能是:ai-applications、ai-tools、indie-dev、prompt-library、troubleshooting。
-
每个 hub 压力测试。 列 20-30 个长尾候选题。撑不到 20 个的 hub 太窄——合到别的 hub 或者砍掉。
-
分类写进 content schema。 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), // 严格属于一个 hub
subcategory: z.string().optional(), // 自由格式,可变
tags: z.array(z.string()).max(8),
publishedAt: z.date(),
lang: z.enum(['en', 'zh']),
translationKey: z.string(),
}),
}),
};
z.enum(HUBS) 防漂移——加第 9 个 hub 是显式决定,不是手滑打错字。
- URL 规则定下来后写进
astro.config.mjs。 推荐扁平 + 语言前缀:
/en/articles/<slug>/ # 文章
/en/category/<hub>/ # 栏目索引
/en/category/<hub>/page/2/ # 分页
/en/tag/<tag>/ # 标签索引(可 noindex)
Astro 路由:
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
- 第一天就建每个 hub 的索引页——就算是空的。 hub 模板要有真实内容,不能只是列表。骨架:
---
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 字真实介绍 -->
<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 级 FAQ -->
空 hub 页会被算薄页——一定要有介绍段和 FAQ。
- sitemap 给 hub 比文章更高 priority。 hub 0.8,文章 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>
- 标签是后加的。 起步最多 8-12 个,每个标签满 8 篇之前都 noindex:
{tagArticleCount < 8 && <meta name="robots" content="noindex,follow" />}
- CI 里强制单 hub 归属。 一个简单的 prebuild 检查:
# scripts/check-single-hub.mjs(节选)
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');
}
- 每 100 篇审一次。 标签少于 5 篇的合并;超过 40 的拆分。审计日期写进 content-ops 日志。
执行检查清单
- content schema 用 enum 约束允许的 hub——typo 直接挂。
- 每个 hub 有自己的索引页:介绍 + FAQ + 分页列表。
- sitemap 给 hub 和文章不同 priority。
- 标签页满阈值之前保持
noindex,follow。 - prebuild 脚本对非法 hub 分配直接挂构建。
上线后验证
- Search Console → Performance → Pages:hub URL 应该在 4-8 周内开始累积 hub 级关键词曝光。
- 用 URL Inspection 抽查 hub URL——确认已收录且渲染出文章列表。
- sitemap 计数:
curl -s https://yourdomain.com/sitemap.xml | grep -c '<loc>'对得上预期。
容易踩的坑
- 搞
/hub/subhub/subsubhub/slug/的多级嵌套,一年内就要重构。检测:任何语言前缀后有 3+ 段路径。 - 让 hub 野蛮生长,最后变成 15 个 hub 每个只有 5 篇。检测:prebuild 脚本统计每 hub 文章数,低于 12 就告警。
- 把标签当 hub 用——搜索引擎更看重 hub。
- hub 中途改名却没做 301,所有积累的权重都丢了。必须改的话,先把 redirect 写进
firebase.json或vercel.json再发改名。 - 允许一篇文章同时属于多个 hub,最后要么 URL 重复要么 canonical 弱。
- 标签页每个不到 5 篇还放 index——Google 视为薄页。
FAQ
- 多少个 hub 算多: 独立站第一年超过 8 个 hub 基本都太多,每个 hub 都填不满。
- hub 页面要不要写内容: 要——大约 120 字介绍、子文章列表、FAQ 都要。空列表页会被算薄页。
- 标签有 SEO 价值吗: 更多是给读者用,SEO 价值有限。很多站把标签页
noindex,follow避免薄页问题。SEO 权重靠 hub。 - 我的方向真的需要子子栏目怎么办: 用扁平 URL,把层级放在面包屑和元数据里,不要写进 URL。
- subcategory 要进 URL 吗: 不要。subcategory 放 metadata 和面包屑,URL 保持单层。URL 改动最痛,metadata 改动最便宜。