PWA Hosting
AuthProxy serves as a high-performance web server for Progressive Web Applications with two distinct PWA architectures optimized for different use cases.
Two PWA Variants
ItBuild uses different technologies for different modules based on performance requirements:
1. AuthProxy PWA (Preact-based) - Size Critical
Purpose: Authentication forms - the FIRST page users see.
Why Preact?: AuthProxy determines entry point based on session. First load performance is CRITICAL for user experience. Bundle size directly impacts user impression.
Tech Stack:
- Preact 10.25 (3KB vs React 40KB)
- TypeScript 5.7
- Vite 6.2
- preact-iso (routing)
- Context API (state management)
- Bundle size target: < 200KB compressed (STRICT!)
Repository: /apg.pwa (separate Git repo)
Build Process:
cd apg.pwa
npm install
npm run PRD
# Output: dist/ → Copy to AuthProxy/wwwroot/
Deployment:
apg.pwa/dist/
├── index.html
├── assets/
│ ├── index-abc123.js # < 200KB total
│ ├── index-def456.css
│ └── logo.svg
└── manifest.webmanifest
↓ Copy to ↓
AuthProxy/wwwroot/
├── index.html
├── assets/
└── manifest.webmanifest
Performance Requirements:
| Metric | Target | Critical |
|---|---|---|
| Compressed Bundle | < 200KB | ✅ STRICT |
| Initial Load | < 2s (3G) | ✅ Yes |
| First Contentful Paint | < 1.5s | ✅ Yes |
| Time to Interactive | < 3s | ✅ Yes |
Why So Strict?
- Auth page is first user interaction
- Slow load = bad first impression
- Mobile users on slow networks
- Competitive advantage
2. Other Modules PWA (React-based) - Feature Rich
Purpose: TrexWallet, Chat, CRM - NOT first load, richer functionality needed.
Why React?: Larger ecosystem, more libraries available, bundle size less critical since user already logged in.
Tech Stack:
- React 19
- TypeScript 5.4+
- Vite 6
- React Router DOM (routing)
- Zustand (state management)
- Tailwind CSS + Emotion
- @headlessui/react
- Bundle size target: < 500KB compressed
- Architecture: Feature-Sliced Design (FSD)
Repositories:
/trexwallet.pwa(TrexWallet frontend)/chat.pwa(Chat frontend)/crm.pwa(CRM frontend)
Build Process:
cd trexwallet.pwa
npm install
npm run PRD
# Output: dist/ → Copy to AuthProxy/wwwroot/wallet/
Deployment:
trexwallet.pwa/dist/
├── index.html
├── assets/
│ ├── index-xyz789.js # < 500KB total
│ ├── index-uvw012.css
│ └── icons/
└── manifest.webmanifest
↓ Copy to ↓
AuthProxy/wwwroot/wallet/
├── index.html
├── assets/
└── manifest.webmanifest
Performance Requirements:
| Metric | Target | Critical |
|---|---|---|
| Compressed Bundle | < 500KB | Preferred |
| Initial Load | < 4s (3G) | No (cached) |
| Code Splitting | Yes | ✅ Required |
| Lazy Loading | Yes | ✅ Required |
Static File Serving
AuthProxy loads all PWA frontends into memory cache for optimal performance:
wwwroot Structure
AuthProxy/wwwroot/
├── index.html # AuthProxy PWA (Preact)
├── assets/
│ ├── index-abc123.js
│ └── index-def456.css
├── manifest.webmanifest
├── sw.js # Service worker
├── wallet/ # TrexWallet PWA (React)
│ ├── index.html
│ ├── assets/
│ └── manifest.webmanifest
├── chat/ # Chat PWA (React)
│ ├── index.html
│ ├── assets/
│ └── manifest.webmanifest
└── crm/ # CRM PWA (React)
├── index.html
├── assets/
└── manifest.webmanifest
Memory Caching
AuthProxy's FileCacheMiddleware loads all static files into memory on startup:
Features:
- Brotli compression (priority)
- Gzip compression (fallback)
- ETag generation for cache validation
- Content-Type detection
- Immutable cache headers for fingerprinted assets
Code Reference: Middleware/FileCacheMiddleware.cs
Source-level examples and database statements are maintained in the module repositories. This public page describes the operational contract and configuration intent.
Cache Headers Strategy
Different cache strategies for different file types:
HTML files (app shell — instant from cache, revalidated in background):
Cache-Control: public, max-age=1, stale-while-revalidate=604800
Fingerprinted JavaScript/CSS (hash in filename — immutable, 1 year cache):
Cache-Control: public, max-age=31536000
Content-Encoding: br
Invalidated by changing the filename hash, never by header expiry.
Customer-overridable assets (per-module settings.js and theme/branding CSS):
Cache-Control: public, max-age=0, stale-while-revalidate=86400
ETag: "def456"
Files a customer overrides (authproxy-settings.js, trexwallet-settings.js, chat-settings.js, itbuild-settings.js, and theme CSS such as authproxy.css, authdark.css, chat.css, trex.css, init_trex_logo.css, init_logo.css) revalidate via ETag instead of being cached immutable. After the in-memory cache reloads the file, the browser picks up a new ETag without bumping a ?vN query in the HTML shell. Matched by a static file-name set in FileCacheMiddleware.IsRevalidateAsset.
Images/Fonts (7 days fresh, then revalidate window):
Cache-Control: public, max-age=604800, stale-while-revalidate=2592000
ETag: "ghi789"
StaticFileCacheService loads all wwwroot files into RAM at startup. Every ~100 seconds the LoadSettings background loop calls SyncChangedFiles() — compares an on-disk index (LastWriteTimeUtc, Length), reloads new/changed files (including Brotli/Gzip variants in Production), and evicts deleted paths. HTTP serving is not blocked during sync. For immediate effect use docker restart; otherwise allow up to ~100 seconds after docker cp or a volume edit.
Routing to PWA
AuthProxy determines which PWA to serve based on session state:
Entry Point Logic
Source-level examples and database statements are maintained in the module repositories. This public page describes the operational contract and configuration intent.
URL Structure
Unauthenticated:
https://app.customer.com/
→ Serves: /wwwroot/index.html (AuthProxy PWA - Preact)
→ Shows: Login form
Authenticated:
https://app.customer.com/
→ Redirects to: /wallet or /core (based on user settings)
https://app.customer.com/wallet
→ Serves: /wwwroot/wallet/index.html (TrexWallet PWA - React)
https://app.customer.com/chat
→ Serves: /wwwroot/chat/index.html (Chat PWA - React)
Service Worker Support
Service Worker Registration
In a flat-merge AuthProxy deployment, all module PWAs share one root wwwroot/. That means the root service worker path /sw.js must have one canonical owner.
Current platform convention:
apg.pwaowns the canonical root worker file- module PWAs such as wallet and chat register
/sw.js, but should not ship competing root worker implementations - customer
Core.pwashould not overwrite/sw.jsunless the deployment intentionally replaces the platform worker contract
Each PWA still registers the same root service worker:
AuthProxy PWA (Preact):
// apg.pwa/src/index.tsx
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js', { scope: '/' })
.then(reg => console.log('SW registered:', reg))
.catch(err => console.error('SW registration failed:', err));
}
Module PWA (React, shared root worker):
// trexwallet.pwa/src/index.tsx
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js', { scope: '/' })
.then(reg => console.log('Root SW registered:', reg))
.catch(err => console.error('Root SW registration failed:', err));
}
Caching Strategy
App Shell (Cache First):
// Service worker
const CACHE_NAME = 'app-v1.2.0';
const APP_SHELL = [
'/index.html',
'/assets/index-abc123.js',
'/assets/index-def456.css'
];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME).then(cache => cache.addAll(APP_SHELL))
);
});
self.addEventListener('fetch', event => {
if (event.request.destination === 'document') {
// Network first for HTML (to get updates)
event.respondWith(
fetch(event.request).catch(() => caches.match(event.request))
);
} else {
// Cache first for assets
event.respondWith(
caches.match(event.request).then(response =>
response || fetch(event.request)
)
);
}
});
PWA Manifest
Each PWA has its own manifest:
AuthProxy PWA:
{
"name": "ItBuild Auth",
"short_name": "Auth",
"description": "ItBuild Authentication",
"start_url": "/",
"display": "standalone",
"theme_color": "#1976d2",
"background_color": "#ffffff",
"icons": [
{
"src": "/icons/icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icons/icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
TrexWallet PWA:
{
"name": "TrexWallet",
"short_name": "Wallet",
"description": "Cryptocurrency Wallet",
"start_url": "/wallet/",
"scope": "/wallet/",
"display": "standalone",
"theme_color": "#4caf50",
"background_color": "#ffffff",
"icons": [...]
}
Build Process
Development Build
AuthProxy PWA:
cd apg.pwa
npm run dev
# Vite dev server at http://localhost:5173
TrexWallet PWA:
cd trexwallet.pwa
npm run dev
# Vite dev server at http://localhost:5174
Production Build
AuthProxy PWA:
cd apg.pwa
# Build with strict size limit
npm run PRD
# Output analysis
npm run analyze
# Check bundle size
ls -lh dist/assets/*.js
# Must be < 200KB compressed!
TrexWallet PWA:
cd trexwallet.pwa
# Build
npm run PRD
# Output analysis
npm run analyze
# Check bundle size
ls -lh dist/assets/*.js
# Target: < 500KB compressed
Deployment to AuthProxy
Manual (development):
# Build PWA
cd apg.pwa
npm run PRD
# Copy to AuthProxy
rm -rf ../AuthProxy/wwwroot/*
cp -r dist/* ../AuthProxy/wwwroot/
# Restart AuthProxy to reload cache
cd ../AuthProxy
dotnet run
Automated (CI/CD):
# In ItBuild platform build pipeline
# 1. Build AuthProxy PWA
cd /repos/apg.pwa
npm ci
npm run PRD
# 2. Build TrexWallet PWA
cd /repos/trexwallet.pwa
npm ci
npm run PRD
# 3. Build AuthProxy module
cd /repos/AuthProxy
dotnet publish -c Release
# 4. Copy PWAs to wwwroot
cp -r /repos/apg.pwa/dist/* bin/Release/net10.0/publish/wwwroot/
cp -r /repos/trexwallet.pwa/dist/* bin/Release/net10.0/publish/wwwroot/wallet/
# 5. Copy binaries to project directory
cp -r bin/Release/net10.0/publish/* /projects/123/AuthProxy/1.2.0/
Performance Optimization
Bundle Size Optimization
For AuthProxy PWA (Preact):
// vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: undefined // No chunking, keep it small
}
},
target: 'es2020',
minify: 'esbuild',
cssMinify: 'lightningcss',
// CRITICAL: Monitor bundle size
chunkSizeWarningLimit: 200 // 200KB limit
},
esbuild: {
legalComments: 'none',
treeShaking: true
}
});
For Module PWAs (React):
// vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
output: {
// Code splitting by route
manualChunks(id) {
if (id.includes('node_modules')) {
return 'vendor';
}
if (id.includes('/features/')) {
return id.split('/features/')[1].split('/')[0];
}
}
}
}
}
});
Compression
AuthProxy serves pre-compressed files:
Brotli (best compression, ~20-30% smaller):
brotli -q 11 dist/assets/*.js
Gzip (fallback for older browsers):
gzip -9 dist/assets/*.js
Middleware Selection:
Source-level examples and database statements are maintained in the module repositories. This public page describes the operational contract and configuration intent.
Monitoring
Bundle Size Monitoring
CI/CD Check:
# Fail build if AuthProxy PWA > 200KB
BUNDLE_SIZE=$(stat -f%z dist/assets/index-*.js)
if [ $BUNDLE_SIZE -gt 204800 ]; then
echo "ERROR: Bundle size $BUNDLE_SIZE exceeds 200KB limit!"
exit 1
fi
Performance Metrics
Lighthouse CI:
# lighthouserc.json
{
"ci": {
"assert": {
"assertions": {
"first-contentful-paint": ["error", {"maxNumericValue": 1500}],
"speed-index": ["error", {"maxNumericValue": 2000}],
"interactive": ["error", {"maxNumericValue": 3000}]
}
}
}
}
Troubleshooting
PWA Not Loading
Check:
- Files exist in
wwwroot/ - FileCacheMiddleware loaded files
- Check logs: "Loaded X static files into cache"
- Browser console for errors
Bundle Too Large
For AuthProxy PWA:
# Analyze bundle
npm run analyze
# Common causes:
- Unused imports
- Large libraries (use lighter alternatives)
- Images not optimized
- Source maps in production
Solutions:
- Use
preact-compatfor React libraries - Lazy load routes
- Use dynamic imports
- Optimize images (WebP, SVG)
Service Worker Not Updating
Force update:
navigator.serviceWorker.getRegistrations().then(registrations => {
registrations.forEach(reg => reg.unregister());
});
Check version:
// In service worker
const VERSION = '1.2.0';
console.log('SW version:', VERSION);
Best Practices
For AuthProxy PWA (Preact)
- ✅ Keep bundle < 200KB (STRICT)
- ✅ Use Preact, not React
- ✅ Minimal dependencies
- ✅ No lazy loading (bundle is small enough)
- ✅ Optimize images aggressively
- ✅ Use SVG for icons
- ✅ Avoid large libraries
For Module PWAs (React)
- ✅ Code split by route
- ✅ Lazy load components
- ✅ Use React 19 features (Suspense, use(), etc.)
- ✅ Optimize images
- ✅ Use tree-shakeable libraries
- ✅ Monitor bundle size
- ✅ Follow Feature-Sliced Design