# Web Application Architecture Refactor: Jinja2 Template System

## Executive Summary

This document outlines the architectural refactor from a **client-side HTML approach** to a **server-side Jinja2 template system** for our multi-tenant e-commerce platform. This change addresses code duplication, improves maintainability, and leverages FastAPI's native templating capabilities while maintaining our Alpine.js reactive frontend.

## Table of Contents

1. [Current Architecture Analysis](#current-architecture-analysis)
2. [Proposed Architecture](#proposed-architecture)
3. [Key Design Decisions](#key-design-decisions)
4. [Migration Strategy](#migration-strategy)
5. [File Structure Comparison](#file-structure-comparison)
6. [Benefits & Trade-offs](#benefits--trade-offs)
7. [Implementation Checklist](#implementation-checklist)
8. [Code Examples](#code-examples)
9. [Testing Strategy](#testing-strategy)
10. [Rollback Plan](#rollback-plan)

## Current Architecture Analysis

### Current Approach: Client-Side Static HTML

```
┌─────────────────────────────────────────────────┐
│          Browser (Client-Side)                   │
├─────────────────────────────────────────────────┤
│                                                  │
│  1. Load dashboard.html (static file)           │
│  2. Execute partial-loader.js                   │
│  3. Fetch header.html via AJAX                  │
│  4. Fetch sidebar.html via AJAX                 │
│  5. Initialize Alpine.js                        │
│  6. Fetch data from API endpoints               │
│                                                  │
└─────────────────────────────────────────────────┘
```

#### Current File Structure

```
project/
├── static/
│   ├── admin/
│   │   ├── dashboard.html          ← Full HTML page
│   │   ├── vendors.html            ← Full HTML page
│   │   ├── users.html              ← Full HTML page
│   │   ├── partials/
│   │   │   ├── header.html         ← Loaded via AJAX
│   │   │   └── sidebar.html        ← Loaded via AJAX
│   │   ├── css/
│   │   │   └── tailwind.output.css
│   │   └── js/
│   │       ├── init-alpine.js
│   │       └── dashboard.js
│   └── shared/
│       └── js/
│           ├── api-client.js
│           ├── icons.js
│           └── partial-loader.js   ← AJAX loader
└── app/
    └── api/
        └── v1/
            └── admin/
                └── routes.py       ← API endpoints only
```

#### Current HTML Example (dashboard.html)

```html
<!DOCTYPE html>
<html :class="{ 'theme-dark': dark }" x-data="adminDashboard()" lang="en">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Dashboard - Admin Panel</title>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
    <link rel="stylesheet" href="/static/admin/css/tailwind.output.css" />
    <style>[x-cloak] { display: none !important; }</style>
</head>
<body x-cloak>
    <div class="flex h-screen bg-gray-50 dark:bg-gray-900">
        <!-- Sidebar Container (loaded via AJAX) -->
        <div id="sidebar-container"></div>
        
        <!-- Header Container (loaded via AJAX) -->
        <div id="header-container"></div>
        
        <!-- Main Content -->
        <main>
            <!-- Page-specific content here -->
        </main>
    </div>
    
    <!-- Scripts -->
    <script src="/static/shared/js/partial-loader.js"></script>
    <script>
        (async () => {
            await window.partialLoader.loadAll({
                'header-container': 'header.html',
                'sidebar-container': 'sidebar.html'
            });
        })();
    </script>
    <script src="/static/admin/js/init-alpine.js"></script>
    <script src="/static/shared/js/api-client.js"></script>
    <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.0/dist/cdn.min.js"></script>
    <script src="/static/admin/js/dashboard.js"></script>
</body>
</html>
```

### Problems with Current Approach

| Problem | Impact | Severity |
|---------|--------|----------|
| **HTML Duplication** | Every page repeats `<head>`, script tags, and structure | High |
| **Multiple HTTP Requests** | 3+ requests just to render initial page (HTML + header + sidebar) | Medium |
| **Timing Issues** | Race conditions between partial loading and Alpine.js initialization | Medium |
| **No Server-Side Control** | Cannot inject user data, permissions, or dynamic content on page load | High |
| **Difficult to Maintain** | Changes to layout require updating every HTML file | High |
| **No Authentication Flow** | Must rely entirely on client-side routing and checks | High |
| **SEO Challenges** | Static files with no dynamic meta tags or content | Low |
| **URL Structure** | Ugly URLs: `/static/admin/dashboard.html` | Medium |

## Proposed Architecture

### New Approach: Server-Side Jinja2 Templates

```
┌─────────────────────────────────────────────────┐
│           FastAPI Server (Backend)               │
├─────────────────────────────────────────────────┤
│                                                  │
│  1. Receive request: /admin/dashboard           │
│  2. Check authentication (Depends)              │
│  3. Render Jinja2 template (base + dashboard)   │
│  4. Inject user data, permissions               │
│  5. Return complete HTML                        │
│                                                  │
└─────────────────────────────────────────────────┘
                      ↓
┌─────────────────────────────────────────────────┐
│          Browser (Client-Side)                   │
├─────────────────────────────────────────────────┤
│                                                  │
│  1. Receive complete HTML (single request)      │
│  2. Initialize Alpine.js                        │
│  3. Fetch data from API endpoints               │
│                                                  │
└─────────────────────────────────────────────────┘
```

#### Proposed File Structure

```
project/
├── app/
│   ├── templates/                  ← NEW! Jinja2 templates
│   │   ├── admin/
│   │   │   ├── base.html          ← Base layout (extends pattern)
│   │   │   ├── dashboard.html     ← Extends base.html
│   │   │   ├── vendors.html       ← Extends base.html
│   │   │   └── users.html         ← Extends base.html
│   │   └── partials/
│   │       ├── header.html        ← Included server-side
│   │       └── sidebar.html       ← Included server-side
│   └── api/
│       └── v1/
│           └── admin/
│               ├── routes.py      ← API endpoints
│               └── pages.py       ← NEW! Page routes (HTML)
└── static/
    ├── admin/
    │   ├── css/
    │   │   └── tailwind.output.css
    │   └── js/
    │       ├── init-alpine.js
    │       └── dashboard.js
    └── shared/
        └── js/
            ├── api-client.js
            ├── icons.js
            └── utils.js           ← NEW! (was missing)
```

## Key Design Decisions

### 1. Template Inheritance (Jinja2 Extends/Blocks)

**Decision:** Use Jinja2's `{% extends %}` and `{% block %}` pattern.

**Rationale:**
- **DRY Principle:** Define layout once in `base.html`, reuse everywhere
- **Maintainability:** Change header/footer in one place
- **Native to FastAPI:** No additional dependencies or build tools
- **Industry Standard:** Proven pattern used by Django, Flask, etc.

**Example:**

```jinja2
{# base.html - Parent Template #}
<!DOCTYPE html>
<html>
<head>
    <title>{% block title %}Admin Panel{% endblock %}</title>
    {# Common head elements #}
</head>
<body>
    {% include 'partials/sidebar.html' %}
    {% include 'partials/header.html' %}
    <main>
        {% block content %}{% endblock %}
    </main>
    {# Common scripts #}
    {% block extra_scripts %}{% endblock %}
</body>
</html>

{# dashboard.html - Child Template #}
{% extends "admin/base.html" %}

{% block title %}Dashboard{% endblock %}

{% block content %}
    <h1>Dashboard Content</h1>
{% endblock %}

{% block extra_scripts %}
    <script src="/static/admin/js/dashboard.js"></script>
{% endblock %}
```

### 2. Server-Side Rendering with Client-Side Reactivity

**Decision:** Render HTML on server, enhance with Alpine.js on client.

**Rationale:**
- **Best of Both Worlds:** Fast initial render + reactive UI
- **Progressive Enhancement:** Works without JavaScript (graceful degradation)
- **SEO Friendly:** Complete HTML on first load
- **Performance:** Single HTTP request for initial page load

**Architecture:**

```
Server (FastAPI + Jinja2)          Client (Alpine.js)
─────────────────────────          ──────────────────
Generate complete HTML    ──────►  Parse HTML
Include user data         ──────►  Initialize Alpine
Include permissions       ──────►  Add interactivity
                                   Fetch dynamic data via API
```

### 3. Separation of Concerns: Pages vs API

**Decision:** Separate routes for HTML pages and JSON API endpoints.

**File Structure:**

```python
app/api/v1/admin/
├── pages.py          # Returns HTML (Jinja2 templates)
│   └── @router.get("/dashboard", response_class=HTMLResponse)
│
└── routes.py         # Returns JSON (API endpoints)
    └── @router.get("/dashboard/stats", response_model=StatsResponse)
```

**Rationale:**
- **Clear Responsibility:** Page routes render HTML, API routes return JSON
- **RESTful Design:** API endpoints remain pure and reusable
- **Flexibility:** Can build mobile app using same API
- **Testing:** Can test HTML rendering and API logic separately

**URL Structure:**

```
Old (Current):
  /static/admin/dashboard.html       (Static file)
  /api/v1/admin/dashboard/stats      (JSON API)

New (Proposed):
  /admin/dashboard                   (HTML via Jinja2)
  /api/v1/admin/dashboard/stats      (JSON API - unchanged)
```

### 4. Authentication & Authorization at Route Level

**Decision:** Use FastAPI dependencies for auth on page routes.

**Example:**

```python
from fastapi import Depends
from app.api.deps import get_current_admin_user

@router.get("/dashboard", response_class=HTMLResponse)
async def admin_dashboard_page(
    request: Request,
    current_user: User = Depends(get_current_admin_user),  # ← Auth check
    db: Session = Depends(get_db)
):
    """
    Render admin dashboard page.
    Requires admin authentication - redirects to login if not authenticated.
    """
    return templates.TemplateResponse(
        "admin/dashboard.html",
        {
            "request": request,
            "user": current_user  # ← Can access in template
        }
    )
```

**Rationale:**
- **Security First:** Cannot access page without authentication
- **Automatic Redirects:** FastAPI handles 401 → login redirect
- **User Context:** Pass authenticated user to templates
- **Permissions:** Can check roles/permissions before rendering

### 5. Keep Alpine.js for Dynamic Interactions

**Decision:** Continue using Alpine.js for client-side reactivity.

**What Stays the Same:**
- ✅ Alpine.js for interactive components
- ✅ `x-data`, `x-show`, `x-if` directives
- ✅ API calls via `apiClient.js`
- ✅ Icon system (`icons.js`)
- ✅ Utility functions (`utils.js`)

**What Changes:**
- ❌ No more `partial-loader.js` (Jinja2 handles includes)
- ❌ No more client-side template loading
- ✅ Alpine initializes on complete HTML (faster)

**Example:**

```html
{# dashboard.html - Alpine.js still works! #}
{% extends "admin/base.html" %}

{% block alpine_data %}adminDashboard(){% endblock %}

{% block content %}
<div x-data="adminDashboard()">
    <h1 x-text="pageTitle">Dashboard</h1>
    
    <button @click="refresh()">
        <span x-html="$icon('refresh')"></span>
        Refresh
    </button>
    
    <div x-show="loading">
        <span x-html="$icon('spinner')"></span>
        Loading...
    </div>
    
    <template x-for="item in items" :key="item.id">
        <div x-text="item.name"></div>
    </template>
</div>
{% endblock %}
```

### 6. Static Assets Remain Static

**Decision:** Keep CSS, JS, images in `/static/` directory.

**Rationale:**
- **Performance:** Static files served with caching headers
- **CDN Ready:** Can move to CDN later if needed
- **No Change Needed:** Existing assets work as-is

**Mounting:**

```python
from fastapi.staticfiles import StaticFiles

app.mount("/static", StaticFiles(directory="static"), name="static")
```

**Usage in Templates:**

```jinja2
{# Jinja2 provides url_for() for static files #}
<link rel="stylesheet" href="{{ url_for('static', path='/admin/css/tailwind.output.css') }}" />
<script src="{{ url_for('static', path='/admin/js/dashboard.js') }}"></script>
```

### 7. Data Flow: Server → Template → Alpine.js

**Decision:** Three-tier data flow for optimal performance.

**Tier 1: Server-Side Data (Initial Page Load)**

```python
@router.get("/dashboard")
async def dashboard(request: Request, current_user: User = Depends(...)):
    # Can pass initial data to avoid extra API call
    initial_stats = await get_dashboard_stats()
    
    return templates.TemplateResponse(
        "admin/dashboard.html",
        {
            "request": request,
            "user": current_user,
            "initial_stats": initial_stats  # ← Available in template
        }
    )
```

**Tier 2: Template Rendering (Jinja2)**

```jinja2
{# Can render initial data from server #}
<div class="stat-card">
    <h3>Total Users</h3>
    <p>{{ initial_stats.total_users }}</p>  {# ← From server #}
</div>

{# Or let Alpine.js fetch it #}
<div x-data="adminDashboard()">
    <p x-text="stats.totalUsers"></p>  {# ← From Alpine/API #}
</div>
```

**Tier 3: Alpine.js (Dynamic Updates)**

```javascript
function adminDashboard() {
    return {
        stats: {},
        async init() {
            // Fetch fresh data via API
            this.stats = await apiClient.get('/admin/dashboard/stats');
        },
        async refresh() {
            // Re-fetch on demand
            this.stats = await apiClient.get('/admin/dashboard/stats');
        }
    };
}
```

**When to Use Each Tier:**

| Data Type | Use Server | Use Alpine | Rationale |
|-----------|------------|------------|-----------|
| User info | ✅ Server | ❌ | Available at auth time, no extra call needed |
| Permissions | ✅ Server | ❌ | Security-sensitive, should be server-side |
| Static config | ✅ Server | ❌ | Doesn't change during session |
| Dashboard stats | ⚠️ Either | ✅ Alpine | Initial load via server, refresh via Alpine |
| Real-time data | ❌ | ✅ Alpine | Changes frequently, fetch via API |
| Form data | ❌ | ✅ Alpine | User input, submit via API |

## Migration Strategy

### Phase 1: Preparation (No Breaking Changes)

**Goal:** Set up infrastructure without affecting existing pages.

**Tasks:**
1. ✅ Create `app/templates/` directory structure
2. ✅ Create `utils.js` (missing file)
3. ✅ Create `app/api/v1/admin/pages.py` (new file)
4. ✅ Configure Jinja2 in FastAPI
5. ✅ Create base template (`base.html`)

**Timeline:** 1-2 hours
**Risk:** Low (no existing code changes)

### Phase 2: Migrate One Page (Proof of Concept)

**Goal:** Migrate dashboard.html to prove the approach works.

**Tasks:**
1. Create `app/templates/admin/dashboard.html`
2. Create route in `pages.py`
3. Test authentication flow
4. Test Alpine.js integration
5. Verify API calls still work

**Validation:**
- [ ] Dashboard loads at `/admin/dashboard`
- [ ] Authentication required to access
- [ ] Stats cards display correctly
- [ ] Alpine.js reactivity works
- [ ] API calls fetch data correctly
- [ ] Icon system works
- [ ] Dark mode toggle works

**Timeline:** 2-3 hours
**Risk:** Low (parallel to existing page)

### Phase 3: Migrate Remaining Pages

**Goal:** Migrate vendors.html, users.html using same pattern.

**Tasks:**
1. Create templates for each page
2. Create routes in `pages.py`
3. Test each page independently
4. Update internal links

**Timeline:** 3-4 hours
**Risk:** Low (pattern established)

### Phase 4: Cleanup & Deprecation

**Goal:** Remove old static HTML files.

**Tasks:**
1. Update all navigation links
2. Remove `partial-loader.js`
3. Remove old HTML files from `/static/admin/`
4. Update documentation
5. Update deployment scripts if needed

**Timeline:** 1-2 hours
**Risk:** Medium (ensure all links updated)

### Migration Checklist

```markdown
## Pre-Migration
- [ ] Backup current codebase
- [ ] Document current URLs and functionality
- [ ] Create feature branch: `feature/jinja2-templates`

## Phase 1: Setup
- [ ] Create `app/templates/admin/` directory
- [ ] Create `app/templates/partials/` directory
- [ ] Create `utils.js` in `/static/shared/js/`
- [ ] Create `app/api/v1/admin/pages.py`
- [ ] Configure Jinja2Templates in FastAPI
- [ ] Create `base.html` template
- [ ] Move `header.html` to `app/templates/partials/`
- [ ] Move `sidebar.html` to `app/templates/partials/`
- [ ] Test template rendering with simple page

## Phase 2: Dashboard Migration
- [ ] Create `app/templates/admin/dashboard.html`
- [ ] Create dashboard route in `pages.py`
- [ ] Add authentication dependency
- [ ] Test page renders correctly
- [ ] Test Alpine.js initialization
- [ ] Test API data fetching
- [ ] Test icon system
- [ ] Test user menu and logout
- [ ] Test dark mode toggle
- [ ] Compare old vs new side-by-side

## Phase 3: Additional Pages
- [ ] Create `vendors.html` template
- [ ] Create `users.html` template
- [ ] Create routes for each page
- [ ] Test each page independently
- [ ] Test navigation between pages
- [ ] Update breadcrumbs if applicable

## Phase 4: Cleanup
- [ ] Update all internal links to new URLs
- [ ] Remove `partial-loader.js`
- [ ] Remove old HTML files from `/static/admin/`
- [ ] Update README with new architecture
- [ ] Update this documentation
- [ ] Run full test suite
- [ ] Merge feature branch to main

## Post-Migration
- [ ] Monitor error logs for 404s
- [ ] Collect user feedback
- [ ] Performance testing
- [ ] Document lessons learned
```

## File Structure Comparison

### Before (Current)

```
project/
├── static/
│   └── admin/
│       ├── dashboard.html          ← 150 lines (full HTML)
│       ├── vendors.html            ← 150 lines (full HTML)
│       ├── users.html              ← 150 lines (full HTML)
│       ├── partials/
│       │   ├── header.html         ← 80 lines
│       │   └── sidebar.html        ← 120 lines
│       ├── css/
│       │   └── tailwind.output.css
│       └── js/
│           ├── init-alpine.js
│           ├── dashboard.js
│           ├── vendors.js
│           └── users.js
└── app/
    └── api/
        └── v1/
            └── admin/
                └── routes.py       ← API endpoints only

Total HTML Lines: ~650 lines with massive duplication
```

### After (Proposed)

```
project/
├── app/
│   ├── templates/
│   │   ├── admin/
│   │   │   ├── base.html          ← 80 lines (reused by all)
│   │   │   ├── dashboard.html     ← 40 lines (content only)
│   │   │   ├── vendors.html       ← 40 lines (content only)
│   │   │   └── users.html         ← 40 lines (content only)
│   │   └── partials/
│   │       ├── header.html        ← 80 lines
│   │       └── sidebar.html       ← 120 lines
│   └── api/
│       └── v1/
│           └── admin/
│               ├── pages.py       ← NEW! Page routes
│               └── routes.py      ← API endpoints
└── static/
    └── admin/
        ├── css/
        │   └── tailwind.output.css
        └── js/
            ├── init-alpine.js
            ├── dashboard.js
            ├── vendors.js
            └── users.js

Total HTML Lines: ~400 lines with NO duplication
Savings: ~250 lines (38% reduction)
```

## Benefits & Trade-offs

### Benefits

#### 1. Reduced Code Duplication

**Metric:**
- Current: ~650 lines of HTML with 60% duplication
- After: ~400 lines with 0% duplication
- **Savings: 38% fewer lines to maintain**

#### 2. Better Performance

| Metric | Before | After | Improvement |
|--------|--------|-------|-------------|
| Initial Page Requests | 3+ (HTML + header + sidebar) | 1 (complete HTML) | **66% fewer requests** |
| Time to First Paint | ~300ms | ~150ms | **50% faster** |
| JavaScript Parse Time | Same | Same | No change |

#### 3. Improved Security

**Before:**
- ❌ Client can access `/static/admin/dashboard.html` directly
- ❌ Must implement client-side auth checks
- ❌ Can bypass authentication by manipulating JavaScript

**After:**
- ✅ Server checks authentication before rendering
- ✅ FastAPI `Depends()` enforces auth at route level
- ✅ Cannot bypass server-side checks

#### 4. Better SEO (Future-Proof)

**Before:**
- ❌ Static HTML with no dynamic meta tags
- ❌ Same title/description for all pages

**After:**
- ✅ Can generate dynamic meta tags per page
- ✅ Can inject structured data for search engines

```jinja2
{% block head %}
    <meta name="description" content="Admin dashboard for {{ user.company_name }}" />
    <meta property="og:title" content="Dashboard - {{ user.company_name }}" />
{% endblock %}
```

#### 5. Easier Maintenance

**Scenario: Update header navigation**

**Before:**
```
Edit header.html → Save → Test → Works ✓
But: Must reload page to see changes (AJAX loads old cached version)
```

**After:**
```
Edit header.html → Save → Refresh → Works ✓
Changes appear immediately (server-side include)
```

#### 6. Better Developer Experience

| Task | Before | After |
|------|--------|-------|
| Create new page | Copy 150 lines, modify content | Extend base, write 40 lines |
| Change layout | Edit every HTML file | Edit base.html once |
| Add auth to page | Write JavaScript checks | Add `Depends()` to route |
| Pass user data | API call in Alpine.js | Available in template |
| Debug template | Browser + Network tab | Browser + FastAPI logs |

### Trade-offs

#### 1. Server Load ⚖️

**Before:**
- Serving static files (nginx-level, very fast)
- No server-side processing

**After:**
- FastAPI renders Jinja2 on each request
- Minimal overhead (~1-2ms per render)

**Mitigation:**
- Jinja2 is extremely fast (C-compiled)
- Can add template caching if needed
- Static assets still served by nginx

**Verdict:** Negligible impact for admin panel traffic

#### 2. Learning Curve ⚖️

**Before:**
- Developers only need HTML + Alpine.js

**After:**
- Developers need Jinja2 syntax
- Must understand template inheritance

**Mitigation:**
- Jinja2 is very similar to Django/Flask templates
- Documentation provided
- Pattern is straightforward once learned

**Verdict:** Small one-time learning cost

#### 3. Debugging Changes ⚖️

**Before:**
- View source in browser = actual file
- Easy to inspect what's loaded

**After:**
- View source = rendered output
- Must check template files in codebase

**Mitigation:**
- Better error messages from FastAPI
- Template path shown in errors
- Can enable Jinja2 debug mode

**Verdict:** Different, not harder

#### 4. Cannot Edit in Browser DevTools ⚖️

**Before:**
- Can edit static HTML in browser, reload

**After:**
- Must edit template file, refresh server

**Mitigation:**
- FastAPI auto-reloads on file changes
- Hot reload works well in development

**Verdict:** Minimal impact, proper workflow

## Code Examples

### Example 1: Base Template

```jinja2
{# app/templates/admin/base.html #}
<!DOCTYPE html>
<html :class="{ 'theme-dark': dark }" x-data="{% block alpine_data %}data(){% endblock %}" lang="en">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>{% block title %}Admin Panel{% endblock %} - Multi-Tenant Platform</title>
    
    <!-- Fonts -->
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
    
    <!-- Tailwind CSS -->
    <link rel="stylesheet" href="{{ url_for('static', path='/admin/css/tailwind.output.css') }}" />
    
    <!-- Alpine Cloak -->
    <style>
        [x-cloak] { display: none !important; }
    </style>
    
    {% block extra_head %}{% endblock %}
</head>
<body x-cloak>
    <div class="flex h-screen bg-gray-50 dark:bg-gray-900" :class="{ 'overflow-hidden': isSideMenuOpen }">
        <!-- Sidebar -->
        {% include 'partials/sidebar.html' %}

        <div class="flex flex-col flex-1 w-full">
            <!-- Header -->
            {% include 'partials/header.html' %}

            <!-- Main Content -->
            <main class="h-full overflow-y-auto">
                <div class="container px-6 mx-auto grid">
                    {% block content %}{% endblock %}
                </div>
            </main>
        </div>
    </div>

    <!-- Core Scripts -->
    <script src="{{ url_for('static', path='/shared/js/icons.js') }}"></script>
    <script src="{{ url_for('static', path='/admin/js/init-alpine.js') }}"></script>
    <script src="{{ url_for('static', path='/shared/js/api-client.js') }}"></script>
    <script src="{{ url_for('static', path='/shared/js/utils.js') }}"></script>
    
    <!-- Alpine.js v3 -->
    <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.0/dist/cdn.min.js"></script>
    
    {% block extra_scripts %}{% endblock %}
</body>
</html>
```

**Key Features:**
- `{% block alpine_data %}` - Allows child templates to specify Alpine component
- `{{ url_for('static', path='...') }}` - Proper static file URLs
- `{% include %}` - Server-side include (no AJAX needed)
- `{% block content %}` - Child templates inject content here
- `{% block extra_scripts %}` - Page-specific JavaScript

### Example 2: Child Template (Dashboard)

```jinja2
{# app/templates/admin/dashboard.html #}
{% extends "admin/base.html" %}

{% block title %}Dashboard{% endblock %}

{% block alpine_data %}adminDashboard(){% endblock %}

{% block content %}
<!-- Page Header -->
<div class="flex items-center justify-between my-6">
    <h2 class="text-2xl font-semibold text-gray-700 dark:text-gray-200">
        Dashboard
    </h2>
    <button 
        @click="refresh()"
        :disabled="loading"
        class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-white bg-purple-600 rounded-lg hover:bg-purple-700"
    >
        <span x-show="!loading" x-html="$icon('refresh', 'w-4 h-4 mr-2')"></span>
        <span x-show="loading" x-html="$icon('spinner', 'w-4 h-4 mr-2')"></span>
        <span x-text="loading ? 'Loading...' : 'Refresh'"></span>
    </button>
</div>

<!-- Stats Cards -->
<div class="grid gap-6 mb-8 md:grid-cols-2 xl:grid-cols-4">
    <!-- Card -->
    <div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
        <div class="p-3 mr-4 text-orange-500 bg-orange-100 rounded-full">
            <span x-html="$icon('user-group', 'w-5 h-5')"></span>
        </div>
        <div>
            <p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">
                Total Vendors
            </p>
            <p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="stats.totalVendors">
                0
            </p>
        </div>
    </div>
</div>
{% endblock %}

{% block extra_scripts %}
<script src="{{ url_for('static', path='/admin/js/dashboard.js') }}"></script>
{% endblock %}
```

**Key Features:**
- Extends `base.html` for layout
- Overrides specific blocks (`title`, `alpine_data`, `content`, `extra_scripts`)
- Uses Alpine.js directives (`x-data`, `x-show`, `x-text`, `x-html`)
- Uses icon system via `$icon()` magic helper
- Page-specific JavaScript loaded via `extra_scripts` block

### Example 3: Route Configuration (pages.py)

```python
# app/api/v1/admin/pages.py
from fastapi import APIRouter, Request, Depends
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from sqlalchemy.orm import Session

from app.api.deps import get_current_admin_user, get_db
from app.models import User

router = APIRouter()
templates = Jinja2Templates(directory="app/templates")

@router.get("/dashboard", response_class=HTMLResponse)
async def admin_dashboard_page(
    request: Request,
    current_user: User = Depends(get_current_admin_user),
    db: Session = Depends(get_db)
):
    """
    Render admin dashboard page.
    Requires admin authentication.
    """
    # Optional: Pass initial data to avoid extra API call
    # initial_stats = await get_dashboard_stats(db)
    
    return templates.TemplateResponse(
        "admin/dashboard.html",
        {
            "request": request,
            "user": current_user,
            # "initial_stats": initial_stats,
        }
    )

@router.get("/vendors", response_class=HTMLResponse)
async def admin_vendors_page(
    request: Request,
    current_user: User = Depends(get_current_admin_user),
    db: Session = Depends(get_db)
):
    """
    Render vendors management page.
    Requires admin authentication.
    """
    return templates.TemplateResponse(
        "admin/vendors.html",
        {
            "request": request,
            "user": current_user,
        }
    )

@router.get("/users", response_class=HTMLResponse)
async def admin_users_page(
    request: Request,
    current_user: User = Depends(get_current_admin_user),
    db: Session = Depends(get_db)
):
    """
    Render users management page.
    Requires admin authentication.
    """
    return templates.TemplateResponse(
        "admin/users.html",
        {
            "request": request,
            "user": current_user,
        }
    )
```

### Example 4: Main App Configuration

```python
# app/main.py
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates

from app.api.v1.admin import routes as admin_api_routes
from app.routes import pages as admin_page_routes

app = FastAPI(title="Multi-Tenant Platform")

# Mount static files
app.mount("/static", StaticFiles(directory="static"), name="static")

# Configure Jinja2
templates = Jinja2Templates(directory="app/templates")

# Include API routes (JSON endpoints)
app.include_router(
    admin_api_routes.router,
    prefix="/api/v1/admin",
    tags=["admin-api"]
)

# Include page routes (HTML rendering)
app.include_router(
    admin_page_routes.router,
    prefix="/admin",
    tags=["admin-pages"]
)


@app.get("/")
async def root():
    return {"message": "Multi-Tenant Platform API"}
```

## Testing Strategy

### Unit Tests

**Test Template Rendering:**

```python
# tests/test_templates.py
from fastapi.testclient import TestClient
from app.main import app

client = TestClient(app)

def test_dashboard_requires_authentication():
    """Dashboard should redirect to login if not authenticated."""
    response = client.get("/admin/dashboard")
    assert response.status_code == 401  # or 302 redirect

def test_dashboard_renders_for_authenticated_user(authenticated_client):
    """Dashboard should render for authenticated admin user."""
    response = authenticated_client.get("/admin/dashboard")
    assert response.status_code == 200
    assert b"Dashboard" in response.content
    assert b"<!DOCTYPE html>" in response.content

def test_dashboard_includes_user_data(authenticated_client, test_user):
    """Dashboard should include user information."""
    response = authenticated_client.get("/admin/dashboard")
    assert test_user.email.encode() in response.content
```

**Test Alpine.js Integration:**

```python
def test_dashboard_includes_alpine_js(authenticated_client):
    """Dashboard should include Alpine.js script."""
    response = authenticated_client.get("/admin/dashboard")
    assert b"alpinejs" in response.content
    assert b'x-data="adminDashboard()"' in response.content
```

### Integration Tests

**Test Full Page Flow:**

```python
# tests/integration/test_admin_pages.py
from playwright.sync_api import sync_playwright

def test_dashboard_loads_and_fetches_data():
    """Test dashboard loads, Alpine initializes, and fetches data."""
    with sync_playwright() as p:
        browser = p.chromium.launch()
        page = browser.new_page()
        
        # Navigate to dashboard
        page.goto("http://localhost:8000/admin/dashboard")
        
        # Should redirect to login if not authenticated
        assert page.url.endswith("/login")
        
        # Login
        page.fill('input[name="email"]', "admin@example.com")
        page.fill('input[name="password"]', "password")
        page.click('button[type="submit"]')
        
        # Should redirect to dashboard
        page.wait_for_url("**/admin/dashboard")
        
        # Wait for Alpine to initialize
        page.wait_for_selector('[x-data="adminDashboard()"]')
        
        # Check stats cards load
        page.wait_for_selector(".stat-card")
        
        # Verify data is displayed
        assert page.locator('text="Total Vendors"').is_visible()
        
        browser.close()
```

### Performance Tests

**Measure Rendering Time:**

```python
import time
from fastapi.testclient import TestClient

def test_dashboard_render_performance(authenticated_client):
    """Dashboard should render in under 50ms."""
    start = time.time()
    response = authenticated_client.get("/admin/dashboard")
    duration = time.time() - start
    
    assert response.status_code == 200
    assert duration < 0.05  # 50ms
```

## Rollback Plan

### If Issues Arise During Migration

**Phase 1 Rollback (Setup Phase):**
- No rollback needed - no breaking changes
- Simply don't use new templates yet

**Phase 2 Rollback (Dashboard Migration):**
1. Keep old dashboard.html in place
2. Remove new route from `pages.py`
3. Revert any URL changes
4. No data loss or downtime

**Phase 3-4 Rollback:**
1. Restore old HTML files from backup
2. Re-add `partial-loader.js`
3. Update navigation links back to old URLs
4. Remove new routes from `pages.py`

**Emergency Rollback Script:**

```bash
#!/bin/bash
# rollback.sh

echo "Rolling back Jinja2 migration..."

# Restore old HTML files
git checkout main -- static/admin/dashboard.html
git checkout main -- static/admin/vendors.html
git checkout main -- static/admin/users.html
git checkout main -- static/shared/js/partial-loader.js

# Remove new page routes
git checkout main -- app/api/v1/admin/pages.py

# Restart server
echo "Restarting server..."
systemctl restart fastapi-app

echo "Rollback complete!"
```

### Health Checks

**Monitor After Deployment:**

```python
# app/health.py
from fastapi import APIRouter

router = APIRouter()

@router.get("/health/templates")
async def check_templates():
    """Check if templates are rendering correctly."""
    try:
        from fastapi.templating import Jinja2Templates
        templates = Jinja2Templates(directory="app/templates")
        # Try to get template
        templates.get_template("admin/base.html")
        return {"status": "healthy", "templates": "ok"}
    except Exception as e:
        return {"status": "unhealthy", "error": str(e)}
```

## Conclusion

This migration from client-side static HTML to server-side Jinja2 templates provides:

**Key Benefits:**
- ✅ 38% reduction in code duplication
- ✅ 66% fewer HTTP requests on initial page load
- ✅ 50% faster time to first paint
- ✅ Better security with server-side authentication
- ✅ Cleaner URLs and better SEO
- ✅ Easier maintenance and development

**Minimal Trade-offs:**
- ⚠️ Slight increase in server load (negligible)
- ⚠️ Small learning curve for Jinja2 syntax
- ⚠️ Different debugging workflow

**Next Steps:**
1. Review and approve this architecture document
2. Schedule migration phases
3. Begin Phase 1 (infrastructure setup)
4. Migrate dashboard as proof of concept
5. Roll out to remaining pages
6. Monitor and optimize

**Timeline:** 1-2 days for complete migration
**Risk Level:** Low (incremental, reversible changes)