# CDN Fallback Strategy ## Overview All three frontends (Shop, Vendor, 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: | 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/vendor/alpine.min.js` | ## 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 `` ## File Structure ``` static/ ├── shared/ │ ├── css/ │ │ └── tailwind.min.css # 2.9M - Tailwind CSS v2.2.19 │ └── js/ │ └── vendor/ │ └── alpine.min.js # 44K - Alpine.js v3.13.3 ├── shop/ │ └── css/ │ └── shop.css # Shop-specific styles ├── vendor/ │ └── css/ │ └── tailwind.output.css # Vendor-specific overrides └── admin/ └── css/ └── tailwind.output.css # Admin-specific overrides ``` ## Frontend-Specific Implementations ### Shop Frontend **Template:** `app/templates/shop/base.html` ```html {# Lines 41-42: Tailwind CSS fallback #} {# Lines 247-263: Alpine.js fallback #} ``` ### Vendor Frontend **Template:** `app/templates/vendor/base.html` Same pattern as Shop frontend, with vendor-specific Tailwind overrides loaded after the base: ```html {# Lines 13-14: Tailwind CSS fallback #} {# Line 17: Vendor-specific overrides #} {# Lines 62-78: Alpine.js fallback #} ``` ### Admin Frontend **Template:** `app/templates/admin/base.html` Same pattern as Vendor 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/vendor/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/vendor/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/vendor/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/vendor curl -o alpine.min.js https://cdn.jsdelivr.net/npm/alpinejs@3.13.3/dist/cdn.min.js ``` **Important:** Update both the local file AND the CDN URL in all three base templates. ## Production Deployment ### Verification Checklist Before deploying to production, verify: - [ ] Local asset files exist in `static/shared/` - [ ] 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 files exist RUN test -f /app/static/shared/css/tailwind.min.css && \ test -f /app/static/shared/js/vendor/alpine.min.js ``` ### 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 - [Shop Frontend Architecture](shop/architecture.md) - [Vendor Frontend Architecture](vendor/architecture.md) - [Admin Frontend Architecture](admin/architecture.md) - [Production Deployment](../deployment/production.md) - [Docker Deployment](../deployment/docker.md) - [Troubleshooting Guide](../development/troubleshooting.md)