Chapter 1: Django – Add Static File
Add Static File: How to properly add and use static files (CSS, JavaScript, images, fonts, favicons, etc.) in Django.
Many beginners either:
- keep writing everything inline inside <style> or <script> tags → code becomes unmaintainable mess
- put static files in wrong folders → get 404 errors forever
- don’t understand the difference between development (runserver) and production (collectstatic) → panic during deployment
Today we are going to fix all of that — step by step — like I’m sitting next to you, creating folders, writing files, running commands, and testing together on your laptop.
We will make your polls app look much more professional with global CSS, app-specific styles, a logo image, and a tiny JavaScript alert — all properly organized.
Goal for Today
By the end you will have:
- Clean static/ folder structure (global + app-specific)
- Global CSS linked from base.html
- App-specific CSS for polls pages
- A logo image in the header
- A small JavaScript file that runs on page load
- Everything working in development
- Understanding of how to prepare for production (collectstatic)
Step 1: Understand the Two Worlds of Static Files
| Mode | Command | Where Django looks for files | How files are served | When used |
|---|---|---|---|---|
| Development | python manage.py runserver | STATICFILES_DIRS + each app’s static/ subfolder | Django’s dev server serves them automatically | Everyday development |
| Production | Real web server (nginx, gunicorn + Whitenoise, etc.) | One single folder created by collectstatic | Web server serves them directly (very fast) | When deploying (Railway, Render, Heroku, VPS…) |
Golden rule: Develop with the same folder structure you will use in production — never rely on dev-server magic.
Step 2: Create the Standard Static Folder Structure
In your project root (next to manage.py):
|
0 1 2 3 4 5 6 7 8 9 |
mkdir -p static/css mkdir -p static/js mkdir -p static/images mkdir -p static/fonts # optional, for custom fonts later |
Now create app-specific static folder (recommended best practice):
|
0 1 2 3 4 5 6 7 8 |
mkdir -p polls/static/polls/css mkdir -p polls/static/polls/js mkdir -p polls/static/polls/images |
Why the extra polls/ folder inside static/?
→ Namespacing — prevents name conflicts If later you add a blog app that also has style.css, Django knows which one to use.
Final structure should look like:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
mysite-project/ ├── manage.py ├── static/ ← project-level (global files) │ ├── css/ │ │ └── global.css │ ├── js/ │ │ └── alert.js │ └── images/ │ └── logo.png ├── polls/ │ └── static/ │ └── polls/ ← app namespace │ ├── css/ │ │ └── polls.css │ ├── js/ │ └── images/ │ └── poll-icon.png └── ... |
Step 3: Configure settings.py (Most Important Step)
Open mysite/settings.py
Add or update these lines:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import os from pathlib import Path BASE_DIR = Path(__file__).resolve().parent.parent # ── Static files configuration ─────────────────────────────────────────────── STATIC_URL = '/static/' # URL prefix in browser # Where Django looks for static files during development STATICFILES_DIRS = [ BASE_DIR / "static", # global static folder # You can add more if needed ] # Where collectstatic will copy everything in production STATIC_ROOT = BASE_DIR / "staticfiles" # created automatically later |
Important: STATICFILES_DIRS tells Django where to find extra static files beyond app folders. STATIC_ROOT is only used in production — never put files there manually.
Step 4: Create Some Real Static Files
1. Global CSS – static/css/global.css
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
/* Global styles — applied to every page via base.html */ :root { --primary: #006400; /* dark green – Hyderabad / Telangana feel */ --primary-dark: #004d00; --light-bg: #f8f9fa; --text: #2d2d2d; --gray: #6c757d; } * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: system-ui, -apple-system, sans-serif; background: var(--light-bg); color: var(--text); line-height: 1.6; } .container { max-width: 1100px; margin: 0 auto; padding: 0 1.5rem; } .btn { display: inline-block; padding: 0.8rem 1.6rem; background: var(--primary); color: white; border-radius: 6px; text-decoration: none; font-weight: 500; transition: background 0.2s; } .btn:hover { background: var(--primary-dark); } .card { background: white; padding: 1.8rem; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.08); margin-bottom: 1.5rem; } |
2. App-specific CSS – polls/static/polls/css/polls.css
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
/* Polls-specific styles */ .poll-card { border-left: 5px solid var(--primary); } .vote-badge { background: #28a745; color: white; padding: 4px 10px; border-radius: 20px; font-size: 0.85rem; font-weight: 500; } |
3. A small JavaScript file – static/js/alert.js
|
0 1 2 3 4 5 6 7 8 9 10 |
// Just for testing – remove later document.addEventListener("DOMContentLoaded", function() { console.log("Static JavaScript loaded successfully!"); // alert("Hey Webliance! Your static JS is working! 🚀"); }); |
4. An image (logo) – static/images/logo.png
(You can download any small logo PNG or create a placeholder text image)
Step 5: Load & Use Static Files in Templates
In your master template templates/base.html (add at top):
|
0 1 2 3 4 5 6 |
{% load static %} <!-- ← THIS LINE IS MANDATORY for {% static %} tag --> |
Now link files:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<head> <!-- Global CSS --> <link rel="stylesheet" href="{% static 'css/global.css' %}"> <!-- App-specific CSS (optional – only if current app is polls) --> {% if request.resolver_match.app_name == 'polls' %} <link rel="stylesheet" href="{% static 'polls/css/polls.css' %}"> {% endif %} <!-- Page-specific CSS --> {% block extra_css %}{% endblock %} </head> <body> <header class="container"> <img src="{% static 'images/logo.png' %}" alt="Polls Logo" height="50" style="vertical-align: middle;"> <h1>Hyderabad Polls 2026</h1> </header> {% block content %}{% endblock %} <!-- Global JS --> <script src="{% static 'js/alert.js' %}"></script> <!-- Page-specific JS --> {% block extra_js %}{% endblock %} </body> |
Step 6: Test in Development
- Save all files
- Restart server if needed (python manage.py runserver)
- Open any page (e.g. /polls/ or /)
- Check browser dev tools (F12 → Network tab):
- See global.css, polls.css, logo.png, alert.js load with 200 OK
- See console message “Static JavaScript loaded successfully!”
- See styles applied (font, colors, card shadows, logo)
Common 404 errors & fixes
- Forgot {% load static %} → 404 on {% static … %}
- Wrong path → check folder structure exactly (polls/static/polls/css/…)
- File not saved or server not restarted → Ctrl+F5 hard refresh
Step 7: Production Preparation (collectstatic)
When you deploy (Railway, Render, Heroku, VPS…):
|
0 1 2 3 4 5 6 |
python manage.py collectstatic |
→ Django copies all static files from:
- STATICFILES_DIRS (project static/)
- every app’s static/ folder
into STATIC_ROOT (staticfiles/ folder)
Then configure your web server to serve /static/ from staticfiles/.
Your Quick Practice Task (Do This Right Now)
- Create folders and files exactly as shown
- Add {% load static %} to base.html
- Link global CSS + logo image + JS file
- Create polls/static/polls/css/polls.css with some style (e.g. .poll-card { border: 2px solid green; })
- In polls/templates/polls/index.html → add class poll-card to one div → see style apply
- Check browser console → see JS message
Tell me what feels next:
- “Done! Now show me how to use Tailwind or Bootstrap via CDN / local files”
- “How to handle user-uploaded images (media files) later?”
- “What is Whitenoise? How to use it for production static files?”
- “Got 404 on static file – here’s the URL and error”
- Or finally ready for Django Forms + Voting + POST + F() increment?
You now have a clean, professional, scalable static files setup — your site is starting to look like a real 2026 web app.
You’re doing really well — let’s keep the momentum! 🚀🇮🇳
