# CDN Fallback Strategy ## Overview All three frontends (Shop, Store, Admin) implement a robust CDN fallback strategy for critical CSS and JavaScript assets. This ensures the application works reliably in: - ✅ **Offline development** environments - ✅ **Corporate networks** with restricted CDN access - ✅ **CDN outages** or performance issues - ✅ **Air-gapped deployments** without internet access ## Assets with Fallback The following assets are loaded from CDN with automatic fallback to local copies: ### Core Assets (Always Loaded) | Asset | CDN Source | Local Fallback | |-------|-----------|----------------| | **Tailwind CSS** | `https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css` | `static/shared/css/tailwind.min.css` | | **Alpine.js** | `https://cdn.jsdelivr.net/npm/alpinejs@3.13.3/dist/cdn.min.js` | `static/shared/js/lib/alpine.min.js` | ### Optional Assets (Loaded On Demand) | Asset | CDN Source | Local Fallback | Used For | |-------|-----------|----------------|----------| | **Chart.js** | `https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js` | `static/shared/js/lib/chart.umd.min.js` | Charts macros | | **Flatpickr JS** | `https://cdn.jsdelivr.net/npm/flatpickr@4.6.13/dist/flatpickr.min.js` | `static/shared/js/lib/flatpickr.min.js` | Datepicker macros | | **Flatpickr CSS** | `https://cdn.jsdelivr.net/npm/flatpickr@4.6.13/dist/flatpickr.min.css` | `static/shared/css/store/flatpickr.min.css` | Datepicker styling | ## Implementation ### Tailwind CSS Fallback Tailwind CSS uses the HTML `onerror` attribute to detect CDN failures and switch to the local copy: ```html ``` **How it works:** 1. Browser attempts to load Tailwind CSS from CDN 2. If CDN fails (network error, timeout, 404), `onerror` event fires 3. Handler sets `onerror=null` to prevent infinite loops 4. Handler updates `href` to local copy path 5. Browser automatically loads local copy ### Alpine.js Fallback Alpine.js uses dynamic script loading with error handling: ```html ``` **How it works:** 1. IIFE creates a script element dynamically 2. Sets `defer` attribute to match CDN loading behavior 3. Attempts to load Alpine.js from CDN 4. If CDN fails, `onerror` handler triggers 5. Logs warning to console for debugging 6. Creates new script element pointing to local copy 7. Appends fallback script to `
` ### Optional Libraries (Chart.js & Flatpickr) Optional libraries are loaded on-demand using Jinja macros. This keeps page load times fast by only loading what's needed. **Location:** `app/templates/shared/includes/optional-libs.html` #### Loading Chart.js In your page template: ```jinja {% block chartjs_script %} {% from 'shared/includes/optional-libs.html' import chartjs_loader %} {{ chartjs_loader() }} {% endblock %} ``` **Generated code:** ```html ``` #### Loading Flatpickr Flatpickr requires both CSS and JS: ```jinja {# In head - load CSS #} {% block flatpickr_css %} {% from 'shared/includes/optional-libs.html' import flatpickr_css_loader %} {{ flatpickr_css_loader() }} {% endblock %} {# Before page scripts - load JS #} {% block flatpickr_script %} {% from 'shared/includes/optional-libs.html' import flatpickr_loader %} {{ flatpickr_loader() }} {% endblock %} ``` **Generated CSS code:** ```html ``` #### Available Blocks in admin/base.html | Block | Purpose | Location | |-------|---------|----------| | `flatpickr_css` | Flatpickr CSS | In `` | | `chartjs_script` | Chart.js | Before page scripts | | `flatpickr_script` | Flatpickr JS | Before page scripts | | `extra_scripts` | Page-specific JS | Last in body | ## File Structure ``` static/ ├── shared/ │ ├── css/ │ │ ├── tailwind.min.css # 2.9M - Tailwind CSS v2.2.19 │ │ └── store/ │ │ └── flatpickr.min.css # 16K - Flatpickr v4.6.13 │ └── js/ │ └── store/ │ ├── alpine.min.js # 44K - Alpine.js v3.13.3 │ ├── chart.umd.min.js # 205K - Chart.js v4.4.1 │ └── flatpickr.min.js # 51K - Flatpickr v4.6.13 ├── shop/ │ └── css/ │ └── shop.css # Shop-specific styles ├── store/ │ └── css/ │ └── tailwind.output.css # Store-specific overrides └── admin/ └── css/ └── tailwind.output.css # Admin-specific overrides app/templates/shared/ ├── includes/ │ └── optional-libs.html # CDN fallback loaders for Chart.js & Flatpickr └── macros/ ├── charts.html # Chart.js wrapper macros └── datepicker.html # Flatpickr wrapper macros ``` ## Frontend-Specific Implementations ### Shop Frontend **Template:** `app/templates/storefront/base.html` ```html {# Lines 41-42: Tailwind CSS fallback #} {# Lines 247-263: Alpine.js fallback #} ``` ### Store Frontend **Template:** `app/templates/store/base.html` Same pattern as Shop frontend, with store-specific Tailwind overrides loaded after the base: ```html {# Lines 13-14: Tailwind CSS fallback #} {# Line 17: Store-specific overrides #} {# Lines 62-78: Alpine.js fallback #} ``` ### Admin Frontend **Template:** `app/templates/admin/base.html` Same pattern as Store frontend, with admin-specific Tailwind overrides: ```html {# Lines 13-14: Tailwind CSS fallback #} {# Line 17: Admin-specific overrides #} {# Lines 62-78: Alpine.js fallback #} ``` ## Development Setup ### Verifying Local Assets Check that local fallback files exist: ```bash # From project root ls -lh static/shared/css/tailwind.min.css ls -lh static/shared/js/lib/alpine.min.js ``` Expected output: ``` -rw-r--r-- 1 user user 2.9M static/shared/css/tailwind.min.css -rw-r--r-- 1 user user 44K static/shared/js/lib/alpine.min.js ``` ### Testing Offline Mode To test the fallback behavior: 1. **Start the application:** ```bash uvicorn app.main:app --reload ``` 2. **Simulate CDN failure:** - Use browser DevTools Network tab - Add blocking rule for `cdn.jsdelivr.net` - Or disconnect from internet 3. **Check console output:** ``` ⚠️ Alpine.js CDN failed, loading local copy... ``` 4. **Verify fallback loaded:** - Open DevTools Network tab - Check that `/static/shared/js/lib/alpine.min.js` was loaded - Check that `/static/shared/css/tailwind.min.css` was loaded ### Updating Local Assets To update Tailwind CSS to a newer version: ```bash cd static/shared/css curl -o tailwind.min.css https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css ``` To update Alpine.js to a newer version: ```bash cd static/shared/js/store curl -o alpine.min.js https://cdn.jsdelivr.net/npm/alpinejs@3.13.3/dist/cdn.min.js ``` To update Chart.js: ```bash cd static/shared/js/store curl -o chart.umd.min.js https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js ``` To update Flatpickr: ```bash # JavaScript cd static/shared/js/store curl -o flatpickr.min.js https://cdn.jsdelivr.net/npm/flatpickr@4.6.13/dist/flatpickr.min.js # CSS cd static/shared/css/store curl -o flatpickr.min.css https://cdn.jsdelivr.net/npm/flatpickr@4.6.13/dist/flatpickr.min.css ``` **Important:** Update both the local file AND the CDN URL in the following locations: | Asset | Update Location | |-------|-----------------| | Tailwind CSS | All three `base.html` templates | | Alpine.js | All three `base.html` templates | | Chart.js | `shared/includes/optional-libs.html` | | Flatpickr | `shared/includes/optional-libs.html` | ## Production Deployment ### Verification Checklist Before deploying to production, verify: - [ ] Local asset files exist in `static/shared/` - [ ] Core assets: `tailwind.min.css`, `alpine.min.js` - [ ] Optional assets: `chart.umd.min.js`, `flatpickr.min.js`, `flatpickr.min.css` - [ ] File permissions are correct (readable by web server) - [ ] Static file serving is enabled in FastAPI - [ ] CDN URLs are accessible from production network - [ ] Fallback mechanism tested in production-like environment ### Docker Deployment Ensure local assets are included in Docker image: ```dockerfile # Dockerfile COPY static/ /app/static/ # Verify core files exist RUN test -f /app/static/shared/css/tailwind.min.css && \ test -f /app/static/shared/js/lib/alpine.min.js # Verify optional library files exist RUN test -f /app/static/shared/js/lib/chart.umd.min.js && \ test -f /app/static/shared/js/lib/flatpickr.min.js && \ test -f /app/static/shared/css/store/flatpickr.min.css ``` ### Static File Configuration FastAPI configuration in `app/main.py`: ```python from fastapi.staticfiles import StaticFiles app.mount("/static", StaticFiles(directory="static"), name="static") ``` ## Troubleshooting ### Issue: Both CDN and local copy fail to load **Symptoms:** - No Tailwind styles applied - Alpine.js not initializing - Console errors about missing files **Solutions:** 1. Check static file mounting configuration 2. Verify file paths in browser Network tab 3. Check file permissions: `chmod 644 static/shared/css/tailwind.min.css` 4. Verify FastAPI StaticFiles mount point ### Issue: Fallback triggers unnecessarily **Symptoms:** - Console warnings in normal operation - Inconsistent CDN loading **Solutions:** 1. Check network stability 2. Verify CDN URLs are correct and accessible 3. Check for firewall/proxy blocking CDN 4. Consider using local-only mode for restricted networks ### Issue: Page loads slowly due to CDN timeout **Symptoms:** - Long wait before fallback triggers - Poor user experience during CDN issues **Solutions:** 1. Reduce CDN timeout (browser-dependent) 2. Consider Content Security Policy (CSP) with shorter timeouts 3. For air-gapped deployments, remove CDN URLs entirely and use local-only ## Performance Considerations ### CDN Benefits (Normal Operation) - ✅ Faster initial load (CDN edge caching) - ✅ Reduced server bandwidth - ✅ Browser caching across sites using same CDN ### Fallback Impact - ⚠️ Additional ~3MB transferred on first load (if CDN fails) - ⚠️ Slight delay during CDN failure detection - ✅ Subsequent page loads use browser cache ### Optimization Strategies 1. **For production with reliable CDN access:** - Keep current implementation - Monitor CDN uptime 2. **For corporate/restricted networks:** - Consider removing CDN URLs entirely - Load local assets directly 3. **For air-gapped deployments:** - Remove CDN URLs completely - Update templates to use local-only: ```html ``` ## Browser Compatibility The fallback strategy works in all modern browsers: | Browser | Tailwind Fallback | Alpine.js Fallback | |---------|------------------|-------------------| | Chrome 90+ | ✅ | ✅ | | Firefox 88+ | ✅ | ✅ | | Safari 14+ | ✅ | ✅ | | Edge 90+ | ✅ | ✅ | **Note:** Internet Explorer is not supported (Alpine.js requires ES6+). ## Security Considerations ### Subresource Integrity (SRI) Currently not implemented. To add SRI hashes: ```html ``` **Trade-off:** SRI hashes prevent loading if CDN file changes, which may interfere with fallback mechanism. ### Content Security Policy (CSP) If using CSP, ensure CDN domains are whitelisted: ```http Content-Security-Policy: style-src 'self' https://cdn.jsdelivr.net; script-src 'self' https://cdn.jsdelivr.net 'unsafe-inline'; ``` **Note:** `'unsafe-inline'` is required for the Alpine.js fallback IIFE. ## Related Documentation - [Storefront Architecture](storefront/architecture.md) - [Store Frontend Architecture](store/architecture.md) - [Admin Frontend Architecture](admin/architecture.md) - [Production Deployment](../deployment/production.md) - [Docker Deployment](../deployment/docker.md) - [Troubleshooting Guide](../development/troubleshooting.md)