Headless CMS 实战指南:解耦架构下的内容管理新范式

欣怡 ☘︎ 框架 阅读 589
赞 18 收藏
二维码
手机扫码查看
反馈

先上手再问为什么:Headless CMS 真的香

我第一次接触 Headless CMS 是在去年一个紧急项目里,客户非要在后台自己改文案,但又死活不同意用 WordPress。当时我第一反应是“这不就是个 API 写死的页面嘛”,结果被产品经理一句话怼回来:“你写死了,人家怎么改?” 无奈之下,我花了一晚上试了几个主流 Headless CMS,最后选了 Strapi(后来也试过 Contentful、Sanity),亲测有效,真香。

Headless CMS 实战指南:解耦架构下的内容管理新范式

别管什么“无头架构”“内容即服务”这些高大上的词,咱就看最实际的:怎么从 CMS 拿数据,怎么渲染到页面上。下面这段代码,是我现在项目里最常用的 fetch 方式:

const API_URL = 'https://jztheme.com/api';

async function fetchBlogPosts() {
  try {
    const res = await fetch(${API_URL}/posts?populate=*);
    const data = await res.json();
    return data.data;
  } catch (error) {
    console.error('Failed to fetch posts:', error);
    return [];
  }
}

注意那个 populate=*,这是 Strapi v4 的坑点——默认不返回关联字段,比如文章的作者、封面图这些,必须显式 populate。我一开始没加,页面全是空的,折腾了半天才发现是这个原因。如果你用的是 Contentful,那它的 GraphQL 查询更灵活,但学习成本略高。

这个场景最好用:动态路由 + 静态生成

我现在做营销页、博客、产品文档这类内容型站点,基本都用 Next.js + Headless CMS 的组合。Next.js 的 getStaticPaths + getStaticProps 简直是为这种场景量身定做的。举个例子,生成所有博客详情页:

// pages/posts/[slug].js
export async function getStaticPaths() {
  const posts = await fetchBlogPosts();
  const paths = posts.map(post => ({
    params: { slug: post.attributes.slug }
  }));
  return { paths, fallback: 'blocking' };
}

export async function getStaticProps({ params }) {
  const res = await fetch(${API_URL}/posts?filters[slug][$eq]=${params.slug}&populate=*);
  const data = await res.json();
  const post = data.data[0];
  if (!post) return { notFound: true };
  return { props: { post } };
}

这里我用 fallback: 'blocking' 而不是 false,是因为 CMS 里可能随时新增文章,如果设成 false,新文章的 URL 就会 404。而 blocking 会在首次访问时动态生成页面,虽然慢一点,但胜在省心。亲测有效,上线后没再因为漏跑构建脚本被 PM 叫去开会。

踩坑提醒:这三点一定注意

Headless CMS 用起来爽,但有几个坑我踩过不止一次,分享出来帮你省点时间:

  • 图片处理别直接扔 CDN 链接:很多 CMS 会返回原始图片 URL,比如 Strapi 默认给的是本地路径。如果你部署在 Vercel 上,直接用这个路径会 404。建议要么配好 public folder,要么用 Cloudinary 这类服务做中转。我现在的做法是在构建时把图片上传到 S3,然后替换 URL——虽然麻烦,但稳定。
  • 权限别开太宽:Strapi 的 Public 角色默认只能读 content-type,但如果你自定义了接口,很可能忘了设置权限。有一次我写了个统计接口,结果没设权限,导致任何人都能调用,差点被刷爆。上线前务必检查 Settings → Users & Permissions Plugin → Roles。
  • 本地开发别硬连线上 CMS:我见过同事直接在 dev 环境连生产 CMS,结果一不小心把测试数据写进去了。现在我的习惯是:.env.local 里放测试环境 API,.env.production 放正式环境。而且测试环境 CMS 会定期清空,避免污染。

高级技巧:用 GraphQL 精准取数(适合 Contentful)

如果你用的是 Contentful,强烈建议上 GraphQL。它比 REST 灵活太多,尤其当你有嵌套内容(比如文章里嵌 Banner,Banner 里又有 CTA 按钮)时,REST 得发好几轮请求,GraphQL 一次搞定。下面是我常用的一个 query:

const query = 
  query GetPost($slug: String!) {
    postCollection(where: { slug: $slug }) {
      items {
        title
        body
        heroImage {
          url
          description
        }
        author {
          name
          avatar {
            url
          }
        }
      }
    }
  }
;

async function fetchPostBySlug(slug) {
  const res = await fetch('https://graphql.contentful.com/content/v1/spaces/your-space-id', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: Bearer ${process.env.CONTENTFUL_ACCESS_TOKEN}
    },
    body: JSON.stringify({ query, variables: { slug } })
  });
  const data = await res.json();
  return data.data.postCollection.items[0];
}

注意:Contentful 的 access token 分 CDA(内容交付)和 CPA(内容预览),别搞混了。CDA 用于生产环境,CPA 用于预览草稿内容。我一般在 preview 模式下切 CPA,方便编辑实时看效果。

不是银弹,但够用

Headless CMS 并不是万能的。比如你要做复杂的用户交互(评论、点赞、登录态内容),它就帮不上忙,还得自己搭后端。但如果你只是需要一个“内容后台”,让非技术人员能改文案、换图、发文章,那它绝对是最省事的方案。我现在的项目里,80% 的页面都靠它驱动,剩下 20% 复杂逻辑才写 API。

另外,别迷信“完全无头”。有些 CMS 其实提供了可视化编辑器(比如 Sanity 的 Studio),体验比传统 WordPress 还要好,编辑能直接在页面上改文字,所见即所得。这种 hybrid 模式我觉得很实用,既保留了灵活性,又降低了使用门槛。

以上是我踩坑后的总结,希望对你有帮助。这个技术的拓展用法还有很多(比如多语言支持、内容版本回滚、Webhook 自动触发构建),后续会继续分享这类博客。有更优的实现方式欢迎评论区交流,比如你们是怎么处理图片优化的?或者有没有试过 DatoCMS?

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论