Skip to main content

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:

MetricTargetCritical
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:

MetricTargetCritical
Compressed Bundle< 500KBPreferred
Initial Load< 4s (3G)No (cached)
Code SplittingYes✅ Required
Lazy LoadingYes✅ 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

Implementation detail

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"
In-memory cache reload

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

Implementation detail

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.pwa owns 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.pwa should not overwrite /sw.js unless 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:

Implementation detail

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:

  1. Files exist in wwwroot/
  2. FileCacheMiddleware loaded files
  3. Check logs: "Loaded X static files into cache"
  4. 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-compat for 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)

  1. ✅ Keep bundle < 200KB (STRICT)
  2. ✅ Use Preact, not React
  3. ✅ Minimal dependencies
  4. ✅ No lazy loading (bundle is small enough)
  5. ✅ Optimize images aggressively
  6. ✅ Use SVG for icons
  7. ✅ Avoid large libraries

For Module PWAs (React)

  1. ✅ Code split by route
  2. ✅ Lazy load components
  3. ✅ Use React 19 features (Suspense, use(), etc.)
  4. ✅ Optimize images
  5. ✅ Use tree-shakeable libraries
  6. ✅ Monitor bundle size
  7. ✅ Follow Feature-Sliced Design