From 5fc6a63b36a512263f45c047485e72fdafa4e176 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 25 Jul 2026 15:33:22 -0700 Subject: [PATCH] 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. --- .gitea/workflows/build-and-publish.yaml | 9 ++++- cloudflare/_headers | 45 +++++++++++++++++++++++++ static/_headers | 17 ---------- 3 files changed, 53 insertions(+), 18 deletions(-) create mode 100644 cloudflare/_headers delete mode 100644 static/_headers diff --git a/.gitea/workflows/build-and-publish.yaml b/.gitea/workflows/build-and-publish.yaml index 7cbac8b9..b03c29fc 100644 --- a/.gitea/workflows/build-and-publish.yaml +++ b/.gitea/workflows/build-and-publish.yaml @@ -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 diff --git a/cloudflare/_headers b/cloudflare/_headers new file mode 100644 index 00000000..e388c413 --- /dev/null +++ b/cloudflare/_headers @@ -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 +# // 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 diff --git a/static/_headers b/static/_headers deleted file mode 100644 index 6e6c1034..00000000 --- a/static/_headers +++ /dev/null @@ -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