之前的文章:

  1. Cloudflare workers不完全指南"
  2. Cloudflare Workers实战(一):随心所欲操作客户端请求
  3. Cloudflare Workers实战(二):动态修改后端响应
  4. Cloudflare Workers实战(三):实现认证、重定向与缓存
  5. Cloudflare Workers实战(四):托管和分发静态文件
  6. Cloudflare Workers实战(五):不止JavaScript,拥抱Python与Rust

前面几篇文章,咱们研究了Workers的各种功能。今天,我要跟你聊点更实用的——用Cloudflare Workers的Static Assets功能来托管网站。你可能会想:“托管网站?这不是有GitHub Pages、Vercel这些免费方案吗?”

莫急,听我娓娓道来,告诉你为啥Workers + Static Assets可能是你见过最香的免费托管方案!

其实还有一些免费的云服务器方案,比如aws,ClawCloud Run等免费的云计算服务,使用服务器的最大好处就是自由度高,缺点就是自由度高导致的维护问题,服务器挂了咋办?服务器死机了咋办?被入侵了咋办?总的来说,你得会点运维知识,才能玩转云服务器,当然了,有AI的帮助就没那么困难了,不过最好还是得具备点运维知识最好,如果你不想运维自己的服务器咋整?又不想花钱,就想写个代码然后就可以简单部署起来,咋整?白嫖赛博佛祖呀。。。

免费托管方案对比

在开始之前,咱们先来对比一下,这样显得文章很全面不是。

其实我只用过GitHub的Pages,其他没用过,优缺点是查文档来的,所以可能不一定准确。

GitHub Pages:老牌选手,稳定但有限制

万能交友网站GitHub

优点

  • 完全免费,和GitHub仓库无缝集成
  • 部署简单
  • 支持自定义域名

缺点

  • 只支持静态网站,不能跑服务端代码
  • 国内访问速度…你懂的(除了这个,其他其实还好)
  • 每月100GB流量限制
  • 仓库大小限制1GB

Vercel:现代化

现在热点比较高的前端部署方案,超级多的开源项目使用这个框架,我之前也clone了几个并部署,用起来还可以。

优点

  • 支持Next.js、React等现代框架
  • 全球CDN,速度不错
  • 支持Serverless Functions
  • 部署体验极佳

缺点

  • 免费版每月100GB流量,超了就要付费
  • Serverless Functions执行时间有限制
  • 商业项目需要付费

Netlify:功能丰富,可能吧

没用过,但是三个显得丰富不是?哈哈。。。

优点

  • 功能强大,支持表单处理、身份验证等
  • 部署简单,Git集成好
  • 有一定的Serverless支持

缺点

  • 免费版每月100GB流量
  • 构建时间有限制
  • 高级功能需要付费

Cloudflare Workers + Static Assets:新晋黑马(也许不新)

之前了解过,但是不深,仔细研究一下之后发现,卧槽,牛逼,虽然JS我写的不好,但是AI写得好呀,然后我就彻底的爱上了cloudflare workers。

优点

  • 免费额度超级大:每天10万次请求,每月1000GB流量!
  • 全球CDN:Cloudflare的边缘网络,速度飞快(虽然国内不那么快,但是总体是能访问滴)
  • 可编程:不只是静态托管,还能跑Worker代码(可以理解成无服务函数Serverless Functions)
  • 域名友好:自带.workers.dev域名,也支持自定义域名, 如果你有域名在cf托管的话,非常方便

缺点

  • 相对较新,生态还在完善(应该吧,因为之前有Cloudflare Pages)
  • 需要一点学习成本,主要是理解Worker的代码,如果你研究过openresty或apisix的lua代码的话,你会很容易理解worker代码能做的事情。

快速入门:5分钟搭建你的第一个网站

假设你已经有了一个静态网站(比如用Vue、React或者纯HTML写的),现在要把它部署到Workers上。

第一步:准备你的静态文件

首先,确保你的静态文件都在一个文件夹里,比如distbuild。目录结构大概是这样:

my-website/
├── dist/
│   ├── index.html
│   ├── about.html
├── src/
│   ├── index.js
└── wrangler.toml

index.htmlabout.html的内容分别是index pageabout,你可以根据自己的需要修改。

第二步:配置wrangler.toml

在项目根目录创建wrangler.toml文件:

name = "my-awesome-website"
main = "src/index.js"
compatibility_date = "2024-01-01"

# 核心配置:指定静态资源目录
[assets]
directory = "./dist"
# 如果是SPA应用,开启这个选项
# not_found_handling = "single-page-application"

第三步:创建Worker代码(可选但推荐)

创建src/index.js文件:

export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);
    
    // 如果是API请求,我们自己处理
    // 这里主要是为了测试,真是情况下我们可以请求真实的后端,或者免费的API来做请求代理
    if (url.pathname.startsWith('/api/')) {
      return new Response(JSON.stringify({
        message: 'Hello from Worker API!',
        timestamp: new Date().toISOString(),
        path: url.pathname
      }), {
        headers: { 'Content-Type': 'application/json' }
      });
    }
    
    // 其他请求交给Static Assets处理
    return env.ASSETS.fetch(request);
  },
};

第四步:测试!

wrangler dev

输出如下:


 ⛅️ wrangler 4.25.0 (update available 4.26.0)
─────────────────────────────────────────────
Your Worker has access to the following bindings:
Binding            Resource      Mode
env.ASSETS         Assets        local

╭──────────────────────────────────────────────────────────────────────╮
[b] open a browser [d] open devtools [c] clear console [x] to exit  │
╰──────────────────────────────────────────────────────────────────────╯
⎔ Starting local server...
[wrangler:info] Ready on http://localhost:18787

我们可以使用curl命令测试

➜  ~ curl 127.0.0.1:8787
index page
➜  ~ curl 127.0.0.1:8787/about
about
➜  ~

可以发现,index.html直接对于/, about.html直接对于/about, 不需要.html的后缀,这和nginx, apache之类的web服务器的处理逻辑一致。

第五步:部署!

# 如果还没安装wrangler的话
npm install -g wrangler

# 登录Cloudflare
wrangler login

# 部署
wrangler deploy

输出如下


 ⛅️ wrangler 4.25.0 (update available 4.26.0)
─────────────────────────────────────────────
🌀 Building list of assets...
✨ Read 2 files from the assets directory /Users/xxxx/workspace/cf-workers/dist
🌀 Starting asset upload...
🌀 Found 2 new or modified static assets to upload. Proceeding with upload...
+ /about.html
+ /index.html
Uploaded 1 of 2 assets
Uploaded 2 of 2 assets
✨ Success! Uploaded 2 files (1.67 sec)

Total Upload: 0.52 KiB / gzip: 0.34 KiB
Your Worker has access to the following bindings:
Binding            Resource
env.ASSETS         Assets

Uploaded demo (14.88 sec)
Deployed demo triggers (2.13 sec)
  https://demo.{你的cf账户}.workers.dev
Current Version ID: e40d6335-bc07-48e1-a28d-33de515efa3c

部署成功后,你会得到一个类似https://demo.your-subdomain.workers.dev的地址。访问它,你的网站就活了!

进阶技巧:run_worker_first参数的妙用

默认情况下,Cloudflare会先尝试匹配静态文件,匹配不到才会执行Worker代码,也就是说,如果上面的目录dist存在/api/index.html路径的话, 访问/api路径会直接请求静态文件,不会执行worker代码,。但有时候,我们希望所有请求都先经过Worker处理,比如做一些统一的认证、日志记录,劫持请求和响应等。

这时候,run_worker_first参数就派上用场了, 修改assets段落就行:

[assets]
directory = "./dist"
# 关键配置:让Worker先执行
run_worker_first = true

启用这个选项后,每个请求都会先到达你的Worker代码。这样你就可以在Worker里做各种预处理,然后再决定是返回静态文件还是自定义响应:

export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);
    
    // 统一添加安全头
    const securityHeaders = {
      'X-Content-Type-Options': 'nosniff',
      'X-Frame-Options': 'DENY',
      'X-XSS-Protection': '1; mode=block'
    };
    
    // 简单的访问控制
    const userAgent = request.headers.get('User-Agent') || '';
    if (userAgent.includes('BadBot')) {
      return new Response('Access Denied', { status: 403 });
    }
    
    // 获取静态资源响应
    const response = await env.ASSETS.fetch(request);
    
    // 给响应添加安全头
    const newResponse = new Response(response.body, response);
    Object.entries(securityHeaders).forEach(([key, value]) => {
      newResponse.headers.set(key, value);
    });
    
    return newResponse;
  },
};

Hono框架:让Worker开发更优雅

虽然原生的Worker API已经很强大了,但如果你想要更优雅的开发体验,我强烈推荐Hono这个轻量级Web框架。它专门为边缘运行时设计,API简洁,性能出色,并且提供了丰富的中间件生态。

为啥需要一个web框架?因为将所有代码写在一个文件实在是不太优雅。。。

安装Hono

npm install hono

用Hono重写我们的Worker

import { Hono } from 'hono';

const app = new Hono();

// API路由
app.get('/api/hello', (c) => {
  return c.json({
    message: 'Hello from Hono!',
    timestamp: new Date().toISOString()
  });
});

app.get('/api/user/:id', (c) => {
  const id = c.req.param('id');
  return c.json({
    id: id,
    name: `User ${id}`,
    email: `user${id}@example.com`
  });
});

// 静态文件处理
app.get('*', (c) => {
  return c.env.ASSETS.fetch(c.req.raw);
});


export default app;

看到没?用Hono写路由比原生API清爽多了!而且它还提供了很多实用的中间件。

在上面的基础上我们可以将worker的代码拆分成一个个文件,比如下面这样。

my-website/
├── dist/
│   ├── index.html
│   ├── about.html
├── src
│   ├── index.js
│   └── routes
│       ├── hello.js
│       └── user.js
└── wrangler.toml

中间件的魔法:给静态网站注入动态元素

Hono的中间件系统非常强大,我们可以用它来给静态网站注入各种动态元素,比如meta, script, style, footer等各种html元素。

如果你跟我上面的about页面一样的话,那么注入不的哦,因为它不是一个合法的html文件,所以要想下面的代码成功,你需要将上面的index.html, about.html等文件都改成html文件,而不是一个简单的文本文件。

咱们来看几个实际的例子:

示例1:自动注入Google Analytics的JS引入文件

你当然也可以注入Bing的Clarity脚本,或者其他的脚本。

import { Hono } from 'hono';

const app = new Hono();

// 中间件:使用HTMLRewriter给HTML页面注入GA代码
app.use('*', async (c, next) => {
  await next();

  const contentType = c.res.headers.get('Content-Type') || '';
  if (contentType.includes('text/html')) {
    // 使用HTMLRewriter进行高效的HTML处理
    const rewriter = new HTMLRewriter()
      .on('head', {
        element(element) {
          // 在head标签内注入Google Analytics代码
          element.append(`
            <script async src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"></script>
            <script>
              window.dataLayer = window.dataLayer || [];
              function gtag(){dataLayer.push(arguments);}
              gtag('js', new Date());
              gtag('config', 'GA_MEASUREMENT_ID');
            </script>
          `, { html: true });
        }
      });

    // 应用HTMLRewriter转换
    c.res = rewriter.transform(c.res);
  }
});

// 静态文件处理
app.get('*', (c) => {
  return c.env.ASSETS.fetch(c.req.raw);
});

export default app;

示例2:根据地理位置注入不同的CSS主题

import { Hono } from 'hono';

const app = new Hono();

// 中间件:使用HTMLRewriter根据用户地理位置注入不同主题
app.use('*', async (c, next) => {
  await next();
  
  const contentType = c.res.headers.get('Content-Type') || '';
  if (contentType.includes('text/html')) {
    // 获取用户地理位置信息
    const country = c.req.raw.cf?.country || 'US';
    
    // 根据国家选择不同的主题CSS文件
    let themeCSSHref = '/css/theme-default.css';
    if (country === 'CN') {
      themeCSSHref = '/css/theme-red.css';
    } else if (country === 'JP') {
      themeCSSHref = '/css/theme-sakura.css';
    }
    
    // 使用HTMLRewriter注入主题CSS
    const rewriter = new HTMLRewriter()
      .on('head', {
        element(element) {
          // 在head标签内注入主题CSS链接
          element.append(
            `<link rel="stylesheet" href="${themeCSSHref}">`, 
            { html: true }
          );
        }
      });
    
    // 应用HTMLRewriter转换
    c.res = rewriter.transform(c.res);
  }
});

// 静态文件处理
app.get('*', (c) => {
  return c.env.ASSETS.fetch(c.req.raw);
});


export default app;

示例3:动态注入页脚信息

import { Hono } from 'hono';

const app = new Hono();

// 中间件:使用HTMLRewriter注入动态页脚
app.use('*', async (c, next) => {
  await next();
  
  const contentType = c.res.headers.get('Content-Type') || '';
  if (contentType.includes('text/html')) {
    // 获取当前时间和用户信息
    const now = new Date().toISOString();
    const userIP = c.req.header('CF-Connecting-IP') || 'unknown';
    const userCountry = c.req.raw.cf?.country || 'unknown';
    const userCity = c.req.raw.cf?.city || 'unknown';
    
    // 使用HTMLRewriter在body结束前注入动态页脚
    const rewriter = new HTMLRewriter()
      .on('body', {
        element(element) {
          // 在body标签结束前注入动态页脚
          element.append(`
            <footer style="background: #f5f5f5; padding: 20px; text-align: center; margin-top: 50px;">
              <p>页面生成时间: ${now}</p>
              <p>访问来源: ${userCity}, ${userCountry}</p>
              <p>由 <a href="https://workers.cloudflare.com/" target="_blank">Cloudflare Workers</a> 强力驱动</p>
            </footer>
          `, { html: true });
        }
      });
    
    // 应用HTMLRewriter转换
    c.res = rewriter.transform(c.res);
  }
});

// 静态文件处理
app.get('*', (c) => {
  return c.env.ASSETS.fetch(c.req.raw);
});


export default app;

上面就是Cloudflare Workers + Static Assets的强大之处。下面简单总结下:

  1. 免费额度超大:每天10万次请求,每月1000GB流量,对个人项目来说几乎用不完
  2. 性能出色:全球CDN + 边缘计算,访问速度飞快(有的地区没那么快哈)
  3. 高度可定制:不只是静态托管,还能跑自定义逻辑,相当于可以代理编程,类似openresty的lua代码
  4. 开发体验好:配合Hono框架,开发效率更高,更优雅
  5. 部署简单:一行命令搞定,支持自定义域名(可以直接在配置文件中定义, 部署的时候cf会自动配置)

当然了,这个方案肯定不是万能的。如果你的项目需要复杂的后端逻辑、特定数据库操作(比如mysql,postgresql等),或者有特殊的运行时要求,传统的服务器方案可能更合适,想迁移现有的代码还是很难的,但是使用各种免费的服务,比如KV,R2等,可以写出很完善的服务了。

最重要的是,它免费! 还功能强大,真想给赛博佛祖上柱香!