diff --git a/axum-core/src/response/into_response_parts.rs b/axum-core/src/response/into_response_parts.rs
index b6e838a8..00c2763d 100644
--- a/axum-core/src/response/into_response_parts.rs
+++ b/axum-core/src/response/into_response_parts.rs
@@ -57,7 +57,10 @@ use std::{
 /// }
 ///
 /// // We can now return `SetHeader` in responses
-/// async fn handler() -> impl IntoResponse {
+/// //
+/// // Note that returning `impl IntoResponse` might be easier if the response has many parts to
+/// // it. The return type is written out here for clarity.
+/// async fn handler() -> (SetHeader<'static>, SetHeader<'static>, &'static str) {
 ///     (
 ///         SetHeader("server", "axum"),
 ///         SetHeader("x-foo", "custom"),
@@ -66,7 +69,7 @@ use std::{
 /// }
 ///
 /// // Or on its own as the whole response
-/// async fn other_handler() -> impl IntoResponse {
+/// async fn other_handler() -> SetHeader<'static> {
 ///     SetHeader("x-foo", "custom")
 /// }
 /// ```
diff --git a/axum-extra/src/extract/cookie/mod.rs b/axum-extra/src/extract/cookie/mod.rs
index 85cd421e..7bda1ea3 100644
--- a/axum-extra/src/extract/cookie/mod.rs
+++ b/axum-extra/src/extract/cookie/mod.rs
@@ -50,7 +50,7 @@ pub use cookie_lib::Key;
 /// async fn create_session(
 ///     TypedHeader(auth): TypedHeader<Authorization<Bearer>>,
 ///     jar: CookieJar,
-/// ) -> impl IntoResponse {
+/// ) -> Result<(CookieJar, Redirect), StatusCode> {
 ///     if let Some(session_id) = authorize_and_create_session(auth.token()).await {
 ///         Ok((
 ///             // the updated jar must be returned for the changes
@@ -63,7 +63,7 @@ pub use cookie_lib::Key;
 ///     }
 /// }
 ///
-/// async fn me(jar: CookieJar) -> impl IntoResponse {
+/// async fn me(jar: CookieJar) -> Result<(), StatusCode> {
 ///     if let Some(session_id) = jar.get("session_id") {
 ///         // fetch and render user...
 ///         # Ok(())
@@ -141,7 +141,7 @@ impl CookieJar {
     /// use axum_extra::extract::cookie::{CookieJar, Cookie};
     /// use axum::response::IntoResponse;
     ///
-    /// async fn handle(jar: CookieJar) -> impl IntoResponse {
+    /// async fn handle(jar: CookieJar) -> CookieJar {
     ///     jar.remove(Cookie::named("foo"))
     /// }
     /// ```
@@ -161,7 +161,7 @@ impl CookieJar {
     /// use axum_extra::extract::cookie::{CookieJar, Cookie};
     /// use axum::response::IntoResponse;
     ///
-    /// async fn handle(jar: CookieJar) -> impl IntoResponse {
+    /// async fn handle(jar: CookieJar) -> CookieJar {
     ///     jar.add(Cookie::new("foo", "bar"))
     /// }
     /// ```
diff --git a/axum-extra/src/extract/cookie/private.rs b/axum-extra/src/extract/cookie/private.rs
index a896d110..a583a3c9 100644
--- a/axum-extra/src/extract/cookie/private.rs
+++ b/axum-extra/src/extract/cookie/private.rs
@@ -33,12 +33,12 @@ use std::{convert::Infallible, fmt, marker::PhantomData};
 ///
 /// async fn set_secret(
 ///     jar: PrivateCookieJar,
-/// ) -> impl IntoResponse {
+/// ) -> (PrivateCookieJar, Redirect) {
 ///     let updated_jar = jar.add(Cookie::new("secret", "secret-data"));
 ///     (updated_jar, Redirect::to("/get"))
 /// }
 ///
-/// async fn get_secret(jar: PrivateCookieJar) -> impl IntoResponse {
+/// async fn get_secret(jar: PrivateCookieJar) {
 ///     if let Some(data) = jar.get("secret") {
 ///         // ...
 ///     }
@@ -129,7 +129,7 @@ impl<K> PrivateCookieJar<K> {
     /// use axum_extra::extract::cookie::{PrivateCookieJar, Cookie};
     /// use axum::response::IntoResponse;
     ///
-    /// async fn handle(jar: PrivateCookieJar) -> impl IntoResponse {
+    /// async fn handle(jar: PrivateCookieJar) -> PrivateCookieJar {
     ///     jar.remove(Cookie::named("foo"))
     /// }
     /// ```
@@ -149,7 +149,7 @@ impl<K> PrivateCookieJar<K> {
     /// use axum_extra::extract::cookie::{PrivateCookieJar, Cookie};
     /// use axum::response::IntoResponse;
     ///
-    /// async fn handle(jar: PrivateCookieJar) -> impl IntoResponse {
+    /// async fn handle(jar: PrivateCookieJar) -> PrivateCookieJar {
     ///     jar.add(Cookie::new("foo", "bar"))
     /// }
     /// ```
diff --git a/axum-extra/src/extract/cookie/signed.rs b/axum-extra/src/extract/cookie/signed.rs
index 000c51e5..51fb865c 100644
--- a/axum-extra/src/extract/cookie/signed.rs
+++ b/axum-extra/src/extract/cookie/signed.rs
@@ -35,7 +35,7 @@ use std::{convert::Infallible, fmt, marker::PhantomData};
 /// async fn create_session(
 ///     TypedHeader(auth): TypedHeader<Authorization<Bearer>>,
 ///     jar: SignedCookieJar,
-/// ) -> impl IntoResponse {
+/// ) -> Result<(SignedCookieJar, Redirect), StatusCode> {
 ///     if let Some(session_id) = authorize_and_create_session(auth.token()).await {
 ///         Ok((
 ///             // the updated jar must be returned for the changes
@@ -48,7 +48,7 @@ use std::{convert::Infallible, fmt, marker::PhantomData};
 ///     }
 /// }
 ///
-/// async fn me(jar: SignedCookieJar) -> impl IntoResponse {
+/// async fn me(jar: SignedCookieJar) -> Result<(), StatusCode> {
 ///     if let Some(session_id) = jar.get("session_id") {
 ///         // fetch and render user...
 ///         # Ok(())
@@ -148,7 +148,7 @@ impl<K> SignedCookieJar<K> {
     /// use axum_extra::extract::cookie::{SignedCookieJar, Cookie};
     /// use axum::response::IntoResponse;
     ///
-    /// async fn handle(jar: SignedCookieJar) -> impl IntoResponse {
+    /// async fn handle(jar: SignedCookieJar) -> SignedCookieJar {
     ///     jar.remove(Cookie::named("foo"))
     /// }
     /// ```
@@ -168,7 +168,7 @@ impl<K> SignedCookieJar<K> {
     /// use axum_extra::extract::cookie::{SignedCookieJar, Cookie};
     /// use axum::response::IntoResponse;
     ///
-    /// async fn handle(jar: SignedCookieJar) -> impl IntoResponse {
+    /// async fn handle(jar: SignedCookieJar) -> SignedCookieJar {
     ///     jar.add(Cookie::new("foo", "bar"))
     /// }
     /// ```
diff --git a/axum-extra/src/response/erased_json.rs b/axum-extra/src/response/erased_json.rs
index 98c791a7..c61d3145 100644
--- a/axum-extra/src/response/erased_json.rs
+++ b/axum-extra/src/response/erased_json.rs
@@ -15,7 +15,7 @@ use serde::Serialize;
 /// ```rust
 /// # use axum::{response::IntoResponse};
 /// # use axum_extra::response::ErasedJson;
-/// async fn handler() -> impl IntoResponse {
+/// async fn handler() -> ErasedJson {
 ///     # let condition = true;
 ///     # let foo = ();
 ///     # let bar = vec![()];
diff --git a/axum/src/body/stream_body.rs b/axum/src/body/stream_body.rs
index 89758ed3..3d83ef56 100644
--- a/axum/src/body/stream_body.rs
+++ b/axum/src/body/stream_body.rs
@@ -32,10 +32,11 @@ pin_project! {
     ///     body::StreamBody,
     ///     response::IntoResponse,
     /// };
-    /// use futures::stream;
+    /// use futures::stream::{self, Stream};
+    /// use std::io;
     ///
-    /// async fn handler() -> impl IntoResponse {
-    ///     let chunks: Vec<Result<_, std::io::Error>> = vec![
+    /// async fn handler() -> StreamBody<impl Stream<Item = io::Result<&'static str>>> {
+    ///     let chunks: Vec<io::Result<_>> = vec![
     ///         Ok("Hello,"),
     ///         Ok(" "),
     ///         Ok("world!"),
diff --git a/axum/src/docs/extract.md b/axum/src/docs/extract.md
index ba5d2784..37920923 100644
--- a/axum/src/docs/extract.md
+++ b/axum/src/docs/extract.md
@@ -316,7 +316,9 @@ use axum::{
 };
 use serde_json::{json, Value};
 
-async fn handler(result: Result<Json<Value>, JsonRejection>) -> impl IntoResponse {
+async fn handler(
+    result: Result<Json<Value>, JsonRejection>,
+) -> Result<Json<Value>, (StatusCode, String)> {
     match result {
         // if the client sent valid JSON then we're good
         Ok(Json(payload)) => Ok(Json(json!({ "payload": payload }))),
diff --git a/axum/src/docs/method_routing/fallback.md b/axum/src/docs/method_routing/fallback.md
index 0d2ee300..c6a0d090 100644
--- a/axum/src/docs/method_routing/fallback.md
+++ b/axum/src/docs/method_routing/fallback.md
@@ -15,7 +15,7 @@ let handler = get(|| async {}).fallback(fallback.into_service());
 
 let app = Router::new().route("/", handler);
 
-async fn fallback(method: Method, uri: Uri) -> impl IntoResponse {
+async fn fallback(method: Method, uri: Uri) -> (StatusCode, String) {
     (StatusCode::NOT_FOUND, format!("`{}` not allowed for {}", method, uri))
 }
 # async {
@@ -44,8 +44,8 @@ let two = post(|| async {})
 
 let method_route = one.merge(two);
 
-async fn fallback_one() -> impl IntoResponse {}
-async fn fallback_two() -> impl IntoResponse {}
+async fn fallback_one() -> impl IntoResponse { /* ... */ }
+async fn fallback_two() -> impl IntoResponse { /* ... */ }
 # let app = axum::Router::new().route("/", method_route);
 # async {
 # hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
diff --git a/axum/src/docs/middleware.md b/axum/src/docs/middleware.md
index a65ca6d5..17a3d72f 100644
--- a/axum/src/docs/middleware.md
+++ b/axum/src/docs/middleware.md
@@ -383,7 +383,7 @@ use axum::{
     Router,
     http::{Request, StatusCode},
     routing::get,
-    response::IntoResponse,
+    response::{IntoResponse, Response},
     middleware::{self, Next},
     extract::Extension,
 };
@@ -391,7 +391,7 @@ use axum::{
 #[derive(Clone)]
 struct CurrentUser { /* ... */ }
 
-async fn auth<B>(mut req: Request<B>, next: Next<B>) -> impl IntoResponse {
+async fn auth<B>(mut req: Request<B>, next: Next<B>) -> Result<Response, StatusCode> {
     let auth_header = req.headers()
         .get(http::header::AUTHORIZATION)
         .and_then(|header| header.to_str().ok());
diff --git a/axum/src/docs/response.md b/axum/src/docs/response.md
index 270ba7bd..09839de5 100644
--- a/axum/src/docs/response.md
+++ b/axum/src/docs/response.md
@@ -133,7 +133,7 @@ async fn all_the_things(uri: Uri) -> impl IntoResponse {
     (
         // set status code
         StatusCode::NOT_FOUND,
-        // headers ith an array
+        // headers with an array
         [("x-custom", "custom")],
         // some extensions
         Extension(Foo("foo")),
@@ -165,11 +165,11 @@ Use [`Response`](crate::response::Response) for more low level control:
 use axum::{
     Json,
     response::{IntoResponse, Response},
-    body::Full,
+    body::{Full, Bytes},
     http::StatusCode,
 };
 
-async fn response() -> impl IntoResponse {
+async fn response() -> Response<Full<Bytes>> {
     Response::builder()
         .status(StatusCode::NOT_FOUND)
         .header("x-foo", "custom header")
@@ -178,6 +178,41 @@ async fn response() -> impl IntoResponse {
 }
 ```
 
+# Returning different response types
+
+If you need to return multiple response types, and `Result<T, E>` isn't appropriate, you can call
+`.into_response()` to turn things into `axum::response::Response`:
+
+```rust
+use axum::{
+    response::{IntoResponse, Redirect, Response},
+    http::StatusCode,
+};
+
+async fn handle() -> Response {
+    if something() {
+        "All good!".into_response()
+    } else if something_else() {
+        (
+            StatusCode::INTERNAL_SERVER_ERROR,
+            "Something went wrong...",
+        ).into_response()
+    } else {
+        Redirect::to("/").into_response()
+    }
+}
+
+fn something() -> bool {
+    // ...
+    # true
+}
+
+fn something_else() -> bool {
+    // ...
+    # true
+}
+```
+
 [`IntoResponse`]: crate::response::IntoResponse
 [`IntoResponseParts`]: crate::response::IntoResponseParts
 [`StatusCode`]: http::StatusCode
diff --git a/axum/src/docs/routing/fallback.md b/axum/src/docs/routing/fallback.md
index 78cac4e1..a5c7467e 100644
--- a/axum/src/docs/routing/fallback.md
+++ b/axum/src/docs/routing/fallback.md
@@ -15,7 +15,7 @@ let app = Router::new()
     .route("/foo", get(|| async { /* ... */ }))
     .fallback(fallback.into_service());
 
-async fn fallback(uri: Uri) -> impl IntoResponse {
+async fn fallback(uri: Uri) -> (StatusCode, String) {
     (StatusCode::NOT_FOUND, format!("No route for {}", uri))
 }
 # async {
diff --git a/axum/src/extension.rs b/axum/src/extension.rs
index 55efa1b5..390f29e0 100644
--- a/axum/src/extension.rs
+++ b/axum/src/extension.rs
@@ -59,7 +59,7 @@ use tower_service::Service;
 ///     response::IntoResponse,
 /// };
 ///
-/// async fn handler() -> impl IntoResponse {
+/// async fn handler() -> (Extension<Foo>, &'static str) {
 ///     (
 ///         Extension(Foo("foo")),
 ///         "Hello, World!"
diff --git a/axum/src/extract/ws.rs b/axum/src/extract/ws.rs
index 008115a5..90a8c9b5 100644
--- a/axum/src/extract/ws.rs
+++ b/axum/src/extract/ws.rs
@@ -6,13 +6,13 @@
 //! use axum::{
 //!     extract::ws::{WebSocketUpgrade, WebSocket},
 //!     routing::get,
-//!     response::IntoResponse,
+//!     response::{IntoResponse, Response},
 //!     Router,
 //! };
 //!
 //! let app = Router::new().route("/ws", get(handler));
 //!
-//! async fn handler(ws: WebSocketUpgrade) -> impl IntoResponse {
+//! async fn handler(ws: WebSocketUpgrade) -> Response {
 //!     ws.on_upgrade(handle_socket)
 //! }
 //!
@@ -148,13 +148,13 @@ impl WebSocketUpgrade {
     /// use axum::{
     ///     extract::ws::{WebSocketUpgrade, WebSocket},
     ///     routing::get,
-    ///     response::IntoResponse,
+    ///     response::{IntoResponse, Response},
     ///     Router,
     /// };
     ///
     /// let app = Router::new().route("/ws", get(handler));
     ///
-    /// async fn handler(ws: WebSocketUpgrade) -> impl IntoResponse {
+    /// async fn handler(ws: WebSocketUpgrade) -> Response {
     ///     ws.protocols(["graphql-ws", "graphql-transport-ws"])
     ///         .on_upgrade(|socket| async {
     ///             // ...
diff --git a/axum/src/handler/mod.rs b/axum/src/handler/mod.rs
index 56348eb3..69482fec 100644
--- a/axum/src/handler/mod.rs
+++ b/axum/src/handler/mod.rs
@@ -126,7 +126,7 @@ pub trait Handler<T, B = Body>: Clone + Send + Sized + 'static {
     /// use tower::make::Shared;
     /// use std::net::SocketAddr;
     ///
-    /// async fn handler(method: Method, uri: Uri) -> impl IntoResponse {
+    /// async fn handler(method: Method, uri: Uri) -> (StatusCode, String) {
     ///     (StatusCode::NOT_FOUND, format!("Nothing to see at {} {}", method, uri))
     /// }
     ///
@@ -157,7 +157,7 @@ pub trait Handler<T, B = Body>: Clone + Send + Sized + 'static {
     /// };
     /// use std::net::SocketAddr;
     ///
-    /// async fn handler(method: Method, uri: Uri, body: String) -> impl IntoResponse {
+    /// async fn handler(method: Method, uri: Uri, body: String) -> String {
     ///     format!("received `{} {}` with body `{:?}`", method, uri, body)
     /// }
     ///
@@ -188,7 +188,7 @@ pub trait Handler<T, B = Body>: Clone + Send + Sized + 'static {
     /// };
     /// use std::net::SocketAddr;
     ///
-    /// async fn handler(ConnectInfo(addr): ConnectInfo<SocketAddr>) -> impl IntoResponse {
+    /// async fn handler(ConnectInfo(addr): ConnectInfo<SocketAddr>) -> String {
     ///     format!("Hello {}", addr)
     /// }
     ///
diff --git a/axum/src/middleware/from_fn.rs b/axum/src/middleware/from_fn.rs
index df8d31ea..9f9563b5 100644
--- a/axum/src/middleware/from_fn.rs
+++ b/axum/src/middleware/from_fn.rs
@@ -34,11 +34,11 @@ use tower_service::Service;
 ///     Router,
 ///     http::{Request, StatusCode},
 ///     routing::get,
-///     response::IntoResponse,
+///     response::{IntoResponse, Response},
 ///     middleware::{self, Next},
 /// };
 ///
-/// async fn auth<B>(req: Request<B>, next: Next<B>) -> impl IntoResponse {
+/// async fn auth<B>(req: Request<B>, next: Next<B>) -> Result<Response, StatusCode> {
 ///     let auth_header = req.headers()
 ///         .get(http::header::AUTHORIZATION)
 ///         .and_then(|header| header.to_str().ok());
@@ -71,7 +71,7 @@ use tower_service::Service;
 ///     Router,
 ///     http::{Request, StatusCode},
 ///     routing::get,
-///     response::IntoResponse,
+///     response::{IntoResponse, Response},
 ///     middleware::{self, Next}
 /// };
 ///
@@ -82,9 +82,9 @@ use tower_service::Service;
 ///     req: Request<B>,
 ///     next: Next<B>,
 ///     state: State,
-/// ) -> impl IntoResponse {
+/// ) -> Response {
 ///     // ...
-///     # ()
+///     # ().into_response()
 /// }
 ///
 /// let state = State { /* ... */ };
@@ -105,7 +105,7 @@ use tower_service::Service;
 ///     extract::Extension,
 ///     http::{Request, StatusCode},
 ///     routing::get,
-///     response::IntoResponse,
+///     response::{IntoResponse, Response},
 ///     middleware::{self, Next},
 /// };
 /// use tower::ServiceBuilder;
@@ -116,11 +116,11 @@ use tower_service::Service;
 /// async fn my_middleware<B>(
 ///     req: Request<B>,
 ///     next: Next<B>,
-/// ) -> impl IntoResponse {
+/// ) -> Response {
 ///     let state: &State = req.extensions().get().unwrap();
 ///
 ///     // ...
-///     # ()
+///     # ().into_response()
 /// }
 ///
 /// let state = State { /* ... */ };
diff --git a/axum/src/routing/method_routing.rs b/axum/src/routing/method_routing.rs
index edd3ac3a..264dedaa 100644
--- a/axum/src/routing/method_routing.rs
+++ b/axum/src/routing/method_routing.rs
@@ -603,7 +603,7 @@ where
     /// };
     /// use std::net::SocketAddr;
     ///
-    /// async fn handler(method: Method, uri: Uri, body: String) -> impl IntoResponse {
+    /// async fn handler(method: Method, uri: Uri, body: String) -> String {
     ///     format!("received `{} {}` with body `{:?}`", method, uri, body)
     /// }
     ///
@@ -637,7 +637,7 @@ where
     /// };
     /// use std::net::SocketAddr;
     ///
-    /// async fn handler(ConnectInfo(addr): ConnectInfo<SocketAddr>) -> impl IntoResponse {
+    /// async fn handler(ConnectInfo(addr): ConnectInfo<SocketAddr>) -> String {
     ///     format!("Hello {}", addr)
     /// }
     ///
diff --git a/axum/src/typed_header.rs b/axum/src/typed_header.rs
index bb5bc721..1ce72d04 100644
--- a/axum/src/typed_header.rs
+++ b/axum/src/typed_header.rs
@@ -40,7 +40,7 @@ use std::{convert::Infallible, ops::Deref};
 ///     headers::ContentType,
 /// };
 ///
-/// async fn handler() -> impl IntoResponse {
+/// async fn handler() -> (TypedHeader<ContentType>, &'static str) {
 ///     (
 ///         TypedHeader(ContentType::text_utf8()),
 ///         "Hello, World!",