Erstellen einer Docusaurus-ähnlichen Website mit FastAPI: Schritt 6 – Generierung der Seitenleiste
Daniel Hayes
Full-Stack Engineer · Leapcell

Im vorherigen Artikel haben wir das Problem des Ladens statischer Ressourcen (wie Bilder) in Markdown gelöst.
Bis zu diesem Punkt können unsere Dokumentationsseiten Inhalte, Codehervorhebungen und Bilder gut anzeigen. Die Leser haben jedoch immer noch Schwierigkeiten, die Dokumentation zu navigieren. Die Seiten sind isolierte Inseln: Ohne manuelle Eingabe der URL können Sie nicht von einer Seite zur anderen springen.
Dokumentationsseiten wie Docusaurus verwenden typischerweise ein „Linke Seitenleiste + rechter Inhalt“-Layout.
In diesem Artikel werden wir diese Funktion implementieren: Wir werden eine Funktion schreiben, um automatisch alle Markdown-Dateien im Verzeichnis docs/ zu scannen, ihre Titel zu extrahieren und dynamisch ein Navigationsmenü für die Seitenleiste zu generieren.
Schritt 1: Erstellen von Layout-Stilen für die Seitenleiste
Zuerst müssen wir das Seitenlayout von der ursprünglichen „Ein-Spalten-Vertikalstruktur“ in eine „Zwei-Spalten-Horizontalstruktur“ ändern.
Wir benötigen hierfür eine neue CSS-Datei. Erstellen Sie eine Datei layout.css im Verzeichnis static/css/.
Aktualisierte Dateistruktur:
static/
└── css/
├── highlight.css
└── layout.css <-- Neu
Bearbeiten Sie static/css/layout.css:
/* Globale Zurücksetzung und grundlegende Stile */ body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; color: #333; } /* Hauptcontainer: Verwendet Flexbox zur Implementierung von nebeneinanderliegenden Layouts */ .main-container { display: flex; min-height: 100vh; } /* Linke Seitenleisten-Stile */ .sidebar { width: 250px; background-color: #f4f4f4; border-right: 1px solid #ddd; padding: 20px; flex-shrink: 0; /* Verhindert, dass die Seitenleiste komprimiert wird */ } .sidebar h3 { margin-top: 0; font-size: 1.1rem; color: #555; } .sidebar ul { list-style: none; padding: 0; } .sidebar li { margin-bottom: 10px; } .sidebar a { text-decoration: none; color: #333; font-size: 0.95rem; } .sidebar a:hover { color: #007bff; } /* Rechte Inhaltsbereich-Stile */ .content { flex-grow: 1; padding: 20px 40px; max-width: 800px; /* Begrenzt die maximale Breite des Inhalts für eine bessere Lesbarkeit */ }
Schritt 2: Ändern der HTML-Vorlage
Ändern Sie als Nächstes templates/doc.html, um die neue CSS-Datei einzuschließen und die HTML-Struktur anzupassen, um die Seitenleiste zu integrieren.
Wir führen eine neue Variable, sidebar_items, in der Vorlage ein. Dies wird ein Array sein, das die Liste der Dokumente enthält, die später aus dem Python-Code übergeben wird.
Bearbeiten Sie templates/doc.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>{{ page_title }} - My Docs Site</title> <link rel="stylesheet" href="{{ url_for('static', path='css/highlight.css') }}" /> <link rel="stylesheet" href="{{ url_for('static', path='css/layout.css') }}" /> </head> <body> <div class="main-container"> <aside class="sidebar"> <h3>Contents</h3> <ul> {% for item in sidebar_items %} <li> <a href="{{ item.url }}">{{ item.title }}</a> </li> {% endfor %} </ul> </aside> <main class="content"> <h1>{{ page_title }}</h1> <hr /> <div class="doc-content">{{ content | safe }}</div> </main> </div> </body> </html>
Schritt 3: Implementieren der Verzeichnis-Scan-Logik
Nun müssen wir Python-Code schreiben, um den Ordner docs/ zu durchsuchen, alle .md-Dateien zu finden und ihr Frontmatter zu parsen, um die Titel zu erhalten.
Öffnen Sie main.py. Wir müssen die pathlib-Bibliothek importieren (Sie können auch os verwenden, aber pathlib ist moderner) und eine Hilfsfunktion schreiben.
Bearbeiten Sie main.py:
# main.py from fastapi import FastAPI, Request from fastapi.templating import Jinja2Templates from fastapi.responses import HTMLResponse import markdown from fastapi.staticfiles import StaticFiles import frontmatter from pathlib import Path # 1. Import Path app = FastAPI() app.mount("/static", StaticFiles(directory="static"), name="static") app.mount("/docs/assets", StaticFiles(directory="docs/assets"), name="doc_assets") templates = Jinja2Templates(directory="templates") # --- Hilfsfunktion: Generiert Daten für die Seitenleiste --- def get_sidebar_items(): items = [] docs_path = Path("docs") # Durchläuft alle .md-Dateien im Verzeichnis docs for file_path in docs_path.glob("*.md"): # Parst Frontmatter, um den Titel zu erhalten try: post = frontmatter.load(file_path) # Wenn kein Titel vorhanden ist, wird der Dateiname verwendet title = post.metadata.get("title", file_path.stem) except: title = file_path.stem # Generiert URL, unter der Annahme der Regel /docs/{Dateiname} # file_path.stem ruft den Dateinamen ohne Erweiterung ab (z. B. "hello") url = f"/docs/{file_path.stem}" items.append({"title": title, "url": url}) # Sortiert nach Titel (oder Sie können ein „order“-Feld zum Sortieren hinzufügen, hier wird einfach nach Titel sortiert) items.sort(key=lambda x: x["title"]) return items @app.get("/", response_class=HTMLResponse) async def root(request: Request): # Die Homepage bleibt vorerst unverändert, wir können später eine Seitenleiste hinzufügen context = { "request": request, "page_title": "Hello, Jinja2!" } return templates.TemplateResponse("index.html", context) @app.get("/docs/hello", response_class=HTMLResponse) async def get_hello_doc(request: Request): md_file_path = "docs/hello.md" try: post = frontmatter.load(md_file_path) except FileNotFoundError: return HTMLResponse(content="<h1>404 - Dokument nicht gefunden</h1>", status_code=404) except Exception as e: return HTMLResponse(content=f"<h1>500 - Parse-Fehler: {e}</h1>", status_code=500) metadata = post.metadata md_content = post.content extensions = ['fenced_code', 'codehilite'] html_content = markdown.markdown(md_content, extensions=extensions) page_title = metadata.get('title', 'Untitled Document') # 2. Ruft Daten für die Seitenleiste ab sidebar_items = get_sidebar_items() context = { "request": request, "page_title": page_title, "content": html_content, "sidebar_items": sidebar_items # 3. Übergibt an die Vorlage } return templates.TemplateResponse("doc.html", context)
Schritt 4: Hinzufügen eines zweiten Dokuments zum Testen
Um zu überprüfen, ob die Seitenleiste tatsächlich funktioniert, benötigen wir eine zweite Markdown-Datei.
Erstellen Sie setup.md im Verzeichnis docs/:
--- title: Umgebungseinrichtungsanleitung autor: Leapcell datum: 2025-11-10 --- # Einrichtung der Projektumgebung Dies ist unser zweites Dokument. 1. Python installieren 2. FastAPI installieren 3. Code ausführen
Schritt 5: Ausführen und Testen
Führen Sie uvicorn main:app --reload aus, um den Server zu starten.
Besuchen Sie http://127.0.0.1:8000/docs/hello.
Sie werden die folgenden Änderungen bemerken:
- Das Seitenlayout wurde zu einer Navigationsleiste links und einem Inhaltsbereich rechts geändert.
- Die linke Seitenleiste listet automatisch zwei Links auf: „Hello, Frontmatter!“ und „Umgebungseinrichtungsanleitung“.

Es gibt jedoch ein kleines Problem:
Wenn Sie in der Seitenleiste auf „Umgebungseinrichtungsanleitung“ klicken, springt der Browser zu /docs/setup. Zu diesem Zeitpunkt sehen Sie eine Fehlermeldung, dass der Inhalt nicht gefunden wird.
Dies liegt daran, dass unsere main.py derzeit nur die hartcodierte Route /docs/hello enthält und /docs/setup noch nicht behandelt.
Zusammenfassung
Durch das Scannen des Dateisystems haben wir unserer Dokumentationsseite die Fähigkeit gegeben, automatisch ein Inhaltsverzeichnis zu generieren. Egal, wie viele .md-Dateien Sie zum docs/-Ordner hinzufügen oder daraus entfernen, die Seitenleiste wird automatisch aktualisiert.
Obwohl die Seitenleiste intelligent ist, ist das Routing „ungeschickt“ – da die Adressen der Routen hartcodiert sind. Wenn wir 100 Dokumente hätten, könnten wir sicherlich keine 100 @app.get("/docs/xxx")-Funktionen in main.py schreiben? Das ist offensichtlich nicht praktikabel.
Wir benötigen eine generische Methode, um Anfragen für /docs/{beliebiger_Dateiname} abzufangen und automatisch die entsprechende Markdown-Datei zu finden.
Im nächsten Artikel werden wir dynamisches Routing basierend auf Dateipfaden implementieren, das das 404-Problem vollständig löst und sicherstellt, dass jeder Link in der Seitenleiste tatsächlich zum entsprechenden Artikel navigiert.
Sonstiges
Nachdem Sie Ihre Website erstellt haben, möchten Sie sie vielleicht online stellen, damit andere sie sehen können. Aber die meisten Cloud-Plattformen sind teuer und es lohnt sich nicht, für ein Übungsprojekt wie dieses einen hohen Preis zu zahlen.
Gibt es eine wirtschaftlichere Möglichkeit, bereitzustellen? Sie können Leapcell ausprobieren. Es unterstützt die Bereitstellung mehrerer Sprachen wie Python, Node.js, Go und Rust und bietet jeden Monat ein großzügiges kostenloses Kontingent, das es Ihnen ermöglicht, bis zu 20 Projekte bereitzustellen, ohne einen Cent auszugeben.

