diff --git a/.gitignore b/.gitignore
index 6bc3d3d5653f7a099a28619cb5af41db2d4ba701..8a683efdb8a787450da3720fcc21e87e0027b980 100644
--- a/.gitignore
+++ b/.gitignore
@@ -54,7 +54,7 @@ coverage.xml
 *.log
 local_settings.py
 */db.sqlite3
-demo/static/*
+demo/staticfiles/*
 
 # Flask stuff:
 instance/
diff --git a/demo/demo/assets/image_one.png b/demo/demo/assets/image_one.png
new file mode 100644
index 0000000000000000000000000000000000000000..6b839e88512e88de381b09ea0f0fc6072e2c42a9
Binary files /dev/null and b/demo/demo/assets/image_one.png differ
diff --git a/demo/demo/plotly_apps.py b/demo/demo/plotly_apps.py
index 9ca734644bdf45542abc8d493a119ab961889b34..de3952a697cc932f549b317308ca9e16792abb50 100644
--- a/demo/demo/plotly_apps.py
+++ b/demo/demo/plotly_apps.py
@@ -84,6 +84,7 @@ def callback_size(dropdown_color, dropdown_size):
 
 a2 = DjangoDash("Ex2",
                 serve_locally=True)
+
 a2.layout = html.Div([
     dcc.RadioItems(id="dropdown-one",
                    options=[{'label':i, 'value':j} for i, j in [("O2", "Oxygen"),
@@ -186,8 +187,7 @@ def callback_liveIn_button_press(red_clicks, blue_clicks, green_clicks,
                                                                                              change_col,
                                                                                              datetime.fromtimestamp(0.001*timestamp))
 
-liveOut = DjangoDash("LiveOutput",
-                    )#serve_locally=True)
+liveOut = DjangoDash("LiveOutput")
 
 def _get_cache_key(state_uid):
     return "demo-liveout-s6-%s" % state_uid
@@ -309,3 +309,10 @@ def callback_show_timeseries(internal_state_string, state_uid, **kwargs):
     return {'data':traces,
             #'layout': go.Layout
            }
+
+localState = DjangoDash("LocalState",
+                        serve_locally=True)
+
+localState.layout = html.Div([html.Img(src=localState.get_asset_url('image_one.png')),
+                              html.Img(src='assets/image_two.png'),
+                              ])
diff --git a/demo/demo/settings.py b/demo/demo/settings.py
index e906b6a90cca6231fd39c53055f09753d8ca830c..a24315d0de571dacb2f043dbef846eef2d366058 100644
--- a/demo/demo/settings.py
+++ b/demo/demo/settings.py
@@ -42,10 +42,14 @@ INSTALLED_APPS = [
     'bootstrap4',
 
     'django_plotly_dash.apps.DjangoPlotlyDashConfig',
+    'dpd_static_support',
 ]
 
 MIDDLEWARE = [
     'django.middleware.security.SecurityMiddleware',
+
+    'whitenoise.middleware.WhiteNoiseMiddleware',
+
     'django.contrib.sessions.middleware.SessionMiddleware',
     'django.middleware.common.CommonMiddleware',
     'django.middleware.csrf.CsrfViewMiddleware',
@@ -53,6 +57,7 @@ MIDDLEWARE = [
     'django.contrib.messages.middleware.MessageMiddleware',
 
     'django_plotly_dash.middleware.BaseMiddleware',
+    'django_plotly_dash.middleware.ExternalRedirectionMiddleware',
 
     'django.middleware.clickjacking.XFrameOptionsMiddleware',
 ]
@@ -134,13 +139,15 @@ PLOTLY_DASH = {
     "view_decorator" : None, # Specify a function to be used to wrap each of the dpd view functions
 
     "cache_arguments" : True, # True for cache, False for session-based argument propagation
+
+    #"serve_locally" : True, # True to serve assets locally, False to use their unadulterated urls (eg a CDN)
     }
 
 # Static files (CSS, JavaScript, Images)
 # https://docs.djangoproject.com/en/2.0/howto/static-files/
 
 STATIC_URL = '/static/'
-STATIC_ROOT = os.path.join(BASE_DIR, 'static')
+STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
 
 STATICFILES_DIRS = [
     os.path.join(BASE_DIR, 'demo', 'static'),
@@ -178,6 +185,7 @@ STATICFILES_FINDERS = [
 
     'django_plotly_dash.finders.DashAssetFinder',
     'django_plotly_dash.finders.DashComponentFinder',
+    'django_plotly_dash.finders.DashAppDirectoryFinder',
 ]
 
 # Plotly components containing static content that should
@@ -189,4 +197,5 @@ PLOTLY_COMPONENTS = [
     'dash_bootstrap_components',
     'dash_renderer',
     'dpd_components',
+    'dpd_static_support',
 ]
diff --git a/demo/demo/templates/demo_nine.html b/demo/demo/templates/demo_nine.html
new file mode 100644
index 0000000000000000000000000000000000000000..d21244e41e2cf79f229defc65846bf166d9f3b25
--- /dev/null
+++ b/demo/demo/templates/demo_nine.html
@@ -0,0 +1,33 @@
+{%extends "base.html"%}
+{%load plotly_dash%}
+
+{%block title%}Demo Nine - Serving local assets{%endblock%}
+
+{%block content%}
+<h1>Serving Local Assets</h1>
+<p>
+  This example demonstrates serving local assets as part of a Dash app.
+</p>
+<p>
+  The extra files are specified using the standard plotly dash approach, and are
+  made available through the standard Django staticfiles infrastructure. This means that
+  they will be served the same way as other static files through a reverse proxy.
+</p>
+<p></p>
+<div class="card bg-light border-dark">
+  <div class="card-body">
+    <p><span>{</span>% load plotly_dash %}</p>
+    <p>&lt;div class="<span>{</span>% plotly_class name="LocalState"%}">
+    <p class="ml-3"><span>{</span>% plotly_app name="LocalState" ratio=0.3 %}</p>
+    <p>&lt;\div>
+  </div>
+</div>
+<p></p>
+<div class="card border-dark">
+  <div class="card-body">
+    <div class="{%plotly_class name="LocalState"%}">
+      {%plotly_app name="LocalState" ratio=0.3 %}
+    </div>
+  </div>
+</div>
+{%endblock%}
diff --git a/demo/demo/templates/index.html b/demo/demo/templates/index.html
index d6e727ad44af6eff0566fe99c5efe13ebb4bbdc4..f0206301a2924a79d07d43da3d82cbc642d06f7d 100644
--- a/demo/demo/templates/index.html
+++ b/demo/demo/templates/index.html
@@ -15,5 +15,6 @@
     <li><a class="btn btn-primary btnspace" href="{%url "demo-six"%}">Demo Six</a> - simple html injection example</li>
     <li><a class="btn btn-primary btnspace" href="{%url "demo-seven"%}">Demo Seven</a> - dash-bootstrap-components example</li>
     <li><a class="btn btn-primary btnspace" href="{%url "demo-eight"%}">Demo Eight</a> - Django session state example</li>
+    <li><a class="btn btn-primary btnspace" href="{%url "demo-nine"%}">Demo Nine</a> - local serving of assets</li>
   </ul>
 {%endblock%}
diff --git a/demo/demo/urls.py b/demo/demo/urls.py
index 0db3cf9ea0a1976794a94d7a8a64501664241584..0e4f215ed6980baea22ca2503386dd4d6685d743 100644
--- a/demo/demo/urls.py
+++ b/demo/demo/urls.py
@@ -44,6 +44,7 @@ urlpatterns = [
     url('^demo-six', dash_example_1_view, name="demo-six"),
     url('^demo-seven', TemplateView.as_view(template_name='demo_seven.html'), name="demo-seven"),
     url('^demo-eight', session_state_view, {'template_name':'demo_eight.html'}, name="demo-eight"),
+    url('^demo-nine', TemplateView.as_view(template_name='demo_nine.html'), name="demo-nine"),
     url('^admin/', admin.site.urls),
     url('^django_plotly_dash/', include('django_plotly_dash.urls')),
 
diff --git a/dev_requirements.txt b/dev_requirements.txt
index a943b788b5dc153d44ad105aa385fd488e8a49bf..3ee1d379a6505ecde7e34d8cfe2a67765d7b2414 100644
--- a/dev_requirements.txt
+++ b/dev_requirements.txt
@@ -1,9 +1,10 @@
 channels>=2.0
 channels-redis
 daphne
-Django>=2.0
+Django>=2.0,<2.2
 django-bootstrap4
 django-redis
+dpd-static-support
 grip
 pandas
 pylint
@@ -16,4 +17,5 @@ redis
 sphinx
 sphinx-autobuild
 twine
+whitenoise
 
diff --git a/django_plotly_dash/assets/some_asset b/django_plotly_dash/assets/some_asset
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/django_plotly_dash/dash_wrapper.py b/django_plotly_dash/dash_wrapper.py
index e0177f857370cba1ffe3233fb9bd328e74ebfa0d..47ba43421e6c42f4de147adb53cb00bc687c1cf7 100644
--- a/django_plotly_dash/dash_wrapper.py
+++ b/django_plotly_dash/dash_wrapper.py
@@ -39,6 +39,8 @@ from plotly.utils import PlotlyJSONEncoder
 from .app_name import app_name, main_view_label
 from .middleware import EmbeddedHolder
 
+from .util import static_asset_path
+from .util import serve_locally as serve_locally_setting
 
 uid_counter = 0
 
@@ -84,11 +86,12 @@ class DjangoDash:
     To use, construct an instance of DjangoDash() in place of a Dash() one.
     '''
     #pylint: disable=too-many-instance-attributes
-    def __init__(self, name=None, serve_locally=False,
+    def __init__(self, name=None, serve_locally=None,
                  expanded_callbacks=False,
                  add_bootstrap_links=False,
                  suppress_callback_exceptions=False,
                  **kwargs): # pylint: disable=unused-argument, too-many-arguments
+
         if name is None:
             global uid_counter # pylint: disable=global-statement
             uid_counter += 1
@@ -104,19 +107,40 @@ class DjangoDash:
         add_usable_app(self._uid,
                        self)
 
+        if serve_locally is None:
+            self._serve_locally = serve_locally_setting()
+        else:
+            self._serve_locally = serve_locally
+
         self._expanded_callbacks = expanded_callbacks
-        self._serve_locally = serve_locally
         self._suppress_callback_exceptions = suppress_callback_exceptions
 
         if add_bootstrap_links:
             from bootstrap4.bootstrap import css_url
             bootstrap_source = css_url()['href']
-            self.css.append_script({'external_url':[bootstrap_source,]})
+
+            if self._serve_locally:
+                # Ensure package is loaded; if not present then pip install dpd-static-support
+                import dpd_static_support
+                hard_coded_package_name = "dpd_static_support"
+                base_file_name = bootstrap_source.split('/')[-1]
+
+                self.css.append_script({'external_url':        [bootstrap_source,],
+                                        'relative_package_path' : base_file_name,
+                                        'namespace':              hard_coded_package_name,
+                                        })
+            else:
+                self.css.append_script({'external_url':[bootstrap_source,],})
 
         # Remember some caller info for static files
         caller_frame = inspect.stack()[1]
         self.caller_module = inspect.getmodule(caller_frame[0])
         self.caller_module_location = inspect.getfile(self.caller_module)
+        self.assets_folder = "assets"
+
+    def get_asset_static_url(self, asset_path):
+        module_name = self.caller_module.__name__
+        return static_asset_path(module_name, asset_path)
 
     def as_dash_instance(self, cache_id=None):
         '''
@@ -205,6 +229,16 @@ class DjangoDash:
         self._expanded_callbacks = True
         return self.callback(output, inputs, state, events)
 
+    def get_asset_url(self, asset_name):
+        '''URL of an asset associated with this component
+
+        Use a placeholder and insert later
+        '''
+
+        return "assets/" + str(asset_name)
+
+        #return self.as_dash_instance().get_asset_url(asset_name)
+
 class PseudoFlask:
     'Dummy implementation of a Flask instance, providing stub functionality'
     def __init__(self):
@@ -234,7 +268,8 @@ class WrappedDash(Dash):
     # pylint: disable=too-many-arguments, too-many-instance-attributes
     def __init__(self,
                  base_pathname=None, replacements=None, ndid=None,
-                 expanded_callbacks=False, serve_locally=False, **kwargs):
+                 expanded_callbacks=False, serve_locally=False,
+                 **kwargs):
 
         self._uid = ndid
 
@@ -245,7 +280,8 @@ class WrappedDash(Dash):
         kwargs['url_base_pathname'] = self._base_pathname
         kwargs['server'] = self._notflask
 
-        super(WrappedDash, self).__init__(**kwargs)
+        super(WrappedDash, self).__init__(__name__,
+                                          **kwargs)
 
         self.css.config.serve_locally = serve_locally
         self.scripts.config.serve_locally = serve_locally
@@ -507,6 +543,8 @@ class WrappedDash(Dash):
 
     def interpolate_index(self, **kwargs): #pylint: disable=arguments-differ
 
+        print("IN INTERPOLATE INDEX")
+
         if not self._return_embedded:
             resp = super(WrappedDash, self).interpolate_index(**kwargs)
             return resp
diff --git a/django_plotly_dash/finders.py b/django_plotly_dash/finders.py
index d7b945791cc858eeadcc738400aec1548fdf3590..d9a5cb40d771001accfa27b0a1b6440cc934bd20 100644
--- a/django_plotly_dash/finders.py
+++ b/django_plotly_dash/finders.py
@@ -33,9 +33,10 @@ from django.contrib.staticfiles.utils import get_files
 from django.core.files.storage import FileSystemStorage
 
 from django.conf import settings
-from django.apps import apps #pylint: disable=unused-import
+from django.apps import apps
 
 from django_plotly_dash.dash_wrapper import all_apps
+from django_plotly_dash.util import full_asset_path
 
 class DashComponentFinder(BaseFinder):
     'Find static files in components'
@@ -104,9 +105,45 @@ class DashComponentFinder(BaseFinder):
         for component_name in self.locations:
             storage = self.storages[component_name]
             for path in get_files(storage, ignore_patterns + self.ignore_patterns):
-                print("DashAssetFinder", path, storage)
                 yield path, storage
 
+class DashAppDirectoryFinder(BaseFinder):
+    'Find static fies in application subdirectories'
+
+    def __init__(self):
+        # get all registered apps
+
+        self.locations = []
+        self.storages = OrderedDict()
+
+        self.ignore_patterns = ["*.py", "*.pyc",]
+
+        for app_config in apps.get_app_configs():
+
+            path_directory = os.path.join(app_config.path, 'assets')
+
+            if os.path.isdir(path_directory):
+
+                storage = FileSystemStorage(location=path_directory)
+
+                storage.prefix = full_asset_path(app_config.name, "")
+
+                self.locations.append(app_config.name)
+                self.storages[app_config.name] = storage
+
+        super(DashAppDirectoryFinder, self).__init__()
+
+    #pylint: disable=redefined-builtin
+    def find(self, path, all=False):
+        return []
+
+    def list(self, ignore_patterns):
+        for component_name in self.locations:
+            storage = self.storages[component_name]
+            for path in get_files(storage, ignore_patterns + self.ignore_patterns):
+                yield path, storage
+
+
 class DashAssetFinder(BaseFinder):
     'Find static files in asset directories'
 
@@ -114,27 +151,34 @@ class DashAssetFinder(BaseFinder):
 
     def __init__(self):
 
-        # Get all registered apps
+        # Ensure urls are loaded
+        root_urls = settings.ROOT_URLCONF
+        importlib.import_module(root_urls)
 
-        self.apps = all_apps()
+        # Get all registered django dash apps
 
-        self.subdir = 'assets'
+        self.apps = all_apps()
 
         self.locations = []
         self.storages = OrderedDict()
 
         self.ignore_patterns = ["*.py", "*.pyc",]
 
+        added_locations = {}
+
         for app_slug, obj in self.apps.items():
+
             caller_module = obj.caller_module
             location = obj.caller_module_location
-            path_directory = os.path.join(os.path.dirname(location), self.subdir)
+            subdir = obj.assets_folder
+
+            path_directory = os.path.join(os.path.dirname(location), subdir)
 
             if os.path.isdir(path_directory):
 
                 component_name = app_slug
                 storage = FileSystemStorage(location=path_directory)
-                path = "dash/assets/%s" % component_name
+                path = full_asset_path(obj.caller_module.__name__,"")
                 storage.prefix = path
 
                 self.locations.append(component_name)
@@ -151,3 +195,4 @@ class DashAssetFinder(BaseFinder):
             storage = self.storages[component_name]
             for path in get_files(storage, ignore_patterns + self.ignore_patterns):
                 yield path, storage
+
diff --git a/django_plotly_dash/middleware.py b/django_plotly_dash/middleware.py
index 89000ffabb567ab17d3e5cad94e563810b84433f..44feb8b2bbd3b4ccaa20385c52e564bf8c893c30 100644
--- a/django_plotly_dash/middleware.py
+++ b/django_plotly_dash/middleware.py
@@ -25,6 +25,8 @@ SOFTWARE.
 
 '''
 
+from .util import serve_locally
+
 #pylint: disable=too-few-public-methods
 
 class EmbeddedHolder:
@@ -98,3 +100,44 @@ class BaseMiddleware:
         response = request.dpd_content_handler.adjust_response(response)
 
         return response
+
+
+# Bootstrap4 substitutions, if available
+try:
+    from dpd_static_support.mappings import substitutions as dpd_ss_substitutions
+    substitutions += dpd_ss_substitutions
+except Exception as e:
+    pass
+
+
+class ExternalRedirectionMiddleware:
+    'Middleware to force redirection in third-party content through rewriting'
+
+    def __init__(self, get_response):
+        self.get_response = get_response
+
+        substitutions = []
+
+        if serve_locally():
+            substitutions += dpd_ss_substitutions
+
+        self._encoding = "utf-8"
+
+        self.substitutions = [(self._encode(source),
+                               self._encode(target)) for source, target in substitutions]
+
+    def __call__(self, request):
+
+        response = self.get_response(request)
+
+        content = response.content
+
+        for source, target in self.substitutions:
+            content = content.replace(source, target)
+
+        response.content = content
+        return response
+
+    def _encode(self, string):
+        return string.encode(self._encoding)
+
diff --git a/django_plotly_dash/tests.py b/django_plotly_dash/tests.py
index f63f1fb4c900702b8652377fed4c01c36f34b8b4..eb6723473b68963b9fabb2421e39b31f207430d7 100644
--- a/django_plotly_dash/tests.py
+++ b/django_plotly_dash/tests.py
@@ -67,6 +67,14 @@ def test_demo_routing():
     assert pipe_ws_endpoint_name() == 'ws/channel'
     assert insert_demo_migrations()
 
+def test_local_serving(settings):
+    'Test local serve settings'
+
+    from django_plotly_dash.util import serve_locally, static_asset_root, full_asset_path
+    assert serve_locally() == settings.DEBUG
+    assert static_asset_root() == 'dpd/assets'
+    assert full_asset_path('fred.jim', 'harry') == 'dpd/assets/fred/jim/harry'
+
 @pytest.mark.django_db
 def test_direct_access(client):
     'Check direct use of a stateless application using demo test data'
@@ -244,3 +252,32 @@ def test_argument_settings(settings, client):
     assert get_initial_arguments(None, None) is None
     assert store_initial_arguments(client, None) is None
     assert get_initial_arguments(client, None) is None
+
+def test_middleware_artifacts():
+    'Import and vaguely exercise middleware objects'
+
+    from django_plotly_dash.middleware import EmbeddedHolder, ContentCollector
+
+    eh = EmbeddedHolder()
+    eh.add_css("some_css")
+    eh.add_config("some_config")
+    eh.add_scripts("some_scripts")
+
+    assert eh.config == 'some_config'
+
+    cc = ContentCollector()
+
+    assert cc._encode("fred") == b'fred'
+
+def test_finders():
+    'Import and vaguely exercise staticfiles finders'
+
+    from django_plotly_dash.finders import DashComponentFinder, DashAppDirectoryFinder, DashAssetFinder
+
+    dcf = DashComponentFinder()
+    dadf = DashAppDirectoryFinder()
+    daf = DashAssetFinder()
+
+    assert dcf is not None
+    assert dadf is not None
+    assert daf is not None
diff --git a/django_plotly_dash/urls.py b/django_plotly_dash/urls.py
index 9bf179ff08397c5281b16ace544da016756f14af..7e2a10ef60a6979f2ac40bb79517ac4ebf3d92c5 100644
--- a/django_plotly_dash/urls.py
+++ b/django_plotly_dash/urls.py
@@ -27,7 +27,7 @@ SOFTWARE.
 from django.urls import path
 from django.views.decorators.csrf import csrf_exempt
 
-from .views import routes, layout, dependencies, update, main_view, component_suites, component_component_suites
+from .views import routes, layout, dependencies, update, main_view, component_suites, component_component_suites, asset_redirection
 
 from .app_name import app_name, main_view_label
 
@@ -48,6 +48,7 @@ for base_type, args, name_prefix, url_ending, name_suffix in [('instance', {}, '
                                                       ('', main_view, main_view_label, '', ),
                                                       ('_dash-component-suites', component_suites, 'component-suites', '/<slug:component>/<resource>', ),
                                                       ('_dash-component-suites', component_component_suites, 'component-component-suites', '/<slug:component>/_components/<resource>', ),
+                                                      ('assets', asset_redirection, 'asset-redirect', '/<path:path>', ),
                                                      ]:
 
         route_name = '%s%s%s' % (name_prefix, name, name_suffix)
diff --git a/django_plotly_dash/util.py b/django_plotly_dash/util.py
index d180bd15ef9f0ecd1b949dc47dc79a75f1d59ef2..15fb9d754ab78b25f82eaca48833d69ab5792ff7 100644
--- a/django_plotly_dash/util.py
+++ b/django_plotly_dash/util.py
@@ -27,6 +27,7 @@ import uuid
 
 from django.conf import settings
 from django.core.cache import cache
+from django.contrib.staticfiles.templatetags.staticfiles import static
 
 def _get_settings():
     try:
@@ -95,3 +96,18 @@ def get_initial_arguments(request, cache_id=None):
         return cache.get(cache_id)
 
     return request.session[cache_id]
+
+def static_asset_root():
+    return _get_settings().get('static_asset_root','dpd/assets')
+
+def full_asset_path(module_name, asset_path):
+    path_contrib = "%s/%s/%s" %(static_asset_root(),
+                                "/".join(module_name.split(".")),
+                                asset_path)
+    return path_contrib
+
+def static_asset_path(module_name, asset_path):
+    return static(full_asset_path(module_name, asset_path))
+
+def serve_locally():
+    return _get_settings().get('serve_locally', settings.DEBUG)
diff --git a/django_plotly_dash/views.py b/django_plotly_dash/views.py
index 814eed71effc556077f4e2791e6ed8f90468e788..27148e1465b9caa46227c514389ef52a2eeab57d 100644
--- a/django_plotly_dash/views.py
+++ b/django_plotly_dash/views.py
@@ -27,6 +27,7 @@ SOFTWARE.
 import json
 
 from django.http import HttpResponse, HttpResponseRedirect
+from django.shortcuts import redirect
 
 from .models import DashApp
 from .util import get_initial_arguments
@@ -117,8 +118,6 @@ def component_suites(request, resource=None, component=None, extra_element="", *
     else:
         redone_url = "/static/dash/component/%s/%s%s" %(component, extra_element, resource)
 
-    print("Redirecting to :", redone_url)
-
     return HttpResponseRedirect(redirect_to=redone_url)
 
 def app_assets(request, **kwargs):
@@ -145,3 +144,14 @@ def add_to_session(request, template_name="index.html", **kwargs):
     request.session['django_plotly_dash'] = django_plotly_dash
 
     return TemplateResponse(request, template_name, {})
+
+def asset_redirection(request, path, ident=None, stateless=False, **kwargs):
+    'Redirect static assets for a component'
+
+    X, app = DashApp.locate_item(ident, stateless)
+
+    # Redirect to a location based on the import path of the module containing the DjangoDash app
+    static_path = X.get_asset_static_url(path)
+
+    return redirect(static_path)
+
diff --git a/docs/configuration.rst b/docs/configuration.rst
index 3883afe7241241252ca8f7b821ee420a687b8b64..b1b01f874c82e7babd0f5825b5e6affcb165d7bc 100644
--- a/docs/configuration.rst
+++ b/docs/configuration.rst
@@ -30,6 +30,9 @@ below.
 
       # Flag to control location of initial argument storage
       "cache_arguments": True,
+
+      # Flag controlling local serving of assets
+      "serve_locally': settings.DEBUG,
   }
 
 Defaults are inserted for missing values. It is also permissible to not have any ``PLOTLY_DASH`` entry in
@@ -44,10 +47,13 @@ file finders
   # Staticfiles finders for locating dash app assets and related files
 
   STATICFILES_FINDERS = [
+
       'django.contrib.staticfiles.finders.FileSystemFinder',
       'django.contrib.staticfiles.finders.AppDirectoriesFinder',
+
       'django_plotly_dash.finders.DashAssetFinder',
       'django_plotly_dash.finders.DashComponentFinder',
+      'django_plotly_dash.finders.DashAppDirectoryFinder',
   ]
 
 and also providing a list of components used
@@ -74,6 +80,41 @@ and also providing a list of components used
 This list should be extended with any additional components that the applications
 use, where the components have files that have to be served locally.
 
+Furthermore, middleware should be added for redirection of external assets from
+underlying packages, such as ``dash-bootstrap-components``. With the standard
+Django middleware, along with ``whitenoise``, the entry within the ``settings.py``
+file will look something like
+
+.. code-block:: python
+
+  # Standard Django middleware with the addition of both
+  # whitenoise and django_plotly_dash items
+
+  MIDDLEWARE = [
+
+        'django.middleware.security.SecurityMiddleware',
+
+        'whitenoise.middleware.WhiteNoiseMiddleware',
+
+        'django.contrib.sessions.middleware.SessionMiddleware',
+        'django.middleware.common.CommonMiddleware',
+        'django.middleware.csrf.CsrfViewMiddleware',
+        'django.contrib.auth.middleware.AuthenticationMiddleware',
+        'django.contrib.messages.middleware.MessageMiddleware',
+
+        'django_plotly_dash.middleware.BaseMiddleware',
+        'django_plotly_dash.middleware.ExternalRedirectionMiddleware',
+
+        'django.middleware.clickjacking.XFrameOptionsMiddleware',
+    ]
+
+
+Individual apps can set their ``serve_locally`` flag. However, it is recommended to use
+the equivalent global ``PLOTLY_DASH`` setting to provide a common approach for all
+static assets. See :ref:`local_assets` for more information on how local assets are configured
+and served as part of the standard Django staticfiles approach, along with details on the
+integration of other components and some known issues.
+
 .. _endpoints:
 
 Endpoints
diff --git a/docs/faq.rst b/docs/faq.rst
index c6afa2f39200a7199787ccdb6d96903e1596ebb7..7a2fcc18961fce520923e2c94070f6038eadec1a 100644
--- a/docs/faq.rst
+++ b/docs/faq.rst
@@ -32,3 +32,28 @@ in this github `issue <https://github.com/GibbsConsulting/django-plotly-dash/iss
 
  Yes. See the :ref:`view_decoration` configuration setting and :ref:`access_control` section.
 
+* What settings are needed to run the server in debug mode?
+
+The ``prepare_demo`` script in the root of the git repository contains the full set of commands
+for running the server in debug mode. In particular, the debug server is launched with the ``--nostatic`` option. This
+will cause the staticfiles to be served from the collected files in the ``STATIC_ROOT`` location rather than the normal
+``runserver`` behaviour of serving directly from the various
+locations in the ``STATICFILES_DIRS`` list.
+
+* Is use of the ``get_asset_url`` function optional for including static assets?
+
+No, it is needed. Consider this example (it is part of ``demo-nine``):
+
+.. code-block:: python
+
+  localState = DjangoDash("LocalState",
+                          serve_locally=True)
+
+  localState.layout = html.Div([html.Img(src=localState.get_asset_url('image_one.png')),
+                                html.Img(src='/assets/image_two.png'),
+                                ])
+
+The first ``Img`` will have its source file correctly served up by Django as a standard static file. However, the second image will
+not be rendered as the path will be incorrect.
+
+See the :ref:`local_assets` section for more information on `configuration` with local assets.
diff --git a/docs/index.rst b/docs/index.rst
index 5776165ba4e322b7cb1307d723ed299ddc654d02..cb77a7de572a242001548a3b314e07dfd81ffe58 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -23,6 +23,7 @@ Contents
    template_tags
    dash_components
    configuration
+   local_assets
    demo_notes
    access_control
    faq
diff --git a/docs/installation.rst b/docs/installation.rst
index cf4d554aefcec8351a9369ec6c10b4041ef627bd..2a75a19cabb7e9ac042d1ac147a391e8ddef0652 100644
--- a/docs/installation.rst
+++ b/docs/installation.rst
@@ -17,12 +17,17 @@ Then, add ``django_plotly_dash`` to ``INSTALLED_APPS`` in the Django ``settings.
         ...
         ]
 
-Further, if the :ref:`header and footer <plotly_header_footer>` tags are in use
-then ``django_plotly_dash.middleware.BaseMiddleware`` should be added to ``MIDDLEWARE`` in the same file. This can be safely added now even if not used.
-
 The project directory name ``django_plotly_dash`` can also be used on its own if preferred, but this will stop the use of readable application names in
 the Django admin interface.
 
+Further, if the :ref:`header and footer <plotly_header_footer>` tags are in use
+then ``django_plotly_dash.middleware.BaseMiddleware`` should be added to ``MIDDLEWARE`` in the same file. This
+can be safely added now even if not used.
+
+If assets are being served locally through the use of the global ``serve_locally`` or on a per-app basis, then
+``django_plotly_dash.middleware.ExternalRedirectionMiddleware`` should be added, along with the ``whitenoise`` package whose
+middleware should also be added as per the instructions for that package.
+
 The application's routes need to be registered within the routing structure by an appropriate ``include`` statement in
 a ``urls.py`` file::
 
diff --git a/docs/local_assets.rst b/docs/local_assets.rst
new file mode 100644
index 0000000000000000000000000000000000000000..137f0c271d182b897cc9cb990e8c32460e358664
--- /dev/null
+++ b/docs/local_assets.rst
@@ -0,0 +1,65 @@
+.. _local_assets:
+
+Local assets
+============
+
+Local ploty dash assets are integrated into the standard Django staticfiles structure. This requires additional
+settings for both staticfiles finders and middleware, and also providing a list of the components used. The
+specific steps are listed in the :ref:`configuration` section.
+
+Individual applications can set a ``serve_locally`` flag but the use of the global setting in the ``PLOTLY_DASH``
+variable is recommended.
+
+Additional components
+---------------------
+
+Some components, such as ``dash-bootstrap-components``, require external packages such as Bootstrap to be supplied. In
+turn this can be achieved using for example the ``bootstrap4`` Django application. As a consequence, dependencies on
+external URLs are introduced.
+
+This can be avoided by use of the ``dpd-static-support`` package, which supplies mappings to locally served versions of
+these assets. Installation is through the standard ``pip`` approach
+
+.. code-block:: bash
+
+   pip install dpd-static-support
+
+and then the package should be added as both an installed app and to the ``PLOTLY_COMPONENTS`` list
+in ``settings.py``, along with the associated middleware
+
+.. code-block:: python
+
+    INSTALLED_APPS = [
+        ...
+        'dpd_static_support',
+    ]
+
+    MIDDLEWARE = [
+        ...
+        'django_plotly_dash.middleware.ExternalRedirectionMiddleware',
+    ]
+
+    PLOTLY_COMPONENTS = [
+        ...
+        'dpd_static_support'
+    ]
+
+Note that the middleware can be safely added even if the ``serve_locally`` functionality is not in use.
+
+Known issues
+------------
+
+Absolute paths to assets will not work correctly. For example:
+
+.. code-block:: python
+
+    app.layout = html.Div([html.Img(src=localState.get_asset_url('image_one.png')),
+                           html.Img(src='assets/image_two.png'),
+                           html.Img(src='/assets/image_three.png'),
+                           ])
+
+Of these three images, both ``image_one.png`` and ``image_two.png`` will be served up - through the static files
+infrastructure - from the ``assets`` subdirectory relative to the code defining the ``app`` object. However, when
+rendered the application will attempt to load ``image_three.png`` using an absolute path. This is unlikely to
+be the desired result, but does permit the use of absolute URLs within the server.
+
diff --git a/prepare_demo b/prepare_demo
index 5023168a5ad0d2c8ccd7d370a64de159520bfdb8..0eec48ba543396894443dc18f22c44dc79947934 100755
--- a/prepare_demo
+++ b/prepare_demo
@@ -5,4 +5,7 @@ cd demo
 ./manage.py migrate
 ./manage.py shell < configdb.py # Add a superuser if needed
 ./manage.py collectstatic -i "*.py" -i "*.pyc" --noinput --link
-./manage.py runserver
+#
+# Run debug server. Use the nostatic flag to enable the use of whitenose rather than the standard Django debug handling
+#
+./manage.py runserver --nostatic
diff --git a/requirements.txt b/requirements.txt
index 3783c074ff48ec8751ffaeab26fe59e700ee8c6d..baf08dac96cada819db277fe55c5d6dd13940cc8 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,11 +1,11 @@
-dash
-dash-core-components
-dash-html-components
-dash-renderer
+dash==0.38
+dash-core-components==0.43.1
+dash-html-components==0.13.5
+dash-renderer==0.19
 plotly
 dpd-components
 
 dash-bootstrap-components
 
-Django>=2
+Django>=2,<2.2
 Flask>=1.0.2