告别繁琐,拥抱自由:使用 Sanity.io 轻松搭建你的现代化博客

对于许多开发者来说,拥有一个属于自己的博客是分享知识、记录成长的最佳方式。比如说我在掘金上发的文章,刀乐都让掘金赚了,搭建一个博客有助于提升我们程序员的影响力。

今天,我们将介绍一个解决方案:使用 Sanity.io 这个 Headless CMS,结合前端框架 SolidStart,来构建一个属于你自己的博客。

先看看Demo

什么是 Sanity.io

Sanity.io 是一个现代化的、可定制的 Headless 内容管理系统(CMS)。就是说它提供可视化管理端来管理我们数据库的东西。这意味着你可以使用任何你喜欢的前端技术(如 Next.js, React, Vue 等)来构建你的网站或应用,并通过 API 从 Sanity 获取内容。

Sanity 的主要优势包括:

  • 高度灵活的内容建模:你可以通过简单的 JavaScript 对象来定义你想要的内容结构(Schema),完全掌控你的数据模型,如下我定义了一个作者表
js 复制代码
import {defineField, defineType} from 'sanity'

export default defineType({
  name: 'author',
  title: 'Author',
  type: 'document',
  fields: [
    defineField({
      name: 'name',
      title: 'Name',
      type: 'string',
    }),
    defineField({
      name: 'slug',
      title: 'Slug',
      type: 'slug',
      options: {
        source: 'name',
        maxLength: 96,
      },
    }),
    defineField({
      name: 'image',
      title: 'Image',
      type: 'image',
      options: {
        hotspot: true,
      },
    }),
    defineField({
      name: 'bio',
      title: 'Bio',
      type: 'array',
      of: [
        {
          title: 'Block',
          type: 'block',
          styles: [{title: 'Normal', value: 'normal'}],
          lists: [],
        },
      ],
    }),
  ],
  preview: {
    select: {
      title: 'name',
      media: 'image',
    },
  },
})
  • 优秀的编辑体验:Sanity Studio 是一个基于 React 的可视化编辑器,你可以对其进行深度定制,以满足你的编辑需求。
  • 强大的查询语言 (GROQ) :Sanity 弄了一种新的查询语言 GROQ,让你可以精确、高效地获取所需数据,如下是通过id查询一篇文章的信息
js 复制代码
export async function getPostBySlug(slug: string): Promise<Post | null> {
  const query = `*[_type == "post" && slug.current == $slug][0] {
    _id,
    title,
    slug,
    excerpt,
    body,
    publishedAt,
    mainImage,
    "categories": categories[]->{title},
    "tags": tags[]->{title, color},
    "author": author->{
      name,
      image,
      bio
    }
  }`;
  const params = { slug };
  return await client.fetch(query, params);
}
  • 慷慨的免费套餐:每月提供 100GB 的流量和 1M 的 API CDN 请求,对于个人博客和小型项目来说完全足够。
  • 丰富的生态:sanity官方和社区提供了大量的编辑器插件,比如有markdown,流媒体上传,还有国际化插件。

我的博客实现

技术选型:

  • 内容后端: Sanity.io (Headless CMS)
  • 核心框架: SolidStart (服务端渲染)
  • UI/样式: Tailwind CSS & daisyUI

项目亮点:

  • 高性能架构: 采用 SolidStart 框架,充分利用其出色的服务端渲染(SSR)能力,为博客提供极致的加载速度和流畅的交互体验。

  • 灵活的双模内容策略:

    • Portable Text (blockContent): 它基于 JSON 的结构化特性,允许在文章中嵌入复杂的自定义组件,实现了内容与表现的彻底解耦。
    • Markdown (markdownContent): 保留对传统 Markdown 的支持,专为技术文章设计。此举旨在简化向外部技术论坛(如掘金、GitHub)的同步发布流程,优化了我的工作流。
  • 高效的前端实现:

    • UI 层面,daisyUI 作为 Tailwind CSS 的组件库,在保证开发灵活性的同时,快速构建出风格统一且富有趣味性的界面。
    • 前端使用 markdown-it 库高效解析 Markdown 文本,并结合 Tailwind CSS 进行精准的样式渲染。
js 复制代码
import MarkdownIt from 'markdown-it';
import highlightjs from 'markdown-it-highlightjs';

const getLineHighlightClass = (line) => {
  const keywordMap = {
    'error': 'bg-error text-error-content',
    'warning': 'bg-warning text-warning-content',
    'success': 'bg-success text-success-content',
    'installed': 'bg-success text-success-content',
    'info': 'bg-info text-info-content',
    'note': 'bg-info text-info-content',
  };
  const lowerLine = line.toLowerCase();
  for (const keyword in keywordMap) {
    if (lowerLine.includes(keyword)) {
      return ` class="${keywordMap[keyword]}"`;
    }
  }
  return '';
};

const customFenceRenderer = (md) => (tokens, idx) => {
  const token = tokens[idx];
  const code = token.content.trim();
  const language = token.info || '';
  const lines = code.split('\n');

  const languageBadge = language ? `<div class="badge badge-sm badge-neutral absolute right-2 top-2">${language}</div>` : '';
  const codeLines = lines.map((line, index) =>
    `<pre data-prefix="${index + 1}"${getLineHighlightClass(line)}><code>${md.utils.escapeHtml(line)}</code></pre>`
  ).join('');

  return `<div class="mockup-code w-full">${languageBadge}${codeLines}</div>`;
};

const enhanceRule = (md, ruleName, modifier) => {
  const defaultRender = md.renderer.rules[ruleName] || ((tokens, idx, options, env, self) => self.renderToken(tokens, idx, options));
  md.renderer.rules[ruleName] = (tokens, idx, options, env, self) => {
    modifier(tokens[idx]);
    return defaultRender(tokens, idx, options, env, self);
  };
};

const md = new MarkdownIt({
  html: true,
  breaks: true,
  linkify: true,
  typographer: true,
}).use(highlightjs, { inline: true });

md.renderer.rules.fence = customFenceRenderer(md);
md.renderer.rules.blockquote_open = () => '<div class="alert my-4"><div><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="stroke-info h-6 w-6 shrink-0"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg><span>';
md.renderer.rules.blockquote_close = () => '</span></div></div>';
md.renderer.rules.table_open = () => '<div class="overflow-x-auto my-4"><table class="table table-zebra w-full">';
md.renderer.rules.table_close = () => '</table></div>';

enhanceRule(md, 'link_open', (token) => {
  token.attrSet('class', 'link link-primary');
  if (token.attrGet('href')?.startsWith('http')) {
    token.attrSet('target', '_blank');
    token.attrSet('rel', 'noopener noreferrer');
  }
});

enhanceRule(md, 'image', (token) => {
  token.attrSet('class', 'rounded-lg shadow-lg max-w-full h-auto my-4');
});

export function renderMarkdown(content) {
  return md.render(content || '');
}
相关推荐
Kiri霧14 分钟前
Kotlin比较接口
android·java·前端·微信·kotlin
LinXunFeng15 分钟前
Flutter - 聊天面板库动画生硬?这次让你丝滑个够
前端·flutter·github
Kiri霧35 分钟前
Kotlin抽象类
android·前端·javascript·kotlin
ai小鬼头1 小时前
创业小公司如何低预算打造网站?熊哥的实用建站指南
前端·后端
阿星做前端1 小时前
聊聊前端请求拦截那些事
前端·javascript·面试
阿凤211 小时前
在UniApp中防止页面上下拖动的方法
前端·uni-app
拾光拾趣录1 小时前
DocumentFragment:高性能DOM操作
前端·dom
归于尽2 小时前
从JS到TS:我们放弃了自由,却赢得了整个世界
前端·typescript
palpitation972 小时前
Fitten Code使用体验
前端
byteroycai2 小时前
用 Tauri + FFmpeg + Whisper.cpp 从零打造本地字幕生成器
前端