From 60a1be6de0c10286a658ae7b9bcf0d891965dc55 Mon Sep 17 00:00:00 2001 From: Caidan Williams Date: Mon, 25 Aug 2025 15:24:57 -0700 Subject: feat(seo): add canonical URL filter to remove query parameters Addresses community feedback about canonical URLs being misleading when they include UTM parameters. The new Pongo2 filter creates clean canonical URLs while preserving tracking parameters for social sharing. --- bskyweb/cmd/bskyweb/filters.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 bskyweb/cmd/bskyweb/filters.go diff --git a/bskyweb/cmd/bskyweb/filters.go b/bskyweb/cmd/bskyweb/filters.go new file mode 100644 index 000000000..0b882780b --- /dev/null +++ b/bskyweb/cmd/bskyweb/filters.go @@ -0,0 +1,28 @@ +package main + +import ( + "net/url" + + "github.com/flosch/pongo2/v6" +) + +func init() { + pongo2.RegisterFilter("canonical", filterCanonical) +} + +func filterCanonical(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) { + urlStr := in.String() + + parsedURL, err := url.Parse(urlStr) + if err != nil { + // If parsing fails, return the original URL + return in, nil + } + + // Remove query parameters and fragment + parsedURL.RawQuery = "" + parsedURL.Fragment = "" + + // Return the cleaned URL + return pongo2.AsValue(parsedURL.String()), nil +} -- cgit 1.4.1 From 5c04c9e3e33b817bbd08f80cca4d66888bd958aa Mon Sep 17 00:00:00 2001 From: Caidan Williams Date: Mon, 25 Aug 2025 15:25:48 -0700 Subject: feat: apply canonical filter to clean URLs in post template Uses canonical filter for canonical link tags while preserving original URLs with parameters for og:url metadata, improving SEO through proper URL canonicalization --- bskyweb/templates/post.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bskyweb/templates/post.html b/bskyweb/templates/post.html index 963af2065..a290b94e7 100644 --- a/bskyweb/templates/post.html +++ b/bskyweb/templates/post.html @@ -14,7 +14,7 @@ {%- if requestURI %} - + {% endif -%} {%- if postView.Author.DisplayName %} -- cgit 1.4.1 From f9118b2729ccfe6a651e9a1b3f033ebca2251c6d Mon Sep 17 00:00:00 2001 From: Caidan Williams Date: Mon, 25 Aug 2025 15:26:42 -0700 Subject: feat: apply canonical filter to profile template URLs for SEO Use canonical filter on requestURI in canonical link tag while preserving original URL with parameters for og:url metadata. This provides clean canonical URLs for search engines while maintaining full Open Graph URLs. --- bskyweb/templates/profile.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bskyweb/templates/profile.html b/bskyweb/templates/profile.html index af4180dc1..449caa39f 100644 --- a/bskyweb/templates/profile.html +++ b/bskyweb/templates/profile.html @@ -15,7 +15,7 @@ {%- if requestURI %} - + {% endif -%} {%- if profileView.DisplayName %} -- cgit 1.4.1 From 685650b2964ffa5e86c122a410e4b9713c01dcf7 Mon Sep 17 00:00:00 2001 From: Caidan Williams Date: Mon, 25 Aug 2025 15:28:09 -0700 Subject: test: add comprehensive coverage for canonical URL filter - Verify clean URL passthrough behavior - Test query parameter removal functionality - Test fragment removal functionality - Test combined query params and fragments handling - Ensure graceful degradation for malformed URLs --- bskyweb/cmd/bskyweb/filters_test.go | 61 +++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 bskyweb/cmd/bskyweb/filters_test.go diff --git a/bskyweb/cmd/bskyweb/filters_test.go b/bskyweb/cmd/bskyweb/filters_test.go new file mode 100644 index 000000000..2a3fc5b86 --- /dev/null +++ b/bskyweb/cmd/bskyweb/filters_test.go @@ -0,0 +1,61 @@ +package main + +import ( + "testing" + + "github.com/flosch/pongo2/v6" +) + +func TestCanonicalFilter(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + { + name: "clean URL", + input: "https://bsky.app/profile/user", + expected: "https://bsky.app/profile/user", + }, + { + name: "URL with query params", + input: "https://bsky.app/profile/user?utm_source=test", + expected: "https://bsky.app/profile/user", + }, + { + name: "URL with multiple params", + input: "https://bsky.app/profile/user?utm_source=twitter&utm_campaign=test", + expected: "https://bsky.app/profile/user", + }, + { + name: "URL with fragment", + input: "https://bsky.app/profile/user#section", + expected: "https://bsky.app/profile/user", + }, + { + name: "URL with both params and fragment", + input: "https://bsky.app/profile/user?param=1#section", + expected: "https://bsky.app/profile/user", + }, + { + name: "malformed URL", + input: "not-a-url", + expected: "not-a-url", // Should return original on error + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + inputValue := pongo2.AsValue(tt.input) + result, err := filterCanonical(inputValue, nil) + if err != nil { + t.Errorf("filterCanonical() error = %v", err) + return + } + + if result.String() != tt.expected { + t.Errorf("filterCanonical() = %v, want %v", result.String(), tt.expected) + } + }) + } +} -- cgit 1.4.1 From eb45154823323b0c7ecbd49b5896118b106cd762 Mon Sep 17 00:00:00 2001 From: Caidan Williams Date: Mon, 25 Aug 2025 17:17:49 -0700 Subject: refactor: rename canonical filter to canonicalize_url for better clarity - Rename filter from 'canonical' to 'canonicalize_url' to follow Pongo2 naming conventions - Update function name from filterCanonical to filterCanonicalizeURL - Update template usage in post.html and profile.html - Update test function name and all references --- bskyweb/cmd/bskyweb/filters.go | 4 ++-- bskyweb/cmd/bskyweb/filters_test.go | 8 ++++---- bskyweb/templates/post.html | 2 +- bskyweb/templates/profile.html | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bskyweb/cmd/bskyweb/filters.go b/bskyweb/cmd/bskyweb/filters.go index 0b882780b..a92cc606b 100644 --- a/bskyweb/cmd/bskyweb/filters.go +++ b/bskyweb/cmd/bskyweb/filters.go @@ -7,10 +7,10 @@ import ( ) func init() { - pongo2.RegisterFilter("canonical", filterCanonical) + pongo2.RegisterFilter("canonicalize_url", filterCanonicalizeURL) } -func filterCanonical(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) { +func filterCanonicalizeURL(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) { urlStr := in.String() parsedURL, err := url.Parse(urlStr) diff --git a/bskyweb/cmd/bskyweb/filters_test.go b/bskyweb/cmd/bskyweb/filters_test.go index 2a3fc5b86..a63ad0317 100644 --- a/bskyweb/cmd/bskyweb/filters_test.go +++ b/bskyweb/cmd/bskyweb/filters_test.go @@ -6,7 +6,7 @@ import ( "github.com/flosch/pongo2/v6" ) -func TestCanonicalFilter(t *testing.T) { +func TestCanonicalizeURLFilter(t *testing.T) { tests := []struct { name string input string @@ -47,14 +47,14 @@ func TestCanonicalFilter(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { inputValue := pongo2.AsValue(tt.input) - result, err := filterCanonical(inputValue, nil) + result, err := filterCanonicalizeURL(inputValue, nil) if err != nil { - t.Errorf("filterCanonical() error = %v", err) + t.Errorf("filterCanonicalizeURL() error = %v", err) return } if result.String() != tt.expected { - t.Errorf("filterCanonical() = %v, want %v", result.String(), tt.expected) + t.Errorf("filterCanonicalizeURL() = %v, want %v", result.String(), tt.expected) } }) } diff --git a/bskyweb/templates/post.html b/bskyweb/templates/post.html index a290b94e7..1f3f6da4e 100644 --- a/bskyweb/templates/post.html +++ b/bskyweb/templates/post.html @@ -14,7 +14,7 @@ {%- if requestURI %} - + {% endif -%} {%- if postView.Author.DisplayName %} diff --git a/bskyweb/templates/profile.html b/bskyweb/templates/profile.html index 449caa39f..8506a9cff 100644 --- a/bskyweb/templates/profile.html +++ b/bskyweb/templates/profile.html @@ -15,7 +15,7 @@ {%- if requestURI %} - + {% endif -%} {%- if profileView.DisplayName %} -- cgit 1.4.1