Repository: DioxusLabs/dioxus (Rust)

Issue:
empty hash fragment persists in link rendering
**Problem**

If I pass an empty fragment in a link, like `blah.com/#` - Dioxus shouldn't necessarily render the `#`. Specifically, in internal links, you have no choice but to provide an empty fragment: 

```rust
pub enum BookRoute {
    #[route("/./chapter_1#:section")]
    Chapter1 { section: Chapter1Section },
    #[route("/./chapter_2#:section")]
    Chapter2 { section: Chapter2Section },
    #[route("/./chapter_3#:section")]
    Chapter3 { section: Chapter3Section },
}
```

Either we shouldn't store links with empty hashes *or* when rendering `Link {}` we just choose to trim off `#` from internal links.

Creating hrefs with JS will lead to the `#` not being rendered if the hash is empty, so we should match that behavior.

<img width="1230" alt="Image" src="https://github.com/user-attachments/assets/cb965713-cc7b-4231-ad27-9f6dafd85413" />

I think we just need to add a `trim_end_matches('#')` to https://github.com/DioxusLabs/dioxus/blob/c85018300b5ee4df3f715ec9507421b3892317a5/packages/router/src/components/link.rs#L163

**Environment:**
 - Dioxus version:  <!-- e.g. v0.17, `master` -->
 - Rust version:    <!-- e.g. 1.43.0, `nightly` -->
 - OS info:         <!-- e.g. MacOS -->
 - App platform:    <!-- e.g. `web`, `desktop` -->

**Questionnaire**
<!-- If you feel up to the challenge, please uncomment one of the lines below: -->

<!-- I'm interested in fixing this myself but don't know where to start -->
<!-- I would like to fix and I have a solution -->
<!-- I don't have time to fix this right now, but maybe later -->



Discussion:

Patch:
diff --git a/packages/router-macro/src/hash.rs b/packages/router-macro/src/hash.rs
index 2a16bc4e02..c199a0ac25 100644
--- a/packages/router-macro/src/hash.rs
+++ b/packages/router-macro/src/hash.rs
@@ -25,7 +25,12 @@ impl HashFragment {
     pub fn write(&self) -> TokenStream2 {
         let ident = &self.ident;
         quote! {
-            write!(f, "#{}", #ident)?;
+            {
+                let __hash = #ident.to_string();
+                if !__hash.is_empty() {
+                    write!(f, "#{}", __hash)?;
+                }
+            }
         }
     }
 


Test patch:
diff --git a/packages/router/tests/via_ssr/link.rs b/packages/router/tests/via_ssr/link.rs
index efc8201f66..2075d632ac 100644
--- a/packages/router/tests/via_ssr/link.rs
+++ b/packages/router/tests/via_ssr/link.rs
@@ -431,3 +431,31 @@ fn with_child_route() {
         "<h1>App</h1><a href=\"/test\">Parent Link</a><a href=\"/child/this-is-a-child-route\">Child Link 1</a><a href=\"/child/this-is-a-child-route\">Child Link 2</a>"
     );
 }
+
+#[test]
+fn with_hash_segment() {
+    #[derive(Routable, Clone)]
+    enum Route {
+        #[route("/#:data")]
+        Root { data: String },
+    }
+
+    #[component]
+    fn Root(data: String) -> Element {
+        rsx! {
+            Link {
+                to: Route::Root { data: "test".to_string() },
+                "Link"
+            }
+            Link {
+                to: Route::Root { data: "".to_string() },
+                "Empty"
+            }
+        }
+    }
+
+    assert_eq!(
+        prepare_at::<Route>("/#test"),
+        "<h1>App</h1><a href=\"/#test\" aria-current=\"page\">Link</a><a href=\"/\">Empty</a>"
+    );
+}