[Fixed]-Why isn't django serving my SPA static files correctly?

4👍

Cause of errors

For the first case

STATIC_URL = '/static/'

Django tries to look for static files in the backend/static/ folder only in the case where anything with url with /static/ i.e. for e.g. /static/css/* or /static/js/* is mentioned in your index.html, but that is not the case here index.html has file references like /css/* and /js/*, hence they are not found.

The reason this case works in the blog example is due to the same reason i.e. their template files are kept under a '../frontend/build directory and static files are in ‘../frontend/build/static’ hence the index.html will look for static/js/* instead of a /js/* hence /static/ url location is accessed in Django, which then looks for the files in backend/static correctly

This is why, in your code, setting it to second case i.e.

STATIC_URL = '/'

gets the urls for static files correctly to /css/* and /js/* and even your /index.html can be thought of as a static file i.e. all these urls are considered static and are searched in the backend/static/ folder and hence appearing correctly in the browser.

But now the URLs are messed up i.e. this:

re_path('', TemplateView.as_view(template_name='index.html')),

can be treated as Django looking for this location: /, but it is already reserved for searching static files and not including any file name after / means you are not looking for any file.

Possible Solution

Django allows custom url patterns i.e. you can create 2 new variables in settings.py i.e.

STATIC_URL = "/static/"
STATIC_ROOT = os.path.join(BASE_DIR, "static/")

STATIC_JS_URL = "/js/"
STATIC_JS_ROOT = os.path.join(STATIC_ROOT, "js/")

STATIC_CSS_URL = "/css/"
STATIC_CSS_ROOT = os.path.join(STATIC_ROOT, "css/")

and then configure your urls.py

from django.contrib import admin
from django.urls import include, path, re_path
from django.views.generic import TemplateView
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    path('admin/', admin.site.urls),
    path('c**kpit/', include("c**kpit.urls")),
    re_path('', TemplateView.as_view(template_name='index.html')),
]

urlpatterns += static(settings.STATIC_JS_URL, document_root=settings.STATIC_JS_ROOT)
urlpatterns += static(settings.STATIC_CSS_URL, document_root=settings.STATIC_CSS_ROOT)

1👍

Use of STATIC_URL

First of all, STATIC_URL will be the url where the static files will be served. So if you set STATIC_URL='/' and then navigate to http://127.0.0.1:8000/index.html it will essentially serve the index.html from within the static folder of your Django application. If you go to http://127.0.0.1:8000/ you’re at your ‘static folder root’, and as you can see, a directory index is forbidden. So don’t use STATIC_URL='/'

Location of the files

The tutorial you followed adds the build folder of the SPA to the templates in your Django settings.py file.

So you should try and add:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(os.path.dirname(BASE_DIR), 'frontend', 'dist')],  # <== ADDED THIS
        'APP_DIRS': True,
        'OPTIONS': {
            # removed to keep this example small
        },
]

or if you want to point to your static files as template directory:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'static')], 
        'APP_DIRS': True,
        'OPTIONS': {
            # removed to keep this example small
        },
]

However I don’t think it is best practice since static files (images, JS, CSS) have their own qualification in Django.

Note on os.path

Also, for your STATICFILES_DIRS, that’s not how os.path.join() works. The first argument should be a path you want to join with one or more path components, the components are then passed as the next arguments. Furthermore, BASE_DIRis not at the right directorylevel, as you can see when you printed out the BASE_DIR path. You need the parent of the BASE_DIRdirectory. To get the path of the parent directory, you can do os.path.dirname(BASE_DIR), because BASE_DIR is a path. So applying this we get:

STATICFILES_DIRS = [
    os.path.join(os.path.dirname(BASE_DIR), frontend, dist),
]

Final note

I do feel that it might be an anti pattern to have your SPA served by Django without it being completely self contained in the Django BASE_DIR (hence the name, base directory). But I don’t know the source for it, or exactly how best practice this.

1👍

I had this problem a long time ago and I solved it with a simple solution.
It’s too simple.
As I see, your static folder is not in your app root and it’s in wrong place.
Put it in your app root…because Django is looking for the static folder inside the main root of your app, where views.py is in there.
Maybe you have 5 apps or more.
Django doesn’t care about the number of apps you have, Django only looks for your static folder in your app root.
But your templates folder can be in your project root.
So , put your static folder in your app root.
In this case I mean inside c**kpit root.

And then you have to add this changes in settings.py

PROJECT_NAME = '---Your projects name---'
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, PROJECT_NAME, 'static/')

EXTERNAL_APPS = [

     'django.contrib.staticfiles',
  ]

Good Luck.

0👍

First things first! If you want to serve your UI from Django as static files for the production, it’s a bad idea! You need to consider using nginx or similar web server to serve static files for you.

The TemplateView in your urls.py is a bit problematic. Since your static files are served under STATIC_URL = '/static/' in your first scenario, the urls for js and css files does not match. You might edit the files and put static template tags inside index.hml but it’s not a good idea as well. When you do changes on vuejs side, you need to build and replace the package directly and your site is updated. So the this line needs to be deleted forever, index.html has to be treated as static file:

    re_path('', TemplateView.as_view(template_name='index.html')),

When you changed STATIC_URL = '/' you may access all of your static files. If you run django in debug = True it serves static files automatically. Otherwise you need to add static url conf to the file. (DO NOT FORGET, This is not good for production!)

from django.contrib import admin
from django.urls import include, path, re_path
from django.views.generic import TemplateView
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    path('admin/', admin.site.urls),
    path('c**kpit/', include("c**kpit.urls")),
]

urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

With this setup you need to be careful about your urls and static file namings. They might override each other.

P.S: Thank you for such a detailed question.

0👍

There is a dumb way to fix this issue.

This issue is about that html built by VueJS will get js and css from path like these /js/app.12312sd.js and /css/app.dsfeewj.css.

html built by vueJS

Default setting of static file root path is /static/, so you only need to change each /js/... and /css/... in index.html manually or use sed or something else to replace them to /static/js/.../ and /static/css/.../.

You cannot replace STATIC_URL to / because it the root url for your website.

After you edit it, it will look like this, and then server your django server it will work well.

enter image description here

0👍

Before building your frontend put the css/ and js/ in a directory named ‘static’ leaving the index.html as it is. Then when you run yarn build your directory structure would be as follows –

c**kpit
├── backend/
│   ├── c**kpit/
│   │   ├── views.py
│   │   ├── css/
│   │   └── etc..
│   ├── settings/
│   │   └── settings.py
│   └── manage.py
└── frontend/
    └── dist/
        ├── static/
        │       ├── css/
        │       └── js/
        └── index.html

Setup your BASE_DIR and a FRONTEND_DIR to point towards ‘c**kpit/frontend/dist’ for quick use later on.

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
FRONTEND_DIR = os.path.join(os.path.dirname(BASE_DIR),'frontend','dist')

Now either copy frontend static files to your backend directory as you did earlier –

STATIC_URL = '/static/'
STATICFILES_DIRS = [
    os.path.join(FRONTEND_DIR,'static/'),
]
STATIC_ROOT = os.path.join(BASE_DIR, "static/")

OR you could directly point your backend to serve from the built frontend directory instead of copying it again. (I somehow always do this)

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(FRONTEND_DIR,'static/')

Include your built frontend directory in your template directories for django to be able to serve index.html

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [FRONTEND_DIR],
        'APP_DIRS': True,
        'OPTIONS': {
            # removed to keep this example small
        },
    },
]

Now run your server

$ python manage.py collectstatic
$ python manage.py runserver

If later you decide on deploying this online you could do that easily using this configuration itself by setting Nginx to serve static files from your static directory.
Hope this helps 🙂

Clearing your confusion in your [EDIT]

STATIC_ROOT points to the directory on your system. STATIC_URL refers to the URL at which your static files that are present in your STATIC_ROOT would be accessed while using your website.

Let’s say you have a file script.js in your directory as shown

c**kpit
├── backend/
│   ├── c**kpit/
│   │   ├── views.py
│   │   ├── css/
│   │   └── etc..
│   ├── settings/
│   │   └── settings.py
│   └── manage.py
└── frontend/
    └── dist/
        ├── static/
        │       ├── css/
        │       └── js/
        │          └── script.js
        └── index.html

and you have set your STATIC_ROOT to point towards ‘c**kpit/frontend/dist/static/’.

Now if you set your STATIC_URL as ‘static/’ you would be able to access that script at http://127.0.0.1:8000/static/js/script.js

Alternatively, if you set your STATIC_URL to ‘xcsdf/’ you would access your js file at http://127.0.0.1:8000/xcsdf/js/script.js

Leave a comment