diff --git a/axum-extra/src/extract/cookie.rs b/axum-extra/src/extract/cookie.rs
index b638da49..e825c60b 100644
--- a/axum-extra/src/extract/cookie.rs
+++ b/axum-extra/src/extract/cookie.rs
@@ -45,7 +45,7 @@ pub use cookie_lib::{Cookie, Key};
 ///             // the updated jar must be returned for the changes
 ///             // to be included in the response
 ///             jar.add(Cookie::new("session_id", session_id)),
-///             Redirect::to("/me".parse().unwrap()),
+///             Redirect::to("/me"),
 ///         ))
 ///     } else {
 ///         Err(StatusCode::UNAUTHORIZED)
@@ -214,7 +214,7 @@ impl IntoResponse for CookieJar {
 ///             // the updated jar must be returned for the changes
 ///             // to be included in the response
 ///             jar.add(Cookie::new("session_id", session_id)),
-///             Redirect::to("/me".parse().unwrap()),
+///             Redirect::to("/me"),
 ///         ))
 ///     } else {
 ///         Err(StatusCode::UNAUTHORIZED)
diff --git a/axum/CHANGELOG.md b/axum/CHANGELOG.md
index 8ffdbe6f..628b3ef8 100644
--- a/axum/CHANGELOG.md
+++ b/axum/CHANGELOG.md
@@ -86,6 +86,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - **fixed:** Fixed several routing bugs related to nested "opaque" tower services (i.e.
   non-`Router` services) ([#841] and [#842])
 - **changed:** Update to tokio-tungstenite 0.17 ([#791])
+- **breaking:** `Redirect::{to, temporary, permanent}` now accept `&str` instead
+  of `Uri` ([#889])
 
 [#644]: https://github.com/tokio-rs/axum/pull/644
 [#665]: https://github.com/tokio-rs/axum/pull/665
@@ -108,6 +110,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 [#841]: https://github.com/tokio-rs/axum/pull/841
 [#842]: https://github.com/tokio-rs/axum/pull/842
 [#879]: https://github.com/tokio-rs/axum/pull/879
+[#889]: https://github.com/tokio-rs/axum/pull/889
 
 # 0.4.4 (13. January, 2022)
 
diff --git a/axum/src/response/redirect.rs b/axum/src/response/redirect.rs
index 77999378..a994db7b 100644
--- a/axum/src/response/redirect.rs
+++ b/axum/src/response/redirect.rs
@@ -1,5 +1,5 @@
 use axum_core::response::{IntoResponse, Response};
-use http::{header::LOCATION, HeaderValue, StatusCode, Uri};
+use http::{header::LOCATION, HeaderValue, StatusCode};
 use std::convert::TryFrom;
 
 /// Response that redirects the request to another location.
@@ -14,7 +14,7 @@ use std::convert::TryFrom;
 /// };
 ///
 /// let app = Router::new()
-///     .route("/old", get(|| async { Redirect::permanent("/new".parse().unwrap()) }))
+///     .route("/old", get(|| async { Redirect::permanent("/new") }))
 ///     .route("/new", get(|| async { "Hello!" }));
 /// # async {
 /// # hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
@@ -40,7 +40,7 @@ impl Redirect {
     /// If `uri` isn't a valid [`HeaderValue`].
     ///
     /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/303
-    pub fn to(uri: Uri) -> Self {
+    pub fn to(uri: &str) -> Self {
         Self::with_status_code(StatusCode::SEE_OTHER, uri)
     }
 
@@ -54,7 +54,7 @@ impl Redirect {
     /// If `uri` isn't a valid [`HeaderValue`].
     ///
     /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/307
-    pub fn temporary(uri: Uri) -> Self {
+    pub fn temporary(uri: &str) -> Self {
         Self::with_status_code(StatusCode::TEMPORARY_REDIRECT, uri)
     }
 
@@ -65,7 +65,7 @@ impl Redirect {
     /// If `uri` isn't a valid [`HeaderValue`].
     ///
     /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/308
-    pub fn permanent(uri: Uri) -> Self {
+    pub fn permanent(uri: &str) -> Self {
         Self::with_status_code(StatusCode::PERMANENT_REDIRECT, uri)
     }
 
@@ -73,7 +73,7 @@ impl Redirect {
     // use the `Location` header, namely `304 Not Modified`.
     //
     // We're open to adding more constructors upon request, if they make sense :)
-    fn with_status_code(status_code: StatusCode, uri: Uri) -> Self {
+    fn with_status_code(status_code: StatusCode, uri: &str) -> Self {
         assert!(
             status_code.is_redirection(),
             "not a redirection status code"
@@ -81,8 +81,7 @@ impl Redirect {
 
         Self {
             status_code,
-            location: HeaderValue::try_from(uri.to_string())
-                .expect("URI isn't a valid header value"),
+            location: HeaderValue::try_from(uri).expect("URI isn't a valid header value"),
         }
     }
 }
diff --git a/axum/src/routing/mod.rs b/axum/src/routing/mod.rs
index 8f38f0a1..3165422b 100644
--- a/axum/src/routing/mod.rs
+++ b/axum/src/routing/mod.rs
@@ -9,7 +9,7 @@ use crate::{
     util::try_downcast,
     BoxError,
 };
-use http::{Request, Uri};
+use http::Request;
 use matchit::MatchError;
 use std::{
     borrow::Cow,
@@ -503,10 +503,10 @@ where
         match self.node.at(&path) {
             Ok(match_) => self.call_route(match_, req),
             Err(MatchError::MissingTrailingSlash) => RouteFuture::from_response(
-                Redirect::permanent(with_path(req.uri(), &format!("{}/", path))).into_response(),
+                Redirect::permanent(&format!("{}/", req.uri().to_string())).into_response(),
             ),
             Err(MatchError::ExtraTrailingSlash) => RouteFuture::from_response(
-                Redirect::permanent(with_path(req.uri(), path.strip_suffix('/').unwrap()))
+                Redirect::permanent(&req.uri().to_string().strip_suffix('/').unwrap())
                     .into_response(),
             ),
             Err(MatchError::NotFound) => match &self.fallback {
@@ -517,35 +517,6 @@ where
     }
 }
 
-fn with_path(uri: &Uri, new_path: &str) -> Uri {
-    let path_and_query = if let Some(path_and_query) = uri.path_and_query() {
-        let new_path = if new_path.starts_with('/') {
-            Cow::Borrowed(new_path)
-        } else {
-            Cow::Owned(format!("/{}", new_path))
-        };
-
-        if let Some(query) = path_and_query.query() {
-            Some(
-                format!("{}?{}", new_path, query)
-                    .parse::<http::uri::PathAndQuery>()
-                    .unwrap(),
-            )
-        } else {
-            Some(new_path.parse().unwrap())
-        }
-    } else {
-        None
-    };
-
-    let mut parts = http::uri::Parts::default();
-    parts.scheme = uri.scheme().cloned();
-    parts.authority = uri.authority().cloned();
-    parts.path_and_query = path_and_query;
-
-    Uri::from_parts(parts).unwrap()
-}
-
 /// Wrapper around `matchit::Router` that supports merging two `Router`s.
 #[derive(Clone, Default)]
 struct Node {
diff --git a/examples/oauth/src/main.rs b/examples/oauth/src/main.rs
index d4dfaf35..6357a7fc 100644
--- a/examples/oauth/src/main.rs
+++ b/examples/oauth/src/main.rs
@@ -120,7 +120,7 @@ async fn discord_auth(Extension(client): Extension<BasicClient>) -> impl IntoRes
         .url();
 
     // Redirect to Discord's oauth service
-    Redirect::to(auth_url.to_string().parse().unwrap())
+    Redirect::to(&auth_url.to_string())
 }
 
 // Valid user session required. If there is none, redirect to the auth page
@@ -139,12 +139,12 @@ async fn logout(
     let session = match store.load_session(cookie.to_string()).await.unwrap() {
         Some(s) => s,
         // No session active, just redirect
-        None => return Redirect::to("/".parse().unwrap()),
+        None => return Redirect::to("/"),
     };
 
     store.destroy_session(session).await.unwrap();
 
-    Redirect::to("/".parse().unwrap())
+    Redirect::to("/")
 }
 
 #[derive(Debug, Deserialize)]
@@ -193,14 +193,14 @@ async fn login_authorized(
     let mut headers = HeaderMap::new();
     headers.insert(SET_COOKIE, cookie.parse().unwrap());
 
-    (headers, Redirect::to("/".parse().unwrap()))
+    (headers, Redirect::to("/"))
 }
 
 struct AuthRedirect;
 
 impl IntoResponse for AuthRedirect {
     fn into_response(self) -> Response {
-        Redirect::temporary("/auth/discord".parse().unwrap()).into_response()
+        Redirect::temporary("/auth/discord").into_response()
     }
 }