diff --git a/samples/StandaloneApp/Shared/NavMenu.cshtml b/samples/StandaloneApp/Shared/NavMenu.cshtml
index bea04d0ce5c3c55d4d52c77c34961448f01ac8a8..cfd37a55a29820b28e5cc28ca69c45528099a6c3 100644
--- a/samples/StandaloneApp/Shared/NavMenu.cshtml
+++ b/samples/StandaloneApp/Shared/NavMenu.cshtml
@@ -13,7 +13,7 @@
         <div class='navbar-collapse collapse'>
             <ul class='nav navbar-nav'>
                 <li>
-                    <NavLink href="/">
+                    <NavLink href="/" Match=NavLinkMatch.All>
                         <span class='glyphicon glyphicon-home'></span> Home
                     </NavLink>
                 </li>
diff --git a/src/Microsoft.AspNetCore.Blazor.Browser/Services/BrowserUriHelper.cs b/src/Microsoft.AspNetCore.Blazor.Browser/Services/BrowserUriHelper.cs
index 171679febb9a1ac4c22f856e220ac7a188fe2657..eb9972d09ec40b115676bb6817acd6a176f1123f 100644
--- a/src/Microsoft.AspNetCore.Blazor.Browser/Services/BrowserUriHelper.cs
+++ b/src/Microsoft.AspNetCore.Blazor.Browser/Services/BrowserUriHelper.cs
@@ -22,8 +22,8 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Services
         static bool _hasEnabledNavigationInterception;
         static string _cachedAbsoluteUri;
         static EventHandler<string> _onLocationChanged;
-        static string _baseUriString;
-        static Uri _baseUri;
+        static string _baseUriStringNoTrailingSlash; // No trailing slash so we can just prepend it to suffixes
+        static Uri _baseUriWithTrailingSlash; // With trailing slash so it can be used in new Uri(base, relative)
 
         /// <inheritdoc />
         public event EventHandler<string> OnLocationChanged
@@ -47,7 +47,7 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Services
         public string GetBaseUriPrefix()
         {
             EnsureBaseUriPopulated();
-            return _baseUriString;
+            return _baseUriStringNoTrailingSlash;
         }
 
         /// <inheritdoc />
@@ -77,7 +77,7 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Services
         public Uri ToAbsoluteUri(string relativeUri)
         {
             EnsureBaseUriPopulated();
-            return new Uri(_baseUri, relativeUri);
+            return new Uri(_baseUriWithTrailingSlash, relativeUri);
         }
 
         /// <inheritdoc />
@@ -118,12 +118,12 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Services
         private static void EnsureBaseUriPopulated()
         {
             // The <base href> is fixed for the lifetime of the page, so just cache it
-            if (_baseUriString == null)
+            if (_baseUriStringNoTrailingSlash == null)
             {
                 var baseUri = RegisteredFunction.InvokeUnmarshalled<string>(
                     $"{_functionPrefix}.getBaseURI");
-                _baseUriString = ToBaseUriPrefix(baseUri);
-                _baseUri = new Uri(_baseUriString);
+                _baseUriStringNoTrailingSlash = ToBaseUriPrefix(baseUri);
+                _baseUriWithTrailingSlash = new Uri(_baseUriStringNoTrailingSlash + "/");
             }
         }
 
diff --git a/src/Microsoft.AspNetCore.Blazor/Routing/NavLink.cs b/src/Microsoft.AspNetCore.Blazor/Routing/NavLink.cs
index a8661bf2afd01f4995663015481dcf89da8673f0..84c8f03d4708f93dbe2a6691e65f36351c471b4b 100644
--- a/src/Microsoft.AspNetCore.Blazor/Routing/NavLink.cs
+++ b/src/Microsoft.AspNetCore.Blazor/Routing/NavLink.cs
@@ -32,6 +32,11 @@ namespace Microsoft.AspNetCore.Blazor.Routing
         private string _hrefAbsolute;
         private IReadOnlyDictionary<string, object> _allAttributes;
 
+        /// <summary>
+        /// Gets or sets a value representing the URL matching behavior.
+        /// </summary>
+        public NavLinkMatch Match { get; set; }
+
         [Inject] private IUriHelper UriHelper { get; set; }
 
         /// <inheritdoc />
@@ -50,11 +55,12 @@ namespace Microsoft.AspNetCore.Blazor.Routing
             parameters.TryGetValue(RenderTreeBuilder.ChildContent, out _childContent);
             parameters.TryGetValue("class", out _cssClass);
             parameters.TryGetValue("href", out string href);
+            Match = parameters.GetValueOrDefault(nameof(Match), NavLinkMatch.Prefix);
             _allAttributes = parameters.ToDictionary();
 
             // Update computed state and render
             _hrefAbsolute = href == null ? null : UriHelper.ToAbsoluteUri(href).AbsoluteUri;
-            _isActive = UriHelper.GetAbsoluteUri().Equals(_hrefAbsolute, StringComparison.Ordinal);
+            _isActive = ShouldMatch(UriHelper.GetAbsoluteUri());
             _renderHandle.Render(Render);
         }
 
@@ -64,11 +70,11 @@ namespace Microsoft.AspNetCore.Blazor.Routing
             UriHelper.OnLocationChanged -= OnLocationChanged;
         }
 
-        private void OnLocationChanged(object sender, string newUri)
+        private void OnLocationChanged(object sender, string newUriAbsolute)
         {
             // We could just re-render always, but for this component we know the
             // only relevant state change is to the _isActive property.
-            var shouldBeActiveNow = newUri.Equals(_hrefAbsolute, StringComparison.Ordinal);
+            var shouldBeActiveNow = ShouldMatch(newUriAbsolute);
             if (shouldBeActiveNow != _isActive)
             {
                 _isActive = shouldBeActiveNow;
@@ -76,6 +82,22 @@ namespace Microsoft.AspNetCore.Blazor.Routing
             }
         }
 
+        private bool ShouldMatch(string currentUriAbsolute)
+        {
+            if (Match == NavLinkMatch.Prefix)
+            {
+                return StartsWithAndHasSeparator(currentUriAbsolute, _hrefAbsolute);
+            }
+            else if (Match == NavLinkMatch.All)
+            {
+                return string.Equals(currentUriAbsolute, _hrefAbsolute, StringComparison.Ordinal);
+            }
+            else 
+            {
+                throw new InvalidOperationException($"Unsupported {nameof(NavLinkMatch)} value: {Match}");
+            }
+        }
+
         private void Render(RenderTreeBuilder builder)
         {
             builder.OpenElement(0, "a");
@@ -98,5 +120,32 @@ namespace Microsoft.AspNetCore.Blazor.Routing
         private string CombineWithSpace(string str1, string str2)
             => str1 == null ? str2
             : (str2 == null ? str1 : $"{str1} {str2}");
+
+        private static bool StartsWithAndHasSeparator(string value, string prefix)
+        {
+            var valueLength = value.Length;
+            var prefixLength = prefix.Length;
+            if (prefixLength == valueLength)
+            {
+                return string.Equals(value, prefix, StringComparison.Ordinal);
+            }
+            else if (valueLength > prefixLength)
+            {
+                return value.StartsWith(prefix, StringComparison.Ordinal)
+                    && (
+                        // Only match when there's a separator character either at the end of the
+                        // prefix or right after it.
+                        // Example: "/abc" is treated as a prefix of "/abc/def" but not "/abcdef"
+                        // Example: "/abc/" is treated as a prefix of "/abc/def" but not "/abcdef"
+                        prefixLength == 0
+                        || !char.IsLetterOrDigit(prefix[prefixLength - 1])
+                        || !char.IsLetterOrDigit(value[prefixLength])
+                    );
+            }
+            else
+            {
+                return false;
+            }
+        }
     }
 }
diff --git a/src/Microsoft.AspNetCore.Blazor/Routing/NavLinkMatch.cs b/src/Microsoft.AspNetCore.Blazor/Routing/NavLinkMatch.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1e08d6f7bce4bcdda38bb1d27e9ee67f1f1ff0e3
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Blazor/Routing/NavLinkMatch.cs
@@ -0,0 +1,23 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Blazor.Routing
+{
+    /// <summary>
+    /// Modifies the URL matching behavior for a <see cref="NavLink"/>.
+    /// </summary>
+    public enum NavLinkMatch
+    {
+        /// <summary>
+        /// Specifies that the <see cref="NavLink"/> should be active when it matches any prefix
+        /// of the current URL.
+        /// </summary>
+        Prefix,
+
+        /// <summary>
+        /// Specifies that the <see cref="NavLink"/> should be active when it matches the entire
+        /// current URL.
+        /// </summary>
+        All,
+    }
+}
diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/RoutingTest.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/RoutingTest.cs
index e6f04cde6096206462f25f7214f23778a25430c1..7d28b3dd03bd23fdccd1173e17cc625b2ba96243 100644
--- a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/RoutingTest.cs
+++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/RoutingTest.cs
@@ -2,6 +2,7 @@
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 
 using System;
+using System.Linq;
 using BasicTestApp;
 using BasicTestApp.RouterTest;
 using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure;
@@ -30,6 +31,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
 
             var app = MountTestComponent<TestRouter>();
             Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text);
+            AssertHighlightedLinks("Default (matches all)");
         }
 
         [Fact]
@@ -39,6 +41,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
 
             var app = MountTestComponent<TestRouter>();
             Assert.Equal("Your full name is Dan Roth.", app.FindElement(By.Id("test-info")).Text);
+            AssertHighlightedLinks();
         }
 
         [Fact]
@@ -48,6 +51,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
 
             var app = MountTestComponent<TestRouter>();
             Assert.Equal("This is another page.", app.FindElement(By.Id("test-info")).Text);
+            AssertHighlightedLinks("Other", "Other with base-relative URL (matches all)");
         }
 
         [Fact]
@@ -58,6 +62,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
             var app = MountTestComponent<TestRouter>();
             app.FindElement(By.LinkText("Other")).Click();
             Assert.Equal("This is another page.", app.FindElement(By.Id("test-info")).Text);
+            AssertHighlightedLinks("Other", "Other with base-relative URL (matches all)");
         }
 
         [Fact]
@@ -66,8 +71,9 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
             SetUrlViaPushState($"{ServerPathBase}/RouterTest/");            
 
             var app = MountTestComponent<TestRouter>();
-            app.FindElement(By.LinkText("Other with base-relative URL")).Click();
+            app.FindElement(By.LinkText("Other with base-relative URL (matches all)")).Click();
             Assert.Equal("This is another page.", app.FindElement(By.Id("test-info")).Text);
+            AssertHighlightedLinks("Other", "Other with base-relative URL (matches all)");
         }
 
         [Fact]
@@ -78,6 +84,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
             var app = MountTestComponent<TestRouter>();
             app.FindElement(By.LinkText("With parameters")).Click();
             Assert.Equal("Your full name is Steve Sanderson.", app.FindElement(By.Id("test-info")).Text);
+            AssertHighlightedLinks("With parameters");
         }
 
         [Fact]
@@ -86,8 +93,9 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
             SetUrlViaPushState($"{ServerPathBase}/RouterTest/Other");
 
             var app = MountTestComponent<TestRouter>();
-            app.FindElement(By.LinkText("Default")).Click();
+            app.FindElement(By.LinkText("Default (matches all)")).Click();
             Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text);
+            AssertHighlightedLinks("Default (matches all)");
         }
 
         [Fact]
@@ -98,6 +106,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
             var app = MountTestComponent<TestRouter>();
             app.FindElement(By.LinkText("Other with query")).Click();
             Assert.Equal("This is another page.", app.FindElement(By.Id("test-info")).Text);
+            AssertHighlightedLinks("Other", "Other with query");
         }
 
         [Fact]
@@ -108,6 +117,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
             var app = MountTestComponent<TestRouter>();
             app.FindElement(By.LinkText("Default with query")).Click();
             Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text);
+            AssertHighlightedLinks("Default with query");
         }
 
         [Fact]
@@ -118,6 +128,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
             var app = MountTestComponent<TestRouter>();
             app.FindElement(By.LinkText("Other with hash")).Click();
             Assert.Equal("This is another page.", app.FindElement(By.Id("test-info")).Text);
+            AssertHighlightedLinks("Other", "Other with hash");
         }
 
         [Fact]
@@ -128,6 +139,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
             var app = MountTestComponent<TestRouter>();
             app.FindElement(By.LinkText("Default with hash")).Click();
             Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text);
+            AssertHighlightedLinks("Default with hash");
         }
 
         [Fact]
@@ -138,6 +150,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
             var app = MountTestComponent<TestRouter>();
             app.FindElement(By.TagName("button")).Click();
             Assert.Equal("This is another page.", app.FindElement(By.Id("test-info")).Text);
+            AssertHighlightedLinks("Other", "Other with base-relative URL (matches all)");
         }
 
         public void Dispose()
@@ -153,5 +166,12 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
             var absoluteUri = new Uri(_server.RootUri, relativeUri);
             jsExecutor.ExecuteScript($"Blazor.navigateTo('{absoluteUri.ToString()}')");
         }
+
+        private void AssertHighlightedLinks(params string[] linkTexts)
+        {
+            var actual = Browser.FindElements(By.CssSelector("a.active"));
+            var actualTexts = actual.Select(x => x.Text);
+            Assert.Equal(linkTexts, actualTexts);
+        }
     }
 }
diff --git a/test/testapps/BasicTestApp/RouterTest/Links.cshtml b/test/testapps/BasicTestApp/RouterTest/Links.cshtml
index 36ca9857b8178d42658c0da11359cd09da25eb22..c45a8dfd582d21781114da4a6d83d7f8146b9ea9 100644
--- a/test/testapps/BasicTestApp/RouterTest/Links.cshtml
+++ b/test/testapps/BasicTestApp/RouterTest/Links.cshtml
@@ -1,14 +1,16 @@
 @page "/Links"
+@using Microsoft.AspNetCore.Blazor.Routing
 @inject Microsoft.AspNetCore.Blazor.Services.IUriHelper uriHelper
+<style type="text/css">a.active { background-color: yellow; font-weight: bold; }</style>
 <ul>
-    <li><a href="/subdir/RouterTest/">Default</a></li>
-    <li><a href="/subdir/RouterTest/?abc=123">Default with query</a></li>
-    <li><a href="/subdir/RouterTest/#blah">Default with hash</a></li>
-    <li><a href="/subdir/RouterTest/Other">Other</a></li>
-    <li><a href="RouterTest/Other">Other with base-relative URL</a></li>
-    <li><a href="/subdir/RouterTest/Other?abc=123">Other with query</a></li>
-    <li><a href="/subdir/RouterTest/Other#blah">Other with hash</a></li>
-    <li><a href="/subdir/RouterTest/WithParameters/Name/Steve/LastName/Sanderson">With parameters</a></li>
+    <li><NavLink href="/subdir/RouterTest/" Match=NavLinkMatch.All>Default (matches all)</NavLink></li>
+    <li><NavLink href="/subdir/RouterTest/?abc=123">Default with query</NavLink></li>
+    <li><NavLink href="/subdir/RouterTest/#blah">Default with hash</NavLink></li>
+    <li><NavLink href="/subdir/RouterTest/Other">Other</NavLink></li>
+    <li><NavLink href="RouterTest/Other" Match=NavLinkMatch.All>Other with base-relative URL (matches all)</NavLink></li>
+    <li><NavLink href="/subdir/RouterTest/Other?abc=123">Other with query</NavLink></li>
+    <li><NavLink href="/subdir/RouterTest/Other#blah">Other with hash</NavLink></li>
+    <li><NavLink href="/subdir/RouterTest/WithParameters/Name/Steve/LastName/Sanderson">With parameters</NavLink></li>
 </ul>
 
 <button onclick=@{ uriHelper.NavigateTo("RouterTest/Other"); }>
diff --git a/test/testapps/BasicTestApp/RouterTest/WithParameters.cshtml b/test/testapps/BasicTestApp/RouterTest/WithParameters.cshtml
index b31a8710716fb83c5827e904915b1671800988e7..b213ef48459b9bb7188177f7cf76c66e9ed60673 100644
--- a/test/testapps/BasicTestApp/RouterTest/WithParameters.cshtml
+++ b/test/testapps/BasicTestApp/RouterTest/WithParameters.cshtml
@@ -1,6 +1,7 @@
 @page "/RouterTest/WithParameters/Name/{firstName}/LastName/{lastName}"
 @using BasicTestApp.RouterTest
 <div id="test-info">Your full name is @FirstName @LastName.</div>
+<Links />
 
 @functions
 {