Add comprehensive documentation for CDN fallback strategy used across the platform's frontend. Documents the pattern for loading external libraries (Alpine.js, Tailwind CSS, etc.) with automatic fallback to local copies when CDN is unavailable. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
11 KiB
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:
<!-- Tailwind CSS with CDN fallback -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css"
onerror="this.onerror=null; this.href='{{ url_for('static', path='shared/css/tailwind.min.css') }}';">
How it works:
- Browser attempts to load Tailwind CSS from CDN
- If CDN fails (network error, timeout, 404),
onerrorevent fires - Handler sets
onerror=nullto prevent infinite loops - Handler updates
hrefto local copy path - Browser automatically loads local copy
Alpine.js Fallback
Alpine.js uses dynamic script loading with error handling:
<!-- Alpine.js with CDN fallback -->
<script>
(function() {
var script = document.createElement('script');
script.defer = true;
script.src = 'https://cdn.jsdelivr.net/npm/alpinejs@3.13.3/dist/cdn.min.js';
script.onerror = function() {
console.warn('Alpine.js CDN failed, loading local copy...');
var fallbackScript = document.createElement('script');
fallbackScript.defer = true;
fallbackScript.src = '{{ url_for("static", path="shared/js/vendor/alpine.min.js") }}';
document.head.appendChild(fallbackScript);
};
document.head.appendChild(script);
})();
</script>
How it works:
- IIFE creates a script element dynamically
- Sets
deferattribute to match CDN loading behavior - Attempts to load Alpine.js from CDN
- If CDN fails,
onerrorhandler triggers - Logs warning to console for debugging
- Creates new script element pointing to local copy
- Appends fallback script to
<head>
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
{# Lines 41-42: Tailwind CSS fallback #}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css"
onerror="this.onerror=null; this.href='{{ url_for('static', path='shared/css/tailwind.min.css') }}';">
{# Lines 247-263: Alpine.js fallback #}
<script>
(function() {
var script = document.createElement('script');
script.defer = true;
script.src = 'https://cdn.jsdelivr.net/npm/alpinejs@3.13.3/dist/cdn.min.js';
script.onerror = function() {
console.warn('Alpine.js CDN failed, loading local copy...');
var fallbackScript = document.createElement('script');
fallbackScript.defer = true;
fallbackScript.src = '{{ url_for("static", path="shared/js/vendor/alpine.min.js") }}';
document.head.appendChild(fallbackScript);
};
document.head.appendChild(script);
})();
</script>
Vendor Frontend
Template: app/templates/vendor/base.html
Same pattern as Shop frontend, with vendor-specific Tailwind overrides loaded after the base:
{# Lines 13-14: Tailwind CSS fallback #}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css"
onerror="this.onerror=null; this.href='{{ url_for('static', path='shared/css/tailwind.min.css') }}';">
{# Line 17: Vendor-specific overrides #}
<link rel="stylesheet" href="{{ url_for('static', path='vendor/css/tailwind.output.css') }}" />
{# Lines 62-78: Alpine.js fallback #}
<!-- Same Alpine.js fallback pattern as Shop -->
Admin Frontend
Template: app/templates/admin/base.html
Same pattern as Vendor frontend, with admin-specific Tailwind overrides:
{# Lines 13-14: Tailwind CSS fallback #}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css"
onerror="this.onerror=null; this.href='{{ url_for('static', path='shared/css/tailwind.min.css') }}';">
{# Line 17: Admin-specific overrides #}
<link rel="stylesheet" href="{{ url_for('static', path='admin/css/tailwind.output.css') }}" />
{# Lines 62-78: Alpine.js fallback #}
<!-- Same Alpine.js fallback pattern as Shop -->
Development Setup
Verifying Local Assets
Check that local fallback files exist:
# 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:
-
Start the application:
uvicorn app.main:app --reload -
Simulate CDN failure:
- Use browser DevTools Network tab
- Add blocking rule for
cdn.jsdelivr.net - Or disconnect from internet
-
Check console output:
⚠️ Alpine.js CDN failed, loading local copy... -
Verify fallback loaded:
- Open DevTools Network tab
- Check that
/static/shared/js/vendor/alpine.min.jswas loaded - Check that
/static/shared/css/tailwind.min.csswas loaded
Updating Local Assets
To update Tailwind CSS to a newer version:
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:
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
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:
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:
- Check static file mounting configuration
- Verify file paths in browser Network tab
- Check file permissions:
chmod 644 static/shared/css/tailwind.min.css - Verify FastAPI StaticFiles mount point
Issue: Fallback triggers unnecessarily
Symptoms:
- Console warnings in normal operation
- Inconsistent CDN loading
Solutions:
- Check network stability
- Verify CDN URLs are correct and accessible
- Check for firewall/proxy blocking CDN
- 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:
- Reduce CDN timeout (browser-dependent)
- Consider Content Security Policy (CSP) with shorter timeouts
- 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
-
For production with reliable CDN access:
- Keep current implementation
- Monitor CDN uptime
-
For corporate/restricted networks:
- Consider removing CDN URLs entirely
- Load local assets directly
-
For air-gapped deployments:
- Remove CDN URLs completely
- Update templates to use local-only:
<!-- Local-only mode (no CDN) -->
<link rel="stylesheet" href="{{ url_for('static', path='shared/css/tailwind.min.css') }}">
<script defer src="{{ url_for('static', path='shared/js/vendor/alpine.min.js') }}"></script>
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:
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css"
integrity="sha384-..."
crossorigin="anonymous"
onerror="...">
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:
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.