Move Cloudflare cache headers to cloudflare/_headers and cover locale copies

static/_headers was removed by 'make clean' (rm -rf static/_*) and got
copied into every non-default locale's build output, where it had no
effect since none of its rules matched locale-prefixed paths. Move the
file to cloudflare/_headers, which is outside static/ and is copied into
build/_headers by the Cloudflare Pages workflow step, after the S3 sync
step has already run so the file is never uploaded to S3/CloudFront.

Add /:locale/assets/*, /:locale/img/* and /:locale/images/* rules so the
caching also applies to the zh-cn and zh-tw locale copies Docusaurus
produces (docusaurus.config.js locales: en-us, zh-cn, zh-tw), which were
still falling back to Cloudflare's default max-age=0 caching. A named
placeholder is used instead of a second '*' splat or an explicit list of
locales, since Cloudflare Pages only allows one splat per path and this
way the rule keeps matching if a locale is ever added or removed.

Revert the earlier aws s3 sync --exclude "_headers" change: with
_headers no longer living under static/, it is never present in build/
at S3 sync time, so the exclude has nothing to do.
This commit is contained in:
Lunny Xiao
2026-07-25 15:33:22 -07:00
parent ae7956cc42
commit 5fc6a63b36
3 changed files with 53 additions and 18 deletions

View File

@@ -39,13 +39,20 @@ jobs:
aws-region: ${{ secrets.AWS_REGION}}
- name: Copy files to the production website with the AWS CLI
run: |
aws s3 sync build/ s3://docs-gitea-com --exclude "_headers"
aws s3 sync build/ s3://docs-gitea-com
aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_DISTRIBUTION}} --paths '/*'
- name: Copy files to Cloudflare Pages
env:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
run: |
# cloudflare/_headers is only meaningful for the Cloudflare Pages
# deployment, so it is copied into build/ here, after the S3 sync
# step above has already run. If it were placed in build/ any
# earlier, that S3 sync would publish it to S3/CloudFront and make
# it publicly accessible at /_headers there.
cp cloudflare/_headers build/_headers
test -f build/_headers || exit 1
pnpm dlx wrangler@4 pages deploy build \
--project-name docs-gitea-com \
--branch main

45
cloudflare/_headers Normal file
View File

@@ -0,0 +1,45 @@
# Cloudflare Pages reads this file from the root of the deployed directory
# (build/_headers once .gitea/workflows/build-and-publish.yaml copies it
# in). It lives here, outside static/, so that `make clean` (which does
# `rm -rf static/_*`) cannot delete it and so Docusaurus does not copy it
# into every locale build directory (see the locale rules below for why
# that would matter anyway).
# Docusaurus content-hashed build output (webpack chunks, css, etc). The
# filename changes whenever the content changes, so it is safe to cache
# these for a long time and mark them immutable.
/assets/*
Cache-Control: public, max-age=31536000, immutable
# docusaurus.config.js configures locales ["en-us", "zh-cn", "zh-tw"] with
# "en-us" as the default. Docusaurus only serves the default locale at the
# site root; every other locale gets a full copy of the build under a
# /<locale>/ prefix (e.g. build/zh-cn, build/zh-tw), including its own
# assets/img/images directories. Confirmed live: /zh-cn/ references
# /zh-cn/assets/css/styles.a0cbb0c4.css, and without a rule matching that
# path it is served with `cache-control: public, max-age=0,
# must-revalidate` instead of the caching below.
#
# Use a `:locale` placeholder rather than listing "zh-cn" and "zh-tw"
# explicitly, so this keeps working if a locale is added or removed later
# without anyone remembering to update this file. Cloudflare Pages only
# allows a single `*` splat per path, so the locale segment has to be a
# named placeholder (`:locale`, matching exactly one path segment) rather
# than a second splat; `/*/assets/*` is not a valid pattern.
/:locale/assets/*
Cache-Control: public, max-age=31536000, immutable
# Files under static/img and static/images keep stable filenames and can be
# replaced in place (same name, new content), so use a short, conservative
# max-age instead of immutable caching.
/img/*
Cache-Control: public, max-age=86400
/:locale/img/*
Cache-Control: public, max-age=86400
/images/*
Cache-Control: public, max-age=86400
/:locale/images/*
Cache-Control: public, max-age=86400

View File

@@ -1,17 +0,0 @@
# Cloudflare Pages cache headers (only applies to the Cloudflare deployment,
# see .gitea/workflows/build-and-publish.yaml for the S3/CloudFront exclude)
# Docusaurus content-hashed build output (webpack chunks, css, etc). The
# filename changes whenever the content changes, so it is safe to cache
# these for a long time and mark them immutable.
/assets/*
Cache-Control: public, max-age=31536000, immutable
# Files under static/img and static/images keep stable filenames and can be
# replaced in place (same name, new content), so use a short, conservative
# max-age instead of immutable caching.
/img/*
Cache-Control: public, max-age=86400
/images/*
Cache-Control: public, max-age=86400