Proper 'Error 404' Fallback in Rust Axum Framework Applications
Proper 'Error 404' Fallback in Rust Axum Framework Applications
Proper 'Error 404' Fallback in Rust Axum Framework Applications

A '404 Not Found' error is not just good UX (user experience) for your visitors but also for search engine bots. Usually, the average visitor sees it as just a normal page but, technically, in the background it is supposed to emit a header that tells search engines that the resource does not exist.

Previously, while building this blog using Axum I had defaulted to this fallback:


	///routes.rs

	/// Create and configure the application router
	pub fn create_router(state: AppState) -> Router {

	    //...code stripped for brevity

	    Router::new()
	        // Public routes
	        .route("/", get(home::index_handler))
	        .route("/some-other-route", get(home::other_handler))
	        .layer(cors)//Set CORS protection guards
	        .with_state(state)
	        .fallback(axum::routing::get(|| async { "404 - Page not found" }))
	}

Unfortunately, this is BAD PRACTICE. While the user will get the '404 - Page not found' error, a '200 OK' header is served at the broswer level and also to search engine bots which is deceptive. A '200 OK' response simply means that the route/resource accessed is available and exists yet in reality it does NOT exist. So what to do? Simple. Return a proper Status Code '404' on the fallback. This is easy to do. The first approach would be to wrap the status code in the 'fallback' call:


	///routes.rs

	use axum::{
	    routing::{get},
	    Router,
	    http::{StatusCode},
	};

	/// Create and configure the application router
	pub fn create_router(state: AppState) -> Router {

	    //...code stripped for brevity

	    Router::new()
	        // Public routes
	        .route("/", get(home::index_handler))
	        .route("/some-other-route", get(home::other_handler))
	        .layer(cors)//Set CORS protection guards
	        .with_state(state)
	        .fallback(|| async {
	            (StatusCode::NOT_FOUND, "404 - Page not found")
	        })
	}

Second approach is to create a function that returns the '404' error that would then be called within the 'fallback' call:


	///routes.rs

	use axum::{
	    routing::{get},
	    Router,
	    http::{StatusCode},
	};

	async fn handler_404() -> (StatusCode, &'static str) {
	    (StatusCode::NOT_FOUND, "404 - Page not found")
	}

	/// Create and configure the application router
	pub fn create_router(state: AppState) -> Router {

	    //...code stripped for brevity

	    Router::new()
	        // Public routes
	        .route("/", get(home::index_handler))
	        .route("/some-other-route", get(home::other_handler))
	        .layer(cors)//Set CORS protection guards
	        .with_state(state)
	        .fallback(handler_404);
	}

Either way works as expected. Always, remember to return a proper '404 Error' header for nonexistent resources instead of merely printing the error to the end user.

WORDCOUNT: 360 words.