Skip to content

Commit a08eb75

Browse files
authored
feat: seo friendly (#27)
* feat: add seo service support * add seo service * add env var S3_CACHE_FOLDER * add set secret in Github Action support * docs: Add SEO doc & Update deploy & env docs * feat: add seo service support * add seo service * add env var S3_CACHE_FOLDER * add set secret in Github Action support * docs: Add SEO doc & Update deploy & env docs * ci: add env for deploy.yaml
1 parent 55b538b commit a08eb75

File tree

12 files changed

+351
-34
lines changed

12 files changed

+351
-34
lines changed

.github/workflows/deploy.yaml

+9
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,19 @@ jobs:
2828
env: # Or as an environment variable
2929
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
3030
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
31+
S3_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}
32+
S3_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }}
33+
JWT_SECRET: ${{ secrets.JWT_SECRET }}
34+
3135
DB_NAME: ${{ vars.DB_NAME }}
3236
WORKER_NAME: ${{ vars.WORKER_NAME }}
3337
FRONTEND_URL: ${{ vars.FRONTEND_URL }}
38+
S3_ACCESS_HOST: ${{ vars.S3_ACCESS_HOST }}
39+
S3_BUCKET: ${{ vars.S3_BUCKET }}
40+
S3_CACHE_FOLDER: ${{ vars.S3_CACHE_FOLDER }}
41+
S3_ENDPOINT: ${{ vars.S3_ENDPOINT }}
3442
S3_FOLDER: ${{ vars.S3_FOLDER }}
43+
S3_REGION: ${{ vars.S3_REGION }}
3544
run: |
3645
cd Rin/
3746
bun i

.github/workflows/seo.yaml

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: SEO Workflow
2+
3+
on:
4+
schedule:
5+
- cron: "0 * * * *"
6+
workflow_dispatch:
7+
8+
9+
jobs:
10+
build:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Clone Rin repository
15+
uses: actions/checkout@v4
16+
with:
17+
path: Rin
18+
19+
- name: Set up Node.js
20+
uses: actions/setup-node@v3
21+
with:
22+
node-version: 21
23+
24+
- name: Set up Bun
25+
uses: oven-sh/setup-bun@v1
26+
27+
- name: Set up Puppeteer
28+
run: |
29+
sudo apt-get update
30+
sudo apt-get install -y libgbm-dev
31+
32+
- name: Run script
33+
env: # Or as an environment variable
34+
S3_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}
35+
S3_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }}
36+
S3_REGION: ${{ vars.S3_REGION }}
37+
S3_ENDPOINT: ${{ vars.S3_ENDPOINT }}
38+
SEO_BASE_URL: ${{ vars.SEO_BASE_URL }}
39+
SEO_CONTAINS_KEY: ${{ vars.SEO_CONTAINS_KEY }}
40+
S3_ACCESS_HOST: ${{ vars.S3_ACCESS_HOST }}
41+
S3_BUCKET: ${{ vars.S3_BUCKET }}
42+
S3_CACHE_FOLDER: ${{ vars.S3_CACHE_FOLDER }}
43+
run: |
44+
cd Rin/
45+
bun i
46+
bun scripts/render.ts

docs/DEPLOY.md

+34-16
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# 部署
22

3+
## 更新日志
4+
### v0.2.0 2024-06-07 更新
5+
* 新增 `S3_CACHE_FOLDER` 环境变量
6+
* 环境变量加密列表与变量列表更新,仅保留必须加密的环境变量
7+
* 加密变量现在可以通过 Github 直接配置
8+
* Github 变量配置更新,新增必须通过 Github 配置的加密变量(S3 存储,用于 SEO 索引保存)
9+
10+
311
## 其他文档
412
[环境变量列表](./ENV.md)
513

@@ -51,7 +59,7 @@ NAME=Xeu # 昵称,显示在左上角
5159
DESCRIPTION=杂食动物 # 个人描述,显示在左上角昵称下方
5260
AVATAR=https://avatars.githubusercontent.com/u/36541432 # 头像地址,显示在左上角
5361
API_URL=https://rin.xeu.life # 服务端域名,可以先留空后面再改
54-
PAGE_SIZE=10 # 默认分页大小
62+
PAGE_SIZE=5 # 默认分页大小,推荐 5
5563
SKIP_DEPENDENCY_INSTALL=true
5664
UNSTABLE_PRE_BUILD=asdf install bun latest && asdf global bun latest && bun i
5765
```
@@ -97,34 +105,44 @@ ID 随意点击一个自己绑定的域名,进入后在右侧(需要向下
97105
CLOUDFLARE_ACCOUNT_ID=<你的用户ID>
98106
CLOUDFLARE_API_TOKEN=<你的令牌>
99107
```
108+
100109
同时你可以在`Actions secrets and variables``Variables` 中创建以下变量:
101110
```ini
102111
DB_NAME=<数据库名称,默认rin>
103112
WORKER_NAME=<Cloudflare Worker 名称,默认rin-server>
104113
FRONTEND_URL=<前端地址,用于Webhook通知时拼接地址,可不填>
105-
S3_FOLDER=<S3 图片资源存储的文件夹,默认为images/>
114+
SEO_BASE_URL=<SEO 基础地址,用于 SEO 索引,默认为 FRONTEND_URL>
115+
SEO_CONTAINS_KEY=<SEO 索引时只索引以 SEO_BASE_URL 开头或包含SEO_CONTAINS_KEY 关键字的链接,默认为空>
116+
S3_FOLDER=<S3 图片资源存储的文件夹,默认为 'images/'>
117+
S3_CACHE_FOLDER=<S3 缓存文件夹(用于 SEO、高频请求缓存),默认为 'cache/'>
118+
S3_BUCKET=<S3 存储桶名称>
119+
S3_REGION=<S3 存储桶所在区域,如使用 Cloudflare R2 填写 auto 即可>
120+
S3_ENDPOINT=<S3 存储桶接入点地址>
121+
S3_ACCESS_HOST=<S3 存储桶访问地址,末尾无'/'>
106122
```
123+
> [!TIP]
124+
> 关于 SEO 工作原理与配置请参考 [SEO 文档](./SEO.md)
107125
108126
完成准备工作以后即可在 Github Action 中手动触发一次 Workflow,一切正常的话很快就能部署完成
109127

110-
这样服务端就部署好了,但是我们还需要配置 Github OAuth用于登录和 S3 存储用于存储图片
111-
128+
这样服务端就部署好了,但是目前仍然不能运行,我们还需要配置 Github OAuth用于登录和 S3 存储用于存储图片
112129

113-
回到 Cloudflare 面板配置后端域名与一些敏感的环境变量
114130

115-
`设置` > `触发器` > `自定义域` 处可以自定义后端的域名,默认也有分配一个`workers.dev`的域名
131+
> [!TIP]
132+
> 在 v0.2.0 版本后,不再需要回到 Cloudflare 面板配置后端域名与一些敏感的环境变量,所有环境变量都可以通过 Github 创建对应的密钥来添加,如果你在更早的版本中部署过,需要将环境变量迁移到 Github 中
116133
117-
`设置` > `变量` > `环境变量` 处编辑变量,点击添加变量,复制粘贴以下内容至变量名处即可自动添加上所有环境变量,之后再根据自己的具体配置修改变量值:
134+
> ~~回到 Cloudflare 面板配置后端域名与一些敏感的环境变量~~
135+
>
136+
> ~~`设置` > `触发器` > `自定义域` 处可以自定义后端的域名,默认也有分配一个`workers.dev`的域名~~
137+
>
138+
> ~~`设置` > `变量` > `环境变量` 处编辑变量,点击添加变量,复制粘贴以下内容至变量名处即可自动添加上所有环境变量,之后再根据自己的具体配置修改变量值:~~
139+
在 v0.2.0 版本后,以下所有环境变量都建议通过在 Github 创建对应的密钥来添加,添加方式与上文添加 `CLOUDFLARE_ACCOUNT_ID``CLOUDFLARE_API_TOKEN` 相同,以下是环境变量列表:
118140
```
119-
GITHUB_CLIENT_ID=YourGithubClientID
120-
GITHUB_CLIENT_SECRET=YourGithubClientSecret
121-
JWT_SECRET=YourJWTSecret
122-
S3_BUCKET=YourBucketName
123-
S3_REGION=YourRegion
124-
S3_ENDPOINT=YourEndpoint
125-
S3_ACCESS_HOST=YourAccessHost
126-
S3_ACCESS_KEY_ID=YourAccessKeyID
127-
S3_SECRET_ACCESS_KEY=YourSecretAccessKey
141+
GITHUB_CLIENT_ID=<你的GithubClientID>
142+
GITHUB_CLIENT_SECRET=<你的GithubClientSecret>
143+
JWT_SECRET=<JWT密钥>
144+
S3_ACCESS_KEY_ID=<你的S3AccessKeyID>
145+
S3_SECRET_ACCESS_KEY=<你的S3SecretAccessKey>
128146
```
129147

130148
## 接入 Github OAuth

docs/ENV.md

+11-10
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
| AVATAR || 网站左上角头像地址 || https://avatars.githubusercontent.com/u/36541432 |
1010
| NAME || 网站左上角名称 & 标题 || Xeu |
1111
| DESCRIPTION || 网站左上角描述 || 杂食动物 |
12-
| PAGE_SIZE || 默认分页限制 | 10 | 10 |
12+
| PAGE_SIZE || 默认分页限制 | 5 | 5 |
1313

1414
**部署环境变量列表**
1515

@@ -28,10 +28,16 @@
2828
> [!NOTE]
2929
> 以下变量在 Cloudflare Workers 中保持不加密即可
3030
31-
| 名称 | 是否必须 | 描述 | 默认值 | 示例值 |
32-
| ------------ | -------- | ------------------------------------------------- | ------ | ---------------- |
33-
| FRONTEND_URL | 暂时必须 | 评论通知 Webhook 时包含评论文章链接时所需,可留空 || https://xeu.life |
34-
| S3_FOLDER | 必须 | 上传保存图片时资源存放的文件路径 || images/ |
31+
| 名称 | 是否必须 | 描述 | 默认值 | 示例值 |
32+
| --------------- | -------- | ------------------------------------------------------ | ----------- | --------------------------------------------------------------- |
33+
| FRONTEND_URL | 暂时必须 | 评论通知 Webhook 时包含评论文章链接时所需,可留空 || https://xeu.life |
34+
| S3_FOLDER || 上传保存图片时资源存放的文件路径 || images/ |
35+
| S3_BUCKET || S3 存储桶名称 || images |
36+
| S3_REGION || S3 存储桶所在区域,如使用 Cloudflare R2 填写 auto 即可 || auto |
37+
| S3_ENDPOINT || S3 存储桶接入点地址 || https://1234567890abcdef1234567890abcd.r2.cloudflarestorage.com |
38+
| WEBHOOK_URL || 新增评论时发送 Webhook 通知目标地址 || https://webhook.example.com/webhook |
39+
| S3_ACCESS_HOST || S3 存储桶访问地址 | S3_ENDPOINT | https://image.xeu.life |
40+
| S3_CACHE_FOLDER || S3 缓存文件夹(用于 SEO、高频请求缓存) | cache/ | cache/ |
3541

3642
**加密环境变量,以下所有内容均为必须(Webhook 除外)**
3743

@@ -44,10 +50,5 @@
4450
| GITHUB_CLIENT_ID | Github OAuth 的客户端 ID | Ux66poMrKi1k11M1Q1b2 |
4551
| GITHUB_CLIENT_SECRET | Github OAuth 的客户端密钥 | 1234567890abcdef1234567890abcdef12345678 |
4652
| JWT_SECRET | JWT 认证所需密钥,可为常规格式的任意密码 | J0sT%Ch@nge#Me1 |
47-
| S3_BUCKET | S3 存储桶名称 | images |
48-
| S3_REGION | S3 存储桶所在区域,如使用 Cloudflare R2 填写 auto 即可 | auto |
49-
| S3_ENDPOINT | S3 存储桶接入点地址 | https://1234567890abcdef1234567890abcd.r2.cloudflarestorage.com |
50-
| S3_ACCESS_HOST | S3 存储桶访问地址 | https://image.xeu.life |
5153
| S3_ACCESS_KEY_ID | S3 存储桶访问所需的 KEY ID,使用 Cloudflare R2 时为拥有 R2 编辑权限的 API 令牌 ID | 1234567890abcdef1234567890abcd |
5254
| S3_SECRET_ACCESS_KEY | S3 存储桶访问所需的 Secret,使用 Cloudflare R2 时为拥有 R2 编辑权限的 API 令牌 | 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef |
53-
| WEBHOOK_URL | 唯一非必须环境变量 | https://webhook.example.com/webhook |

docs/SEO.md

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# SEO 工作原理介绍与配置指南
2+
## 前言
3+
由于采用前后端分离的技术,导致搜索引擎无法直接获取到页面内容,因此需要通过 SEO 优化来提高搜索引擎的收录效果。本文将介绍本项目中 SEO 实现的工作原理与配置指南。
4+
5+
## 工作原理
6+
本项目采用的 SEO 优化方案是通过 Github Action进行预渲染,将预渲染后的页面上传到 S3 存储桶,通过 Cloudflare Workers 代理请求,实现 SEO 优化。
7+
8+
预渲染是一个简单的爬虫。从提供的 SEO_BASE_URL 开始,每次请求一个页面,将渲染完成后的 html 内容上传至 S3 存储桶缓存。同时提取出页面中的所有链接,判断是否以 SEO_BASE_URL 开头或包含 SEO_CONTAINS_KEY 关键字,如果是则请求该链接并预渲染,直到没有新的链接为止。
9+
10+
## 配置指南
11+
### 环境变量
12+
在部署后端时,需要在 Github 配置以下环境变量(明文):
13+
```ini
14+
SEO_BASE_URL=<SEO 基础地址,用于 SEO 索引,默认为 FRONTEND_URL>
15+
SEO_CONTAINS_KEY=<SEO 索引时只索引以 SEO_BASE_URL 开头或包含SEO_CONTAINS_KEY 关键字的链接,默认为空>
16+
S3_FOLDER=<S3 图片资源存储的文件夹,默认为 'images/'>
17+
S3_CACHE_FOLDER=<S3 缓存文件夹(用于 SEO、高频请求缓存),默认为 'cache/'>
18+
S3_BUCKET=<S3 存储桶名称>
19+
S3_REGION=<S3 存储桶所在区域,如使用 Cloudflare R2 填写 auto 即可>
20+
S3_ENDPOINT=<S3 存储桶接入点地址>
21+
S3_ACCESS_HOST=<S3 存储桶访问地址>
22+
```
23+
24+
以及以下环境变量(加密):
25+
```ini
26+
S3_ACCESS_KEY_ID=<你的S3AccessKeyID>
27+
S3_SECRET_ACCESS_KEY=<你的S3SecretAccessKey>
28+
```
29+
30+
由于这些环境变量数量庞大且覆盖了相当一部分环境变量全列表,因此在 `v0.2.0` 及之后都建议在部署时直接在 Github 中添加这些环境变量,而不是通过 Cloudflare 面板添加。这样能够一定程度上减少配置的时间成本。
31+
32+
### 部署
33+
在配置好环境变量后,即可在 Github Action 中手动触发一次 Workflow,一切正常的话很快就能部署完成。
34+
35+
### 配置 Workers 路由
36+
在 Cloudflare Workers 面板中打开自己的域名详情页,点击 `Workers 路由`,添加一个新路由,路由填写为:
37+
```
38+
<前端域名>/seo/*
39+
```
40+
如:
41+
```
42+
xeu.life/seo/*
43+
```
44+
Worker 选择为部署的 Worker,点击保存。
45+
46+
随后点击侧边栏菜单 > `规则` > `转换规则` > `重写 URL` > `创建规则`,规则名称随意,自定义筛选表达式为:
47+
```
48+
(http.host eq "<前端域名,如xeu.life>" and http.user_agent contains "Googlebot")
49+
```
50+
重写路径设置为 `Dynamic`,值为:
51+
```
52+
concat("/seo",http.request.uri.path)
53+
```
54+
`保留查询`
55+
56+
点击部署,即可完成 SEO 配置。

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"devDependencies": {
2323
"autoprefixer": "^10.4.19",
2424
"postcss": "^8.4.38",
25+
"puppeteer": "^22.10.0",
2526
"tailwindcss": "^3.4.3",
2627
"turbo": "^1.13.3",
2728
"vitest": "1.3.0"

scripts/migrator.ts

+54-8
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,35 @@
1-
import { $ } from "bun";
2-
import stripIndent from 'strip-indent';
3-
import { readdir } from "node:fs/promises";
1+
import { $ } from "bun"
2+
import { readdir } from "node:fs/promises"
3+
import stripIndent from 'strip-indent'
44

5-
const DB_NAME = process.env.DB_NAME || 'rin'
6-
const WORKER_NAME = process.env.WORKER_NAME || 'rin-server'
7-
const FRONTEND_URL = process.env.FRONTEND_URL || ""
8-
const S3_FOLDER = process.env.S3_FOLDER || 'images/'
5+
6+
function env(name: string, defaultValue?: string, required = false) {
7+
const env = process.env
8+
const value = env[name] || defaultValue
9+
if (required && !value) {
10+
throw new Error(`${name} is not defined`)
11+
}
12+
return value
13+
}
14+
const renv = (name: string, defaultValue?: string) => env(name, defaultValue, true)
15+
16+
const DB_NAME = renv("DB_NAME", 'rin')
17+
const WORKER_NAME = renv("WORKER_NAME", 'rin-server')
18+
const FRONTEND_URL = env("FRONTEND_URL", "")
19+
20+
const S3_ENDPOINT = renv("S3_ENDPOINT")
21+
const S3_ACCESS_HOST = renv("S3_ACCESS_HOST", S3_ENDPOINT)
22+
const S3_BUCKET = renv("S3_BUCKET")
23+
const S3_CACHE_FOLDER = renv("S3_CACHE_FOLDER", 'cache/')
24+
const S3_FOLDER = renv("S3_FOLDER", 'images/')
25+
const S3_REGION = renv("S3_REGION")
26+
27+
// Secrets
28+
const accessKeyId = env("S3_ACCESS_KEY_ID")
29+
const secretAccessKey = env("S3_SECRET_ACCESS_KEY")
30+
const jwtSecret = env("JWT_SECRET")
31+
const githubClientId = env("GITHUB_CLIENT_ID")
32+
const githubClientSecret = env("GITHUB_CLIENT_SECRET")
933

1034
Bun.write('wrangler.toml', stripIndent(`
1135
#:schema node_modules/wrangler/config-schema.json
@@ -21,6 +45,11 @@ crons = ["*/20 * * * *"]
2145
[vars]
2246
FRONTEND_URL = "${FRONTEND_URL}"
2347
S3_FOLDER = "${S3_FOLDER}"
48+
S3_CACHE_FOLDER="${S3_CACHE_FOLDER}"
49+
S3_REGION = "${S3_REGION}"
50+
S3_ENDPOINT = "${S3_ENDPOINT}"
51+
S3_ACCESS_HOST = "${S3_ACCESS_HOST}"
52+
S3_BUCKET = "${S3_BUCKET}"
2453
2554
[placement]
2655
mode = "smart"
@@ -65,7 +94,7 @@ console.log(`----------------------------`)
6594

6695
console.log(`Migrating D1 "${DB_NAME}"`)
6796
try {
68-
const files = await readdir("./server/sql", { recursive: false });
97+
const files = await readdir("./server/sql", { recursive: false })
6998
for (const file of files) {
7099
await $`bunx wrangler d1 execute ${DB_NAME} --remote --file ./server/sql/${file} -y`
71100
console.log(`Migrated ${file}`)
@@ -77,6 +106,23 @@ try {
77106

78107
console.log(`Migrated D1 "${DB_NAME}"`)
79108
console.log(`----------------------------`)
109+
console.log(`Put secrets`)
110+
111+
async function putSecret(name: string, value?: string) {
112+
if (value) {
113+
console.log(`Put ${name}`)
114+
await $`echo "${value}" | bun wrangler secret put ${name}`
115+
}
116+
}
117+
118+
await putSecret('S3_ACCESS_KEY_ID', accessKeyId)
119+
await putSecret('S3_SECRET_ACCESS_KEY', secretAccessKey)
120+
await putSecret('GITHUB_CLIENT_ID', githubClientId)
121+
await putSecret('GITHUB_CLIENT_SECRET', githubClientSecret)
122+
await putSecret('JWT_SECRET', jwtSecret)
123+
124+
console.log(`Put Done.`)
125+
console.log(`----------------------------`)
80126
console.log(`Deploying`)
81127
await $`echo -e "n\ny\n" | bunx wrangler deploy`
82128
console.log(`Deployed`)

0 commit comments

Comments
 (0)