Serve_locally option with additional scripts and style sheets

Update July 11, 2018

:wave: Hi Dash Community, @chriddyp here. We’re working on a better solution for this here: https://github.com/plotly/dash/pull/286. It will be released within the coming week.


Hi,

how to use the app.css.config.serve_locally and app.scripts.config.serve_locally option for scripts and style sheets that are not part of the dash package? It seems like @app.server.route is ignored if serve_locally is active.

The warning message is comprehensible, because the custom style sheet is not part of dash:

~/anaconda3/lib/python3.6/site-packages/dash/resources.py:35: UserWarning:

A local version of /static/style.css is not available

Background: I’m behind a firewall and cannot access CDN to load the dash/react javascript files. But I also have a custom CSS style sheet, served by a flask route.

Thanks

I’m not sure what is the final goal of your app, but assuming you want eventually to deploy it publically, most likely you will need the Nginx server for that. Said all that, I also reuse the Nginx server to serve the static assets, that Plotly Dash picks up. That includes scripts, css, images.

More details on my blog

The application should run behind a company firewall. There should be no communication to the internet. Why should I use Nginx to to serve the static content?Is there no way to use serve_locally and static routes side by side?

I would say you by no means must use Nginx if you don’t serve your app in public. But you still can use it even on your private network.

In any case, I’m myself a physicist by background and cant really answer all the Whys. I’ve simply implemented Nginx->Gunicorn->Flask chain because internet recommends it as a best practice. As a part of this chain, I have also found a way how to utilize Nginx for serving static assets. Sort of win-win-win, all boxes ticked.

If you’re only deploying to a small number of users, then you can probably get away with just using Flask to serve your static content and also just using the Flask development server (as used in the Dash Guide).

Using a web server for hosting static content (like Nginx or Apache) will allow your app to scale to many more users, and using a WSGI server such as gunicorn will provide more robustness and again scalability of the of the app itself.

@HansG - For now, you can insert CSS with a html.Link component inside app.layout. This will bypass the unflexible append_css method. Here’s an example:

import dash
import dash_html_components as html
import os

from flask import send_from_directory

app = dash.Dash()
app.css.config.serve_locally = True
app.scripts.config.serve_locally = True

app.layout = html.Div([

    html.Link(
        rel='stylesheet',
        href='/static/stylesheet.css'
    ),

    html.Div('Hello world')

])


@app.server.route('/static/<path:path>')
def static_file(path):
    static_folder = os.path.join(os.getcwd(), 'static')
    return send_from_directory(static_folder, path)


if __name__ == '__main__':
    app.run_server(debug=True)

and here’s the folder structure:

-- app.py
-- static/
-- static/stylesheet.css

(Note that this solution was originally shared in this topic: How do I use Dash to add local css?)

1 Like

thank you! Adding the style sheet within a link bypasses the problem.
The app is now available without any internet connection.

1 Like

I’m looking to update this behaviour (adding external CSS and JS) in this PR: https://github.com/plotly/dash/pull/171. Feedback welcome! (Please comment on the PR itself)

2 Likes

I’ve managed to use the html.Link solution above to serve local css files in my dash app, however, subsequent changes to the local css are not reflected in the app. The original css is being loaded instead. Is this being cached somewhere? Is there any way that I can purge this?

At the moment I basically have to change the name of the css file and the href in the main app to get it to load any changes.

Thanks in advance.

The browser is likely caching these assets. You can force the browser to refresh and also bypass the cache by hitting CTRL+F5.

Under chrome, forcing a refresh/by-passing the cache with CTRL+F5 doesn’t resolve the issue. The old stylesheet is still being used. The chrome developer tools panel tells me this is being take from the disk cache, has a max-age=43200 and was initiated by http://localhost:8050/_dash-component-suites/dash_renderer/react-dom@15.4.2.min.js?v=0.11.3.

It would appear this is linked to the react issue in this thread: https://github.com/facebook/create-react-app/issues/1910

Ah, frustrating.

I never liked the html.Link solution much myself, as for the app I tried it on it resulted in a momentary period of time where the styles were not applied, while the sheet was loaded. This is somewhat jarring, so I gave up on that. The other approach you can take until Dash properly supports adding you own entries in the header, is to subclass the Dash class and override the index method which includes any stylesheets you want.

Like this for example:

from dash import Dash

STYLESHEETS = ['foo.css', 'bar.css']

class CustomIndexDash(Dash):
    """Custom Dash class overriding index() method for local CSS support"""
    def _generate_css_custom_html(self):
        link_str = '<link rel="stylesheet" href="{}/{}">'
        static_url_path = self.server.config['STATIC_URL_PATH']
        return '\n'.join(link_str.format(static_url_path, path)
                         for path in STYLESHEETS)

    def index(self, *args, **kwargs):
        scripts = self._generate_scripts_html()
        css = self._generate_css_dist_html()
        custom_css = self._generate_css_custom_html()
        config = self._generate_config_html()
        title = getattr(self, 'title', 'Dash')
        return f'''
        <!DOCTYPE html>
        <html>
            <head>
                <meta charset="UTF-8">
                <title>{title}</title>
                {css}
                {custom_css}
            </head>
            <body>
                <div id="react-entry-point">
                    <div class="_dash-loading">
                        Loading...
                    </div>
                </div>
                <footer>
                    {config}
                    {scripts}
                </footer>
            </body>
        </html>
        '''
2 Likes

Thanks for the response. I managed to get this working with a bit of tweaking. The line

static_url_path = self.server.config['STATIC_URL_PATH']

in _generate_css_custom_html threw a KeyError, even when I tried to initialise the app like a flask instance using

app = CustomIndexDash(__name__,static_url_path='',static_folder='static')

Changing the _generate_css_custom_html line to

static_url_path = ''

sorted the issue. CSS changes were reflected in the app in chrome when refreshing page with CRTL+F5 to clear the cache. Thank you very much!

I am following the following issue: It works perfectly untill I make any change at the .css file. For example, I change the background-color at the file and this does not apply any change to the interface. If I want to see the change I have to save the modified file with a different name (e.g. stylesheet_new.css), then change the href='/static/stylesheet_new.css and only then I will notice the changes.

Hey @Dimkoim,

This is due to browser caching as discussed by @nedned and @adm78. You can work around this by using their approach:

import dash

STYLESHEETS = ['style.css']

class CustomIndexDash(dash.Dash):
    """Custom Dash class overriding index() method for local CSS support"""
    def _generate_css_custom_html(self):
        link_str = '<link rel="stylesheet" href="{}/{}">'
        static_url_path = 'static'
        return '\n'.join(link_str.format(static_url_path, path)
                         for path in STYLESHEETS)

    def index(self, *args, **kwargs):
        scripts = self._generate_scripts_html()
        css = self._generate_css_dist_html()
        custom_css = self._generate_css_custom_html()
        config = self._generate_config_html()
        title = getattr(self, 'title', 'Dash')
        return f'''
        <!DOCTYPE html>
        <html>
            <head>
                <meta charset="UTF-8">
                <title>{title}</title>
                {css}
                {custom_css}
            </head>
            <body>
                <div id="react-entry-point">
                    <div class="_dash-loading">
                        Loading...
                    </div>
                </div>
                <footer>
                    {config}
                    {scripts}
                </footer>
            </body>
        </html>
        '''

app = CustomIndexDash(
    __name__,
    # Serve any files that are available in the `static` folder
    static_folder='static'
)

for the folder structure of:

-- app.py
-- static/
-- static/style.css

Thanks for your reply! Still does not work for me. I have to clear my cache in order to see any change.

Hello. We are using Dash to build a fairly straight forward web app. This app includes custom stylesheets, custom Dash components and other custom javascript. We have our own static content web server (not nginx but equivalent). We want to serve everything locally from this server including the javascript.

However, when we set

app.css.config.serve_locally = True
app.scripts.config.serve_locally = True

Dash will only apparently try to serve it’s own files and ignores ours even though we have added our files to app.css.append_css and app.scripts.append_script.

The work around given here (thanks for that!) using html.Link does work for css files but there seems to be no equivalent way of loading our custom javascript when serve_locally is true. Doing the same but with the html.Script component seems to have no effect.

Does anyone have any ideas or suggestions? The problem does not seem to be how we are serving things statically but rather in how Dash itself works.

Cheers,
andrewp

If you’re silly like me and 1) didn’t know what f-strings were and 2) didn’t realize that they didn’t exist until python 3.6 and 3) you need to use python 2.7 for some reason… use:

return '''
    <!DOCTYPE html>
    <html>
        ...
    </html>
    '''.format(title=title, css=css, custom_css=custom_css, config=config, scripts=scripts)
1 Like

Hi,

while some of these answers work for single small settings, my situation is a bit different. I develop a larger server app, with flask pages and dash pages, moreover I am abroad and have only very limited internet resources, so I would like to develop the flask/dash app without internet connection.

I serve my css, and js files via a /static/ Flask route, which works when I set both of the next two lines to ‘False’

dash_app.scripts.config.serve_locally=True
dash_app.css.config.serve_locally=False

However, my Flask pages need also jQuery which I would like to server also via the static route.
If both set to “False” my static rout provides the relevant css and js files, all is well, but the React components needed to render dash pages are not loaded (no inet connection). If I set the lines to “True” and “False” as shown above, Flask is unhappy (as the locally stored jQuery et all. are not loaded) but the dash pages have the react scripts…

Any idea how to solve my offline problem?

In the end the server runs with an internet connection, but for development without internet the situation is rather daring. (When the React components are being tried to load from the iNet, the server start is rather long winded, as there are many timeouts to wait for, this makes offline development essentially not possible)

Help and ideas welcome!
Cheers

PS: Maybe the app.head functionality can help? I have not investigated that yet. See allow optional headers:

We’re working on a better solution for this in https://github.com/plotly/dash/pull/286. Please hang tight!