如何使用 Cloudflare Workers 在 30 分钟内构建一个高性能博客

在这个教程中,我将带你一步步使用 Cloudflare Workers 构建一个完整的博客系统。无需服务器,无需复杂的配置,只需要一个 Cloudflare 账号和几行代码。

为什么选择 Cloudflare Workers?



Cloudflare Workers 是一个革命性的无服务器平台,它允许你在 Cloudflare 的全球边缘网络上运行 JavaScript 代码。这意味着:
  • 极速访问:代码运行在距离用户最近的 200+ 数据中心
  • 零运维:无需管理服务器,自动扩展
  • 成本低廉: generous 的免费额度,适合个人博客
  • 全球 CDN:内容自动分发到全球


  • 项目结构



    我们将创建一个简洁的博客系统,包含以下功能:
  • 文章列表页
  • 文章详情页
  • 标签分类
  • 响应式设计
  • RESTful API


  • text
    .
    ├── src/
    │   ├── index.js      # Worker 入口文件
    │   ├── router.js     # 路由处理器
    │   ├── data.js       # 数据层
    │   └── templates.js  # HTML 模板
    ├── wrangler.toml     # 部署配置
    └── package.json      # 项目配置


    第一步:初始化项目



    首先创建项目目录并初始化:

    bash
    mkdir cloudflare-worker-blog
    cd cloudflare-worker-blog
    npm init -y


    安装 Wrangler CLI 工具:

    bash
    npm install --save-dev wrangler


    创建 wrangler.toml 配置文件:

    toml
    name = "cloudflare-worker-blog"
    main = "src/index.js"
    compatibility_date = "2024-01-01"


    第二步:创建路由系统



    创建一个简单的路由处理器 src/router.js

    javascript
    export class Router {
      constructor() {
        this.routes = [];
      }
    
      get(path, handler) {
        this.routes.push({ method: 'GET', path, handler });
      }
    
      async handle(request) {
        const url = new URL(request.url);
        const method = request.method;
        const pathname = url.pathname;
    
        for (const route of this.routes) {
          if (route.method !== method) continue;
    
          const params = this.matchPath(route.path, pathname);
          if (params !== null) {
            return await route.handler(params);
          }
        }
        return null;
      }
    
      matchPath(routePath, actualPath) {
        if (!routePath.includes(':')) {
          return routePath === actualPath ? {} : null;
        }
    
        const routeParts = routePath.split('/').filter(Boolean);
        const actualParts = actualPath.split('/').filter(Boolean);
    
        if (routeParts.length !== actualParts.length) {
          return null;
        }
    
        const params = {};
        for (let i = 0; i < routeParts.length; i++) {
          if (routeParts[i].startsWith(':')) {
            params[routeParts[i].slice(1)] = decodeURIComponent(actualParts[i]);
          } else if (routeParts[i] !== actualParts[i]) {
            return null;
          }
        }
        return params;
      }
    }


    这个路由系统支持:
  • 静态路由(如 /about
  • 动态路由(如 /post/:slug
  • 参数提取


  • 第三步:准备博客数据



    创建 src/data.js,包含示例文章:

    javascript
    const SAMPLE_POSTS = [
      {
        id: 1,
        title: '开始使用 Cloudflare Workers',
        slug: 'getting-started-with-cloudflare-workers',
        excerpt: 'Cloudflare Workers 是一个强大的无服务器平台...',
        content: '<p>详细内容...</p>',
        author: '博客作者',
        date: '2024-01-15',
        tags: ['Cloudflare', 'Workers', 'Serverless'],
        readTime: 5
      },
      // 更多文章...
    ];
    
    export async function getPosts(env) {
      // 支持 KV 存储和内存存储
      if (env?.BLOG_KV) {
        const posts = await env.BLOG_KV.get('posts', 'json');
        if (posts) return posts;
      }
      return SAMPLE_POSTS;
    }
    
    export async function getPostBySlug(env, slug) {
      if (env?.BLOG_KV) {
        const post = await env.BLOG_KV.get(`post:${slug}`, 'json');
        if (post) return post;
      }
      return SAMPLE_POSTS.find(post => post.slug === slug) || null;
    }
    
    export async function getPostsByTag(env, tag) {
      const posts = await getPosts(env);
      return posts.filter(post => 
        post.tags.some(t => t.toLowerCase() === tag.toLowerCase())
      );
    }


    第四步:创建 HTML 模板



    创建 src/templates.js 来渲染页面:

    javascript
    function baseLayout(title, content) {
      return `<!DOCTYPE html>
    <html lang="zh-CN">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>${title}</title>
      <link rel="stylesheet" href="/style.css">
    </head>
    <body>
      <header>
        <nav>
          <a href="/" class="logo">我的博客</a>
          <ul class="nav-links">
            <li><a href="/">首页</a></li>
            <li><a href="/about">关于</a></li>
          </ul>
        </nav>
      </header>
      <main>${content}</main>
      <footer>
        <p>© ${new Date().getFullYear()} Cloudflare Worker 博客</p>
      </footer>
    </body>
    </html>`;
    }
    
    export function renderHomePage(posts) {
      const content = `
        <h1>最新文章</h1>
        <div class="post-list">
          ${posts.map(post => `
            <article class="post-item">
              <h2><a href="/post/${post.slug}">${post.title}</a></h2>
              <div class="post-meta">
                ${post.date} · ${post.author} · ${post.readTime}分钟阅读
              </div>
              <p>${post.excerpt}</p>
              <div class="post-tags">
                ${post.tags.map(tag => `
                  <a href="/tag/${tag}" class="tag">${tag}</a>
                `).join('')}
              </div>
            </article>
          `).join('')}
        </div>
      `;
      return baseLayout('首页', content);
    }
    
    export function renderPostPage(post) {
      const content = `
        <article class="post-detail">
          <h1>${post.title}</h1>
          <div class="post-meta">
            ${post.date} · ${post.author} · ${post.readTime}分钟阅读
          </div>
          <div class="post-content">${post.content}</div>
        </article>
      `;
      return baseLayout(post.title, content);
    }


    第五步:创建主入口文件



    创建 src/index.js,整合所有功能:

    javascript
    import { Router } from './router.js';
    import { renderHomePage, renderPostPage } from './templates.js';
    import { getPosts, getPostBySlug, getPostsByTag } from './data.js';
    
    export default {
      async fetch(request, env, ctx) {
        const router = new Router();
        
        // CSS 样式
        router.get('/style.css', () => {
          return new Response(CSS_CONTENT, {
            headers: { 'Content-Type': 'text/css' }
          });
        });
        
        // API 路由
        router.get('/api/posts', async () => {
          const posts = await getPosts(env);
          return jsonResponse(posts);
        });
        
        router.get('/api/posts/:slug', async (params) => {
          const post = await getPostBySlug(env, params.slug);
          if (!post) {
            return jsonResponse({ error: '文章不存在' }, 404);
          }
          return jsonResponse(post);
        });
        
        // 页面路由
        router.get('/', async () => {
          const posts = await getPosts(env);
          return htmlResponse(renderHomePage(posts));
        });
        
        router.get('/post/:slug', async (params) => {
          const post = await getPostBySlug(env, params.slug);
          if (!post) {
            return htmlResponse('<h1>404</h1>', 404);
          }
          return htmlResponse(renderPostPage(post));
        });
        
        router.get('/tag/:tag', async (params) => {
          const posts = await getPostsByTag(env, params.tag);
          return htmlResponse(renderHomePage(posts, params.tag));
        });
        
        const response = await router.handle(request);
        if (response) return response;
        
        return htmlResponse('<h1>404 - 页面不存在</h1>', 404);
      }
    };
    
    function jsonResponse(data, status = 200) {
      return new Response(JSON.stringify(data), {
        status,
        headers: {
          'Content-Type': 'application/json',
          'Access-Control-Allow-Origin': '*'
        }
      });
    }
    
    function htmlResponse(html, status = 200) {
      return new Response(html, {
        status,
        headers: { 'Content-Type': 'text/html;charset=UTF-8' }
      });
    }
    
    // CSS 样式内容
    const CSS_CONTENT = `
    /* 你的 CSS 样式 */
    body { font-family: system-ui, sans-serif; line-height: 1.6; }
    .container { max-width: 800px; margin: 0 auto; padding: 0 20px; }
    /* ... 更多样式 */
    `;


    第六步:本地测试



    在部署之前,先在本地测试:

    bash
    npx wrangler dev


    这将启动本地开发服务器,通常在 http://localhost:8787

    访问以下地址测试:
  • http://localhost:8787/ - 首页
  • http://localhost:8787/api/posts - API 接口
  • http://localhost:8787/post/getting-started-with-cloudflare-workers - 文章详情


  • 第七步:部署到 Cloudflare



    首先登录 Cloudflare:

    bash
    npx wrangler login


    这会在浏览器中打开认证页面,登录你的 Cloudflare 账号。

    然后部署:

    bash
    npx wrangler deploy


    部署成功后,你会看到类似这样的输出:

    text
    Uploaded cloudflare-worker-blog
    Deployed cloudflare-worker-blog triggers
      https://cloudflare-worker-blog.your-account.workers.dev


    🎉 恭喜!你的博客已经上线!

    进阶:使用 KV 存储



    要支持动态添加文章,可以配置 Cloudflare KV:
  • 在 Cloudflare Dashboard 创建 KV 命名空间
  • 更新 wrangler.toml


  • toml
    [[kv_namespaces]]
    binding = "BLOG_KV"
    id = "your-kv-namespace-id"
  • 重新部署:


  • bash
    npx wrangler deploy


    总结



    通过本教程,你学会了:
  • ✅ 使用 Cloudflare Workers 构建 Web 应用
  • ✅ 创建自定义路由系统
  • ✅ 渲染 HTML 模板
  • ✅ 提供 RESTful API
  • ✅ 部署到全球边缘网络


  • 这个博客系统虽然简单,但具备了核心功能。你可以在此基础上继续扩展:
  • 添加评论系统(使用 KV 或 D1 数据库)
  • 实现文章管理后台
  • 添加搜索功能
  • 集成 Markdown 渲染
  • 添加 RSS 订阅


  • Cloudflare Workers 的潜力无限,期待你创造出更多精彩的应用!




    相关链接:
  • Cloudflare Workers 文档
  • Wrangler CLI 文档
  • KV 存储文档


  • 完整代码: [GitHub 仓库链接]

    💬 评论区 (0)

    暂无评论,快来抢沙发吧!