Monorepo for Tangled tangled.org
8

Configure Feed

Select the types of activity you want to include in your feed.

sites: enforce trailing slash canonical redirects for sub-path sites

Redirect directory-like paths to their trailing slash variants
(e.g. /docs -> /docs/) to ensure consistent canonical URLs and
relative asset resolution for sub-path sites.

+28
+28
sites/src/lib.rs
··· 84 84 } 85 85 } 86 86 87 + /// Returns true when a directory-like path is missing a trailing slash. 88 + /// 89 + /// Examples: 90 + /// - "/docs" => true 91 + /// - "/docs/" => false 92 + /// - "/file.txt" => false 93 + /// - "/" => false 94 + fn needs_trailing_slash(path: &str) -> bool { 95 + if path == "/" || path.ends_with('/') { 96 + return false; 97 + } 98 + let last_segment = path.rsplit('/').next().unwrap_or(path); 99 + !last_segment.contains('.') 100 + } 101 + 102 + /// Return the canonical URL with a trailing slash appended to the path. 103 + fn with_trailing_slash(url: &Url) -> String { 104 + let mut url = url.clone(); 105 + url.set_path(&format!("{}/", url.path())); 106 + url.to_string() 107 + } 108 + 87 109 /// Fetch an object from R2, falling back to appending /index.html if the 88 110 /// key looks like a directory (no file extension in the last segment). 89 111 async fn fetch_from_r2(bucket: &Bucket, key: &str) -> Result<Option<Object>> { ··· 142 164 143 165 if is_excluded(path) { 144 166 return Fetch::Request(req).send().await; 167 + } 168 + 169 + // Canonical redirect for directory-like paths. 170 + if needs_trailing_slash(path) { 171 + let redirect_url = with_trailing_slash(&url); 172 + return Response::redirect(redirect_url.parse()?, 308); 145 173 } 146 174 147 175 // Single KV lookup for the whole domain.