Bluesky app fork with some witchin' additions 💫

Merge pull request #8900 from internet-development/caidanw/app-1413-clean-up-canonical-urls

feat: implement canonical URL filter to clean parameters for SEO

authored by

jim and committed by
GitHub
d1aab569 bb1eab41

+91 -2
+28
bskyweb/cmd/bskyweb/filters.go
··· 1 + package main 2 + 3 + import ( 4 + "net/url" 5 + 6 + "github.com/flosch/pongo2/v6" 7 + ) 8 + 9 + func init() { 10 + pongo2.RegisterFilter("canonicalize_url", filterCanonicalizeURL) 11 + } 12 + 13 + func filterCanonicalizeURL(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) { 14 + urlStr := in.String() 15 + 16 + parsedURL, err := url.Parse(urlStr) 17 + if err != nil { 18 + // If parsing fails, return the original URL 19 + return in, nil 20 + } 21 + 22 + // Remove query parameters and fragment 23 + parsedURL.RawQuery = "" 24 + parsedURL.Fragment = "" 25 + 26 + // Return the cleaned URL 27 + return pongo2.AsValue(parsedURL.String()), nil 28 + }
+61
bskyweb/cmd/bskyweb/filters_test.go
··· 1 + package main 2 + 3 + import ( 4 + "testing" 5 + 6 + "github.com/flosch/pongo2/v6" 7 + ) 8 + 9 + func TestCanonicalizeURLFilter(t *testing.T) { 10 + tests := []struct { 11 + name string 12 + input string 13 + expected string 14 + }{ 15 + { 16 + name: "clean URL", 17 + input: "https://bsky.app/profile/user", 18 + expected: "https://bsky.app/profile/user", 19 + }, 20 + { 21 + name: "URL with query params", 22 + input: "https://bsky.app/profile/user?utm_source=test", 23 + expected: "https://bsky.app/profile/user", 24 + }, 25 + { 26 + name: "URL with multiple params", 27 + input: "https://bsky.app/profile/user?utm_source=twitter&utm_campaign=test", 28 + expected: "https://bsky.app/profile/user", 29 + }, 30 + { 31 + name: "URL with fragment", 32 + input: "https://bsky.app/profile/user#section", 33 + expected: "https://bsky.app/profile/user", 34 + }, 35 + { 36 + name: "URL with both params and fragment", 37 + input: "https://bsky.app/profile/user?param=1#section", 38 + expected: "https://bsky.app/profile/user", 39 + }, 40 + { 41 + name: "malformed URL", 42 + input: "not-a-url", 43 + expected: "not-a-url", // Should return original on error 44 + }, 45 + } 46 + 47 + for _, tt := range tests { 48 + t.Run(tt.name, func(t *testing.T) { 49 + inputValue := pongo2.AsValue(tt.input) 50 + result, err := filterCanonicalizeURL(inputValue, nil) 51 + if err != nil { 52 + t.Errorf("filterCanonicalizeURL() error = %v", err) 53 + return 54 + } 55 + 56 + if result.String() != tt.expected { 57 + t.Errorf("filterCanonicalizeURL() = %v, want %v", result.String(), tt.expected) 58 + } 59 + }) 60 + } 61 + }
+1 -1
bskyweb/templates/post.html
··· 14 14 <meta property="profile:username" content="{{ profileView.Handle }}"> 15 15 {%- if requestURI %} 16 16 <meta property="og:url" content="{{ requestURI }}"> 17 - <link rel="canonical" href="{{ requestURI }}" /> 17 + <link rel="canonical" href="{{ requestURI|canonicalize_url }}" /> 18 18 {% endif -%} 19 19 {%- if postView.Author.DisplayName %} 20 20 <meta property="og:title" content="{{ postView.Author.DisplayName }} (@{{ postView.Author.Handle }})">
+1 -1
bskyweb/templates/profile.html
··· 15 15 <meta property="profile:username" content="{{ profileView.Handle }}"> 16 16 {%- if requestURI %} 17 17 <meta property="og:url" content="{{ requestURI }}"> 18 - <link rel="canonical" href="{{ requestURI }}" /> 18 + <link rel="canonical" href="{{ requestURI|canonicalize_url }}" /> 19 19 {% endif -%} 20 20 {%- if profileView.DisplayName %} 21 21 <meta property="og:title" content="{{ profileView.DisplayName }} (@{{ profileView.Handle }})">