Custom CSS for 500 error page

Hi,

During the development of my Dash application, I wanted to change the default 500 error page from Flask to match the theme of the app. I am using the following snippet of code to accomplish this.

@server.errorhandler(HTTPException)
def internal_error(error):
    return render_template('500.html'), 500

The correct HTML file is loaded, but the CSS I am trying to load returns a 500 internal server error. The CSS file is located in the assets folder of Dash.:

<link rel="stylesheet" href="/dashboard/assets/build/theme.css">

I hope someone can help me out with this issue!

Greetings Niek

Hello @niekvleeuwen,

Make sure on the page that the style sheet links properly, you can do this by looking at the elements and opening the link in a new tab.

If it links properly, check to make sure the page design will allow for the css to apply.

1 Like

Thank you for the fast response. The CSS is linked correctly, because in a non-error state, the CSS is loaded (for a different route). I think the Dash application is not serving static files when an exception occurs.

Schermafbeelding 2022-10-10 144804

I dont think that that is the case. Flask (and Dash) should be catering the responses independently of each other. The only reason this would occur is if something is the killing the application. You should always be able to navigate to a static file.

Is it possible to see a little more of your code? Also, which page is causing the error?

Thanks for the clarification. I am using the multipage support introduced in Dash 2.5. Last week, we had a page which was not working, which broke the whole app. I am simulating this issue (an AttributeError in the layout() function of one of the pages).

(relevant) code looks as follows:

app.py

app = dash.Dash(
    __name__,
    suppress_callback_exceptions=True,
    meta_tags=[{
        'name': 'viewport',
        'content': 'width=device-width, initial-scale=1, shrink-to-fit=no'
    }],
    title=routing.BASE_TITLE,
    url_base_pathname='/dashboard/',
    update_title=None,
    use_pages=True
)

server = app.server

# Use HTTPS when deployed
server.config['PREFERRED_URL_SCHEME'] = 'https'
server.wsgi_app = ProxyFix(server.wsgi_app)

app.index_string = """
<!DOCTYPE html>
<html>
    <head>
        {%metas%}
        <!-- Font -->
        <link href='https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap' rel='stylesheet'>
        <title>{%title%}</title>
        {%favicon%}
        {%css%}
    </head>
    <body>
        {%app_entry%}
        <footer>
            {%config%}
            {%scripts%}
            {%renderer%}
        </footer>
    </body>
</html>
"""

@server.errorhandler(500)
def internal_error(error):
    return render_template('500.html')

base.html

<!DOCTYPE html>
<html lang="nl" class="h-100">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Title -->
    <title>{% block title %} {% endblock %}</title>

    <!-- Font -->
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet">

    <!-- CSS Front Template -->
    <link rel="stylesheet" href="/dashboard/assets/build/theme.css">

    <!-- Favicon -->
    <link rel="icon" type="image/x-icon" href="/dashboard/assets/favicon.ico">
</head>

<body class="d-flex align-items-center min-h-100">
<header id="header" class="navbar navbar-expand navbar-light navbar-absolute-top">
    <div class="container-fluid">
        <nav class="navbar-nav-wrap">
            <a class="navbar-brand" href="/">
                <span class="navbar-brand-text-logo">/span>
                <span class="navbar-brand-text-logop-1"></span>
            </a>

            <div class="ms-auto">
                {% if user %}
                    <a class="link link-sm link-primary" href="/logout">
                        <em class="bi-chevron-right small ms-1"></em>
                    </a>
                {% else %}
                    <a class="link link-sm link-primary" href="">
                        <em class="bi-chevron-right small ms-1"></em>
                    </a>
                {% endif %}
                {% block navbar %} {% endblock %}
            </div>
        </nav>
    </div>
</header>

<main id="content" role="main" class="flex-grow-1">
    {% block content %} {% endblock %}
</main>

<script src="/dashboard/assets/static/js/theme.min.js"></script>
</body>
</html>

500.html

{% extends "base.html" %}

{% block title %}Error 500{% endblock %}

{% block content %}
    <div class="container">
        <div class="row">
            <div class="d-flex justify-content-center align-items-center min-vh-lg-100">
                <div class="flex-grow-1 mx-auto" style="max-width: 30rem;">
                    <div class="text-center mb-5 mb-md-7">
                        <h1 class="h2 mb-4"></h1>
                        <dl>
                            <div class="alert alert-danger" role="alert">
                            </div>
                        </dl>

                    </div>
                    <div class="d-grid mb-3">
                        <a href='{{ url_for('index') }}' class="btn btn-primary btn-lg text-white"></a>
                    </div>
                </div>
            </div>
        </div>
    </div>
{% endblock %}

Exception:

ERROR:main:Exception on / [GET]
Traceback (most recent call last):
  File "pat\to\repo\venv\lib\site-packages\flask\app.py", line 2077, in wsgi_app
    response = self.full_dispatch_request()
  File "pat\to\repo\venv\lib\site-packages\flask\app.py", line 1518, in full_dispatch_request
    self.try_trigger_before_first_request_functions()
  File "pat\to\repo\venv\lib\site-packages\flask\app.py", line 1570, in try_trigger_before_first_request_functions
    self.ensure_sync(func)()
  File "pat\to\repo\venv\lib\site-packages\dash\dash.py", line 2079, in router
    [
  File "pat\to\repo\venv\lib\site-packages\dash\dash.py", line 2080, in <listcomp>
    page["layout"]() if callable(page["layout"]) else page["layout"]
  File "pat\to\repo\app\pages\my_account\my_account.py", line 20, in layout
    first_name = user.not_existing_attribute or '-'
AttributeError: 'User' object has no attribute 'not_existing_attribute'
INFO:werkzeug:127.0.0.1 - - [10/Oct/2022 15:30:09] "GET / HTTP/1.1" 200 -
ERROR:main:Exception on /dashboard/assets/build/theme.css [GET]
Traceback (most recent call last):
  File "pat\to\repo\venv\lib\site-packages\flask\app.py", line 2077, in wsgi_app
    response = self.full_dispatch_request()
  File "pat\to\repo\venv\lib\site-packages\flask\app.py", line 1518, in full_dispatch_request
    self.try_trigger_before_first_request_functions()
  File "pat\to\repo\venv\lib\site-packages\flask\app.py", line 1570, in try_trigger_before_first_request_functions
    self.ensure_sync(func)()
  File "pat\to\repo\venv\lib\site-packages\dash\dash.py", line 2079, in router
    [
  File "pat\to\repo\venv\lib\site-packages\dash\dash.py", line 2080, in <listcomp>
    page["layout"]() if callable(page["layout"]) else page["layout"]
  File "pat\to\repo\app\pages\my_account\my_account.py", line 20, in layout
    first_name = user.not_existing_attribute or '-'
AttributeError: 'User' object has no attribute 'not_existing_attribute'
INFO:werkzeug:127.0.0.1 - - [10/Oct/2022 15:30:09] "GET /dashboard/assets/build/theme.css HTTP/1.1" 200 -
ERROR:main:Exception on /dashboard/assets/static/js/theme.min.js [GET]
Traceback (most recent call last):
  File "pat\to\repo\venv\lib\site-packages\flask\app.py", line 2077, in wsgi_app
    response = self.full_dispatch_request()
  File "pat\to\repo\venv\lib\site-packages\flask\app.py", line 1518, in full_dispatch_request
    self.try_trigger_before_first_request_functions()
  File "pat\to\repo\venv\lib\site-packages\flask\app.py", line 1570, in try_trigger_before_first_request_functions
    self.ensure_sync(func)()
  File "pat\to\repo\venv\lib\site-packages\dash\dash.py", line 2079, in router
    [
  File "pat\to\repo\venv\lib\site-packages\dash\dash.py", line 2080, in <listcomp>
    page["layout"]() if callable(page["layout"]) else page["layout"]
  File "pat\to\repo\app\pages\my_account\my_account.py", line 20, in layout
    first_name = user.not_existing_attribute or '-'
AttributeError: 'User' object has no attribute 'not_existing_attribute'
INFO:werkzeug:127.0.0.1 - - [10/Oct/2022 15:30:10] "GET /dashboard/assets/static/js/theme.min.js HTTP/1.1" 200 -
ERROR:main:Exception on /dashboard/assets/favicon.ico [GET]
Traceback (most recent call last):
  File "pat\to\repo\venv\lib\site-packages\flask\app.py", line 2077, in wsgi_app
    response = self.full_dispatch_request()
  File "pat\to\repo\venv\lib\site-packages\flask\app.py", line 1518, in full_dispatch_request
    self.try_trigger_before_first_request_functions()
  File "pat\to\repo\venv\lib\site-packages\flask\app.py", line 1570, in try_trigger_before_first_request_functions
    self.ensure_sync(func)()
  File "pat\to\repo\venv\lib\site-packages\dash\dash.py", line 2079, in router
    [
  File "pat\to\repo\venv\lib\site-packages\dash\dash.py", line 2080, in <listcomp>
    page["layout"]() if callable(page["layout"]) else page["layout"]
  File "pat\to\repo\app\pages\my_account\my_account.py", line 20, in layout
    first_name = user.not_existing_attribute or '-'
AttributeError: 'User' object has no attribute 'not_existing_attribute'
INFO:werkzeug:127.0.0.1 - - [10/Oct/2022 15:30:10] "GET /dashboard/assets/favicon.ico HTTP/1.1" 200 -

If there is any more information needed, please let me know!

Is your css file a customized file based upon the user?

The CSS file is static, the exception shown is from a different page. I am running the application locally as follows (debug is set to False to show me the 500 page instead of a stack trace):

if __name__ == '__main__':
    app.run_server(debug=False, host='localhost')

Maybe this does work when deployed to a server?

It depends.

If you are using a reverse proxy, then it’d fine to run it on the localhost. However, you are trying to navigate to it via ip and port, then you can’t unless you pass ‘0.0.0.0’ as the host.

It looked like according to the trace back that the server is seeing your request.

Can you upgrade Dash and see if that solves the issue? Dash is at 2.6.2 now.

I was using Dash 2.6.1, updating to Dash 2.6.2 fixes the problem of other pages not loading (and displaying the 500 error), if one of the pages is broken! Thanks so much for the help thus far.

Now when a page is broken, only the navbar is shown for that page, and the dash.page_container remains empty. I wonder if it is possible to display an error message instead of an empty container?

Ah, one of your posts said 2.5. But glad that fixed it somewhat.

When you say, “broken”, is it that the page doesnt send a valid response? Or the page is completely not working?

def layout():
     return x/0

vs

no valid layout

By broken, I indeed mean something like the first option. For example, last week, some users had an invalid database field set which caused an exception on one of the pages, which broke the entire app. After applying the update, only the page which it is used on will be broken.

I could probably wrap the contents of the layout function in a try/except clause, but that’s not the cleanest option I suppose.

You can do that. I was looking at the after_request, but it looked a little too complex for right now. And would wreck havoc on the design in some instances.

What you can do is something along these lines:

def alertError(subject, error):
     msg = Message(subject=flask_login.current_user.id + ' ' + subject,
                  sender='youremail',
                  recipients=['youritdepemails'])
    msg.body = error
    mail.send(msg)

Where this will send a customized email about what happened based on the info you give it. Using flask-mail as the vehicle.

You implement it this way:

def layout():
      try:
           layout = x/0
      except:
          alertError(flask.request.url + ' issue', traceback.format_exec())
          layout = html.Div(["Something didn't work quite right."])
      return layout

Awesome, thank you so much for all the help!

In fact, you could probably use the alertError to return your error layout as well.