diff --git a/docs/frontend/cdn-fallback-strategy.md b/docs/frontend/cdn-fallback-strategy.md new file mode 100644 index 00000000..591c2969 --- /dev/null +++ b/docs/frontend/cdn-fallback-strategy.md @@ -0,0 +1,375 @@ +# 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)