diff --git a/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj b/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj
index 2d13b0ccb7fc..9c4ab4216a93 100644
--- a/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj
+++ b/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj
@@ -19,10 +19,11 @@
     <Compile Include="$(ComponentsSharedSourceRoot)src\HotReloadManager.cs" LinkBase="HotReload" />
     <Compile Include="$(SharedSourceRoot)LinkerFlags.cs" LinkBase="Shared" />
     <Compile Include="$(SharedSourceRoot)QueryStringEnumerable.cs" LinkBase="Shared" />
+    <Compile Include="$(SharedSourceRoot)UrlDecoder\UrlDecoder.cs" LinkBase="Shared" />
   </ItemGroup>
 
   <Import Project="Microsoft.AspNetCore.Components.Routing.targets" />
-  
+
   <ItemGroup>
     <Reference Include="Microsoft.Extensions.Logging.Abstractions" />
     <Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
diff --git a/src/Components/Components/src/Routing/RouteContext.cs b/src/Components/Components/src/Routing/RouteContext.cs
index de7aeedb7e5d..bb2739887103 100644
--- a/src/Components/Components/src/Routing/RouteContext.cs
+++ b/src/Components/Components/src/Routing/RouteContext.cs
@@ -1,7 +1,11 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using System.Buffers;
 using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using System.Text;
+using Microsoft.AspNetCore.Internal;
 using Microsoft.AspNetCore.Routing.Tree;
 using static Microsoft.AspNetCore.Internal.LinkerFlags;
 
@@ -11,7 +15,28 @@ internal sealed class RouteContext
 {
     public RouteContext(string path)
     {
-        Path = Uri.UnescapeDataString(path);
+        Path = path.Contains('%') ? GetDecodedPath(path) : path;
+
+        [SkipLocalsInit]
+        static string GetDecodedPath(string path)
+        {
+            using var uriBuffer = path.Length < 128 ?
+                new UriBuffer(stackalloc byte[path.Length]) :
+                new UriBuffer(path.Length);
+
+            var utf8Span = uriBuffer.Buffer;
+
+            if (Encoding.UTF8.TryGetBytes(path.AsSpan(), utf8Span, out var written))
+            {
+                utf8Span = utf8Span[..written];
+                var decodedLength = UrlDecoder.DecodeInPlace(utf8Span, isFormEncoding: false);
+                utf8Span = utf8Span[..decodedLength];
+                path = Encoding.UTF8.GetString(utf8Span);
+                return path;
+            }
+
+            return path;
+        }
     }
 
     public string Path { get; set; }
@@ -24,4 +49,27 @@ public RouteContext(string path)
     public Type? Handler => Entry?.Handler;
 
     public IReadOnlyDictionary<string, object?>? Parameters => RouteValues;
+
+    private readonly ref struct UriBuffer
+    {
+        private readonly byte[]? _pooled;
+
+        public Span<byte> Buffer { get; }
+
+        public UriBuffer(int length)
+        {
+            _pooled = ArrayPool<byte>.Shared.Rent(length);
+            Buffer = _pooled.AsSpan(0, length);
+        }
+
+        public UriBuffer(Span<byte> buffer) => Buffer = buffer;
+
+        public void Dispose()
+        {
+            if (_pooled != null)
+            {
+                ArrayPool<byte>.Shared.Return(_pooled);
+            }
+        }
+    }
 }
diff --git a/src/Components/Components/src/Routing/RouteTable.cs b/src/Components/Components/src/Routing/RouteTable.cs
index f657221bdde3..2d4c9335cc87 100644
--- a/src/Components/Components/src/Routing/RouteTable.cs
+++ b/src/Components/Components/src/Routing/RouteTable.cs
@@ -28,6 +28,18 @@ internal static RouteData ProcessParameters(RouteData endpointRouteData)
                 ((Type page, string template) key) => RouteTableFactory.CreateEntry(key.page, key.template));
 
             var routeValueDictionary = new RouteValueDictionary(endpointRouteData.RouteValues);
+            foreach (var kvp in endpointRouteData.RouteValues)
+            {
+                if (kvp.Value is string value)
+                {
+                    // At this point the values have already been URL decoded, but we might not have decoded '/' characters.
+                    // as that can cause issues when routing the request (You wouldn't be able to accept parameters that contained '/').
+                    // To be consistent with existing Blazor quirks that used Uri.UnescapeDataString, we'll replace %2F with /.
+                    // We don't want to call Uri.UnescapeDataString here as that would decode other characters that we don't want to decode,
+                    // for example, any value that was "double" encoded (for whatever reason) within the original URL.
+                    routeValueDictionary[kvp.Key] = value.Replace("%2F", "/", StringComparison.OrdinalIgnoreCase);
+                }
+            }
             ProcessParameters(entry, routeValueDictionary);
             return new RouteData(endpointRouteData.PageType, routeValueDictionary)
             {
@@ -66,6 +78,19 @@ private static void ProcessParameters(InboundRouteEntry entry, RouteValueDiction
             }
         }
 
+        foreach (var kvp in routeValues)
+        {
+            if (kvp.Value is string value)
+            {
+                // At this point the values have already been URL decoded, but we might not have decoded '/' characters.
+                // as that can cause issues when routing the request (You wouldn't be able to accept parameters that contained '/').
+                // To be consistent with existing Blazor quirks that used Uri.UnescapeDataString, we'll replace %2F with /.
+                // We don't want to call Uri.UnescapeDataString here as that would decode other characters that we don't want to decode,
+                // for example, any value that was "double" encoded (for whatever reason) within the original URL.
+                routeValues[kvp.Key] = value.Replace("%2F", "/", StringComparison.OrdinalIgnoreCase);
+            }
+        }
+
         foreach (var parameter in entry.RoutePattern.Parameters)
         {
             // Add null values for optional route parameters that weren't provided.
diff --git a/src/Components/test/E2ETest/ServerRenderingTests/UnifiedRoutingTests.cs b/src/Components/test/E2ETest/ServerRenderingTests/UnifiedRoutingTests.cs
index 11100ee530a2..d0fce2fb6203 100644
--- a/src/Components/test/E2ETest/ServerRenderingTests/UnifiedRoutingTests.cs
+++ b/src/Components/test/E2ETest/ServerRenderingTests/UnifiedRoutingTests.cs
@@ -24,10 +24,19 @@ public UnifiedRoutingTests(
     public override Task InitializeAsync()
         => InitializeAsync(BrowserFixture.StreamingContext);
 
-    [Fact]
-    public void Routing_CanRenderPagesWithParameters_And_TransitionToInteractive()
+    [Theory]
+    [InlineData("routing/parameters/value", "value")]
+    // Issue 53138
+    [InlineData("%F0%9F%99%82/routing/parameters/http%3A%2F%2Fwww.example.com%2Flogin%2Fcallback", "http://www.example.com/login/callback")]
+    // Note this double encodes the final 2 slashes
+    [InlineData("%F0%9F%99%82/routing/parameters/http%3A%2F%2Fwww.example.com%2520login%2520callback", "http://www.example.com%20login%20callback")]
+    // Issue 53262
+    [InlineData("routing/parameters/%21%40%23%24%25%5E%26%2A%28%29_%2B-%3D%5B%5D%7B%7D%5C%5C%7C%3B%27%3A%5C%22%3E%3F.%2F", """!@#$%^&*()_+-=[]{}\\|;':\">?./""")]
+    // Issue 52808
+    [InlineData("routing/parameters/parts%20w%2F%20issue", "parts w/ issue")]
+    public void Routing_CanRenderPagesWithParameters_And_TransitionToInteractive(string url, string expectedValue)
     {
-        ExecuteRoutingTestCore("routing/parameters/value", "value");
+        ExecuteRoutingTestCore(url, expectedValue);
     }
 
     [Fact]
@@ -36,10 +45,12 @@ public void Routing_CanRenderPagesWithConstrainedParameters_And_TransitionToInte
         ExecuteRoutingTestCore("routing/constraints/5", "5");
     }
 
-    [Fact]
-    public void Routing_CanRenderPagesWithComplexSegments_And_TransitionToInteractive()
+    [Theory]
+    [InlineData("routing/complex-segment(value)", "value")]
+    [InlineData("%F0%9F%99%82/routing/%F0%9F%99%82complex-segment(http%3A%2F%2Fwww.example.com%2Flogin%2Fcallback)", "http://www.example.com/login/callback")]
+    public void Routing_CanRenderPagesWithComplexSegments_And_TransitionToInteractive(string url, string expectedValue)
     {
-        ExecuteRoutingTestCore("routing/complex-segment(value)", "value");
+        ExecuteRoutingTestCore(url, expectedValue);
     }
 
     [Fact]
@@ -54,10 +65,12 @@ public void Routing_CanRenderPagesWithOptionalParameters_And_TransitionToInterac
         ExecuteRoutingTestCore("routing/optional", "null");
     }
 
-    [Fact]
-    public void Routing_CanRenderPagesWithCatchAllParameters_And_TransitionToInteractive()
+    [Theory]
+    [InlineData("routing/catch-all/rest/of/the/path", "rest/of/the/path")]
+    [InlineData("%F0%9F%99%82/routing/catch-all/http%3A%2F%2Fwww.example.com%2Flogin%2Fcallback/another", "http://www.example.com/login/callback/another")]
+    public void Routing_CanRenderPagesWithCatchAllParameters_And_TransitionToInteractive(string url, string expectedValue)
     {
-        ExecuteRoutingTestCore("routing/catch-all/rest/of/the/path", "rest/of/the/path");
+        ExecuteRoutingTestCore(url, expectedValue);
     }
 
     [Fact]
diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Routing/Encoded/EncodedCatchAll.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Routing/Encoded/EncodedCatchAll.razor
new file mode 100644
index 000000000000..94a4010f2a7d
--- /dev/null
+++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Routing/Encoded/EncodedCatchAll.razor
@@ -0,0 +1,25 @@
+@page "/🙂/routing/catch-all/{*parameter}"
+
+<h3>Catch all</h3>
+
+<p id="parameter-value">@Parameter</p>
+
+@if (_interactive)
+{
+    <p id="interactive">Rendered interactive.</p>
+}
+
+@code {
+    private bool _interactive;
+
+    [Parameter] public string Parameter { get; set; }
+
+    protected override void OnAfterRender(bool firstRender)
+    {
+        if (firstRender)
+        {
+            _interactive = true;
+            StateHasChanged();
+        }
+    }
+}
diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Routing/Encoded/EncodedComplexSegments.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Routing/Encoded/EncodedComplexSegments.razor
new file mode 100644
index 000000000000..aef674fc997b
--- /dev/null
+++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Routing/Encoded/EncodedComplexSegments.razor
@@ -0,0 +1,24 @@
+@page "/🙂/routing/🙂complex-segment({parameter})"
+<h3>Complex segment parameters</h3>
+
+<p id="parameter-value">@Parameter</p>
+
+@if(_interactive)
+{
+    <p id="interactive">Rendered interactive.</p>
+}
+
+@code {
+    private bool _interactive;
+
+    [Parameter] public string Parameter { get; set; }
+
+    protected override void OnAfterRender(bool firstRender)
+    {
+        if (firstRender)
+        {
+            _interactive = true;
+            StateHasChanged();
+        }
+    }
+}
diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Routing/Encoded/EncodedParameters.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Routing/Encoded/EncodedParameters.razor
new file mode 100644
index 000000000000..0a73e86965e8
--- /dev/null
+++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Routing/Encoded/EncodedParameters.razor
@@ -0,0 +1,24 @@
+@page "/🙂/routing/parameters/{parameter}"
+<h3>Parameters</h3>
+
+<p id="parameter-value">@Parameter</p>
+
+@if (_interactive)
+{
+    <p id="interactive">Rendered interactive.</p>
+}
+
+@code {
+    private bool _interactive;
+
+    [Parameter] public string Parameter { get; set; }
+
+    protected override void OnAfterRender(bool firstRender)
+    {
+        if (firstRender)
+        {
+            _interactive = true;
+            StateHasChanged();
+        }
+    }
+}
diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Routing/RoutingTestCasesWithEncoding.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Routing/RoutingTestCasesWithEncoding.razor
new file mode 100644
index 000000000000..b91714bdb697
--- /dev/null
+++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Routing/RoutingTestCasesWithEncoding.razor
@@ -0,0 +1,15 @@
+@page "/🙂/routing"
+@inject NavigationManager NavigationManager
+<h3>Routing test cases with encoded urls</h3>
+
+<ul>
+    <li>
+        <a href="%F0%9F%99%82/routing/catch-all/http%3A%2F%2Fwww.example.com%2Flogin%2Fcallback/another">Catch all</a>
+    </li>
+    <li>
+        <a href="%F0%9F%99%82/routing/parameters/http%3A%2F%2Fwww.example.com%2Flogin%2Fcallback">Parameters</a>
+    </li>
+    <li>
+        <a href="%F0%9F%99%82/routing/%F0%9F%99%82complex-segment(http%3A%2F%2Fwww.example.com%2Flogin%2Fcallback)">Complex segments</a>
+    </li>
+</ul>