Why does dash return a status code of 200 when a page is not found

I trying to set up a 404.html page template but the issue that I am running into is that dash returns a 200 when a request is made to a page that does not exist. Why is that?

Request URL: http://127.0.0.1:8050/test
Request Method: GET
Status Code: 200 OK

Example:

app.py

from dash import Dash, html, page_container
from flask import render_template

app = Dash(__name__, use_pages=True)
server = app.server

app.layout = html.Div([
    page_container
])

@app.server.errorhandler(404)
def not_found(error):
    return render_template('404.html'), 404


if __name__ == "__main__":
    app.run(debug=False)

pages/home.py

import dash
from dash import html

dash.register_page(__name__, path='/')

layout = html.Div('Hello World')

404.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>404 Not Found</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            text-align: center;
            background-color: #1a1a2e;
            color: white;
            margin: 0;
            padding: 0;
        }
        h1 {
            font-size: 4em;
            margin-top: 50px;
        }
        p {
            font-size: 1.5em;
        }
        .emoji {
            font-size: 5em;
        }
        a {
            color: #ffcc00;
            text-decoration: none;
            font-weight: bold;
        }
        a:hover {
            text-decoration: underline;
        }
    </style>
</head>
<body>
    <div class="emoji">🚀👨‍🚀</div>
    <h1>404: Lost in Space</h1>
    <p>Uh-oh! The page you’re looking for has drifted into the void.</p>
    <p>Maybe it’s on another planet? Or just <i>really</i> good at hiding?</p>
    <p><a href="/">Return to Mission Control</a></p>
</body>
</html>
1 Like

Hi @PyGuy !

Dash handles 404’s internally and returns a valid response with the 404 dash component layout. If you haven’t set a not_found_404.py file dash is going to render a basic Title.

Just create the mentioned file and ad the styles from the html to a css file in your assets and you are good to go.

Thank you for the response but that option only inserts a layout when there is a “404” into dash.page_container and keeps the default layout. I really just want to return a static html file. I will probably end up going with something like

@app.server.before_request
def override_dash_404():
    """ Ensure unknown paths return a proper 404 page. """

    # Store the stripped request path once
    requested_path = flask.request.path.strip("/")

    # Known Dash and Flask routes
    dash_routes = {page["path"].strip("/") for page in dash.page_registry.values()}
    flask_routes = {rule.rule.strip("/") for rule in app.server.url_map.iter_rules()}

    # ✅ Allow valid Dash routes
    if requested_path in dash_routes:
        return  # Let Dash handle it

    # ✅ Allow valid Flask routes
    if requested_path in flask_routes:
        return  # Let Flask handle it

    # ✅ Allow Dash-internal API calls (updates, assets, favicon, etc.)
    if flask.request.path.startswith(("/_dash", "/assets/", "/favicon.ico")):
        return  # Let Dash handle it

    # ❌ Everything else gets a real 404 error
    flask.abort(404)  # This ensures Flask's real 404 handling takes over

@app.server.errorhandler(404)
def page_not_found(error):
    """ Serve a proper 404 page instead of Dash's fake 404 """
    return flask.render_template("404.html"), 404
1 Like

Yes, that’s the solution then

Hello @PyGuy,

That will work, though I’m not sure how it will play with templates for the dash pages. You can make special cases for that however.

Many thanks for the working solution, @PyGuy !

It would have taken me a lot of time to figure it out, and it works with minimal changes to my existing code. Moreover, it seems to play nice with multi-page Dash app.

I believe the solution could be slightly improved:

  • dash_routes = ... could be taken outside the function, as it does not rely on requested_path, so no need to re-compute it on each call;
  • for flask_routes, maybe Flask’s pattern/rule matching infrastructure could be used? There are rules like 'assets/<path:filename>', so the third check (.startswith(...)) would not be needed.

Did you improve it eventually? Am I missing anything?


@jinnyzor Perhaps this great solution could be polished and included (or referenced) in official docs, like https://dash.plotly.com/urls?


Update. A slightly improved version that worked in my case. Additionally, use PyGuy’s page_not_found() to render a page.

all_page_paths = {page["path"]: page for page in dash.page_registry.values()}

@app.server.before_request
def override_dash_404():
    if (flask.request.endpoint == "/<path:path>") and (flask.request.path not in all_page_paths):
        flask.abort(404)
1 Like