pythonhtmlcsshttp

Static files not recognized in base template


I can't figure out why the CSS sheet called in my base template's header isn't hooked up when I check my index.html file on http.server. I'm migrating my site to Python and Jinja, no frameworks.

My server module/script:

# server.py
import os
import http.server
import socketserver
from jinja2 import Environment, FileSystemLoader

PORT = 8000
STATIC_DIR = "static/"

class StaticFileHandler(http.server.SimpleHTTPRequestHandler):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, directory=STATIC_DIR, **kwargs)

with socketserver.TCPServer(("", PORT), StaticFileHandler) as httpd:
    print(f"Serving at http://localhost:{PORT}/")
    httpd.serve_forever()

The base template's header (omitting the rest of the template):

<!DOCTYPE html>
<html lang="en-us">
    <head>
        <meta charset="utf-8">
            <title>{% block title %}{% endblock title %}</title>
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <meta name="robots" content="noai, noimageai, noarchive">
            <link rel="icon" type="image/x-icon" href="favicon.ico">
            <link rel="stylesheet" href="styles.css">
    </head>

I've tried writing the paths to the static files as "static/styles.css", but no luck there. What am I missing? In server.py, what do I enter for STATIC_DIR if I want the project's home folder to be the static directory? Leaving it blank or adding / show my computer's root directory, not the project's.

How would I set up server.py so it pulls static files like CSS, fonts, etc. from STATIC_DIR, but looks for index.html from a different directory like the project folder, or a "site" folder?

My test script for generating index.html saves the HTML file to the project folder (I drag it to "static" for testing):

# write-posts.py
from jinja2 import Environment, FileSystemLoader
from datetime import datetime, timezone

# date = datetime(tzinfo=timezone.utc).isoformat()
posts = [
    {"title": "First Post", "slug": "first-post", "date": "2025, 7, 1", "body": "First post!", "tags": "First Post"},
]

environment = Environment(loader=FileSystemLoader("templates"))
template = environment.get_template("index.html")
filename = "index.html"

content = template.render(posts=posts)

with open(filename, mode="w", encoding="utf-8") as message:
        message.write(content)
        print(f"... wrote {filename}")

I tried Furas's suggestions, but I'm still not getting the static files to connect:

server.py:

import os
import http.server
import socketserver
from jinja2 import Environment, FileSystemLoader

PORT = 8000
BASE_DIR = "source/" # Directory containing static files. "BASE_DIR="/full/path/to/www/" might be better if server runs from a different working directory.

class StaticFileHandler(http.server.SimpleHTTPRequestHandler):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, directory=BASE_DIR, **kwargs)

with socketserver.TCPServer(("", PORT), StaticFileHandler) as httpd:
    print(f"Serving at http://localhost:{PORT}/")
    httpd.serve_forever()

base.html template (head tag only):

<!DOCTYPE html>
<html lang="en-us">
    <head>
        <meta charset="utf-8">
            <title>{% block title %}{% endblock title %}</title>
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <meta name="robots" content="noai, noimageai, noarchive">
            <link rel="icon" type="image/x-icon" href="static/favicon.ico">
            <link rel="stylesheet" href="static/styles.css">
    </head>

write-posts.py (generating index.html, I also have a template for my homepage titled "index.html"):

from jinja2 import Environment, FileSystemLoader
from datetime import datetime, timezone

# date = datetime(tzinfo=timezone.utc).isoformat()
posts = [
    {"title": "First Post", "slug": "first-post", "date": "2025, 7, 1", "body": "First post!", "tags": "First Post"},
]

environment = Environment(loader=FileSystemLoader("source/templates"))
template = environment.get_template("index.html")
filename = "index.html"

In my CSS file paths to static files are written like:

src: url("static/fonts/font-example.woff2") format("woff2");

Messages in the terminal when I load the page in my browser:

127.0.0.1 - - [18/Jul/2025 10:22:56] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [18/Jul/2025 10:23:05] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [18/Jul/2025 10:23:05] code 404, message File not found
127.0.0.1 - - [18/Jul/2025 10:23:05] "GET /favicon.ico HTTP/1.1" 404 -
127.0.0.1 - - [18/Jul/2025 10:29:44] "GET / HTTP/1.1" 304 -

In my browser's console (Firefox) I only see a 404 message for the favicon file.

My project structure:

project folder
    -- server.py (`http.server` module/script)
    -- write-posts.py (test script for generating index.html from Jinja templates and static files)
    -- venv folder (virtual environment folder)
    -- source folder (for HTML, static, and template files, an "input" folder)
        -- index.html (what write-posts.py generates, I manually move it from project to source and overwrite the previous version)
        -- static folder
            -- static files like styles.css, favicon.ico, logo.png, paper.webp (for wallpaper)
            -- fonts folder
        -- templates folder
            -- base.html (base template)
            -- homepage.html (child template of base, template for the homepage. previously called "index.html" but renamed to avoid confusion)

server.py:

import os
import http.server
import socketserver
from jinja2 import Environment, FileSystemLoader

PORT = 8000
BASE_DIR = "source/"

class StaticFileHandler(http.server.SimpleHTTPRequestHandler):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, directory=BASE_DIR, **kwargs)

with socketserver.TCPServer(("", PORT), StaticFileHandler) as httpd:
    print(f"Serving at http://localhost:{PORT}/")
    httpd.serve_forever()

write-posts.py:

from jinja2 import Environment, FileSystemLoader
from datetime import datetime, timezone

# date = datetime(tzinfo=timezone.utc).isoformat()
posts = [
    {"title": "First Post", "slug": "first-post", "date": "2025, 7, 1", "body": "First post!", "tags": "First Post"},
]

environment = Environment(loader=FileSystemLoader("source/templates"))
template = environment.get_template("homepage.html")
filename = "index.html"

content = template.render(posts=posts)

with open(filename, mode="w", encoding="utf-8") as message:
        message.write(content)
        print(f"... wrote {filename}")

base.html:

<!DOCTYPE html>
<html lang="en-us">
    <head>
        <meta charset="utf-8">
            <title>{% block title %}{% endblock title %}</title>
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <meta name="robots" content="noai, noimageai, noarchive">
            <link rel="icon" type="image/x-icon" href="favicon.ico">
            <link rel="stylesheet" href="styles.css">
            <link rel="alternate" type="application/feed+json" href="/rss.json"/>
    </head>
    <body>
        <header>
            <a href="{% url 'home' %}" class="/static/logo"><img src="logo.png" alt="TBD"></a>

In write-posts.py it saves index.html to the "project" folder. Since server.py looks at the source folder for files, I manually drag index.html from project to source. I have tried these file paths in base.html and styles.css yet none work:

project/source/static/<file-name>
/project/source/static/<file-name>
/source/static/<file-name>
source/static/<file-name>
/static/<file-name>
static/<file-name>
/<file-name>
<file-name>

Solution

  • I think name of variable STATIC_DIR is missleading and it can confuse with folder name static.

    As for me it should have name BASE_DIR and you could have folder www/ or html/ like in real WWW servers.

    So your structure could be

    project/
      +-> server.py
      +-> www/
           +-> index.html
           +-> static/
                +-> style.css
                +-> script.js
           +-> posts/
                +-> 1.html
                +-> 2.html         
    

    And code could use BASE_DIR = "www/" or even better BASE_DIR="/full/path/to/www/" because you may run server from different current working directory.

    (frankly, even name StaticFileHandler can be missleading because it is handler for all files on server, not only in folder static. To server only static files it would need something more complex, and it would need something else to serve other files - like .html)

    server.py

    import os
    import http.server
    import socketserver
    from jinja2 import Environment, FileSystemLoader
    
    PORT = 8000
    BASE_DIR = "www/"
    
    class StaticFileHandler(http.server.SimpleHTTPRequestHandler):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, directory=BASE_DIR, **kwargs)
    
    with socketserver.TCPServer(("", PORT), StaticFileHandler) as httpd:
        print(f"Serving at http://localhost:{PORT}/")
        httpd.serve_forever()
    

    And html could use static/style.css or even better with leading / like /static/style.css if you would like to use it also in html in subfolders like posts/1.html

    index.html

    <!DOCTYPE html>
    <html>
    <head>
       <link rel="stylesheet" type="text/css" href="/static/style.css">
    </head>
    <body>
       <a href="/posts/1.html">Post 1</a><br/>
       <a href="/posts/2.html">Post 2</a><br/>
    </body>
    </html>