mykiwi/mykiwi.blogpublic Fork
⑂ a658c96
Commits
⬇ Clone ▾
git clone https://git.rickub.com/mykiwi/mykiwi.blog.git
git clone ssh://git@rickub.com/mykiwi/mykiwi.blog.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP

Redirect <project>.pages.dev to the custom domains

Each Pages project also answers on its pages.dev address: duplicate
content for search engines, and stray rows in GoatCounter. That host is
outside the zone, so it takes an account-level Bulk Redirect list and
ruleset, and two more token permissions. Preview subdomains stay
reachable.

The apex record is now named by the zone name rather than "@".
Romain Gautier committed 2026-10-11T20:43:37+02:00 Browse files
a658c96 parent: 2f29c99
modified infra/README.md +3 -2
@@ -15,8 +15,9 @@ on.
1515
1616 Create a token at https://dash.cloudflare.com/profile/api-tokens (Custom
1717 Token, scoped to the `mykiwi.blog` zone) with `Zone:DNS:Edit`,
18-`Zone:Zone:Read`, `Zone:Dynamic Redirect:Edit`, and
19-`Account:Cloudflare Pages:Edit`, then:
18+`Zone:Zone:Read`, `Zone:Dynamic Redirect:Edit`, `Account:Cloudflare Pages:Edit`,
19+`Account:Account Filter Lists:Edit`, and `Account:Account Rulesets:Edit` (the
20+last two for the `*.pages.dev` bulk redirects), then:
2021
2122 ```sh
2223 export CLOUDFLARE_API_TOKEN=...
@@ -15,8 +15,9 @@ on.
15 15
16 Create a token at https://dash.cloudflare.com/profile/api-tokens (Custom16 Create a token at https://dash.cloudflare.com/profile/api-tokens (Custom
17 Token, scoped to the `mykiwi.blog` zone) with `Zone:DNS:Edit`,17 Token, scoped to the `mykiwi.blog` zone) with `Zone:DNS:Edit`,
18-`Zone:Zone:Read`, `Zone:Dynamic Redirect:Edit`, and18+`Zone:Zone:Read`, `Zone:Dynamic Redirect:Edit`, `Account:Cloudflare Pages:Edit`,
19-`Account:Cloudflare Pages:Edit`, then:19+`Account:Account Filter Lists:Edit`, and `Account:Account Rulesets:Edit` (the
20+last two for the `*.pages.dev` bulk redirects), then:
20 21
21 ```sh22 ```sh
22 export CLOUDFLARE_API_TOKEN=...23 export CLOUDFLARE_API_TOKEN=...
modified infra/main.tf +59 -1
@@ -45,7 +45,7 @@ resource "cloudflare_record" "site" {
4545 for_each = local.languages
4646
4747 zone_id = data.cloudflare_zone.this.id
48- name = each.key == "en" ? "@" : each.key
48+ name = each.key == "en" ? var.zone_name : each.key
4949 type = "CNAME"
5050 content = cloudflare_pages_project.site[each.key].subdomain
5151 proxied = true
@@ -99,3 +99,61 @@ resource "cloudflare_ruleset" "www_redirect" {
9999 }
100100 }
101101 }
102+
103+# <project>.pages.dev -> the custom domain. Every Pages project also answers
104+# on its pages.dev address, which serves the same site under a second host
105+# (duplicate content for search engines, stray rows in GoatCounter since the
106+# host is part of the counted path). That host isn't in this zone, so zone
107+# rules can't see it - Bulk Redirects, an account-level list plus the
108+# ruleset below, can. Subdomains aren't included, so the per-deploy preview
109+# URLs (<hash>.<project>.pages.dev) stay reachable for checking a deploy.
110+resource "cloudflare_list" "pages_dev_redirects" {
111+ account_id = var.cloudflare_account_id
112+ name = "pages_dev_redirects"
113+ description = "<project>.pages.dev -> custom domain, path preserved"
114+ kind = "redirect"
115+
116+ dynamic "item" {
117+ for_each = local.languages
118+
119+ content {
120+ value {
121+ redirect {
122+ source_url = "${cloudflare_pages_project.site[item.key].subdomain}/"
123+ target_url = "https://${item.value.hostname}/"
124+ status_code = 301
125+ include_subdomains = "disabled"
126+ subpath_matching = "enabled"
127+ preserve_path_suffix = "enabled"
128+ preserve_query_string = "enabled"
129+ }
130+ }
131+ }
132+ }
133+}
134+
135+# Bulk Redirects need an account-level entrypoint ruleset in the
136+# http_request_redirect phase to actually apply the list. There can only be
137+# one per account and phase, so this fails to create if one already exists
138+# (import it instead).
139+resource "cloudflare_ruleset" "pages_dev_redirect" {
140+ account_id = var.cloudflare_account_id
141+ name = "pages.dev to custom domain redirects"
142+ description = "Applies the ${cloudflare_list.pages_dev_redirects.name} list"
143+ kind = "root"
144+ phase = "http_request_redirect"
145+
146+ rules {
147+ ref = "pages_dev_redirect"
148+ description = "Redirect <project>.pages.dev to its custom domain"
149+ expression = format("http.request.full_uri in $%s", cloudflare_list.pages_dev_redirects.name)
150+ action = "redirect"
151+
152+ action_parameters {
153+ from_list {
154+ name = cloudflare_list.pages_dev_redirects.name
155+ key = "http.request.full_uri"
156+ }
157+ }
158+ }
159+}
@@ -45,7 +45,7 @@ resource "cloudflare_record" "site" {
45 for_each = local.languages45 for_each = local.languages
46 46
47 zone_id = data.cloudflare_zone.this.id47 zone_id = data.cloudflare_zone.this.id
48- name = each.key == "en" ? "@" : each.key48+ name = each.key == "en" ? var.zone_name : each.key
49 type = "CNAME"49 type = "CNAME"
50 content = cloudflare_pages_project.site[each.key].subdomain50 content = cloudflare_pages_project.site[each.key].subdomain
51 proxied = true51 proxied = true
@@ -99,3 +99,61 @@ resource "cloudflare_ruleset" "www_redirect" {
99 }99 }
100 }100 }
101 }101 }
102+
103+# <project>.pages.dev -> the custom domain. Every Pages project also answers
104+# on its pages.dev address, which serves the same site under a second host
105+# (duplicate content for search engines, stray rows in GoatCounter since the
106+# host is part of the counted path). That host isn't in this zone, so zone
107+# rules can't see it - Bulk Redirects, an account-level list plus the
108+# ruleset below, can. Subdomains aren't included, so the per-deploy preview
109+# URLs (<hash>.<project>.pages.dev) stay reachable for checking a deploy.
110+resource "cloudflare_list" "pages_dev_redirects" {
111+ account_id = var.cloudflare_account_id
112+ name = "pages_dev_redirects"
113+ description = "<project>.pages.dev -> custom domain, path preserved"
114+ kind = "redirect"
115+
116+ dynamic "item" {
117+ for_each = local.languages
118+
119+ content {
120+ value {
121+ redirect {
122+ source_url = "${cloudflare_pages_project.site[item.key].subdomain}/"
123+ target_url = "https://${item.value.hostname}/"
124+ status_code = 301
125+ include_subdomains = "disabled"
126+ subpath_matching = "enabled"
127+ preserve_path_suffix = "enabled"
128+ preserve_query_string = "enabled"
129+ }
130+ }
131+ }
132+ }
133+}
134+
135+# Bulk Redirects need an account-level entrypoint ruleset in the
136+# http_request_redirect phase to actually apply the list. There can only be
137+# one per account and phase, so this fails to create if one already exists
138+# (import it instead).
139+resource "cloudflare_ruleset" "pages_dev_redirect" {
140+ account_id = var.cloudflare_account_id
141+ name = "pages.dev to custom domain redirects"
142+ description = "Applies the ${cloudflare_list.pages_dev_redirects.name} list"
143+ kind = "root"
144+ phase = "http_request_redirect"
145+
146+ rules {
147+ ref = "pages_dev_redirect"
148+ description = "Redirect <project>.pages.dev to its custom domain"
149+ expression = format("http.request.full_uri in $%s", cloudflare_list.pages_dev_redirects.name)
150+ action = "redirect"
151+
152+ action_parameters {
153+ from_list {
154+ name = cloudflare_list.pages_dev_redirects.name
155+ key = "http.request.full_uri"
156+ }
157+ }
158+ }
159+}