Open
Conversation
f3aa705 to
7966603
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
NOTE: Builds on top of the codeSplitting branch.
This exploration looks at other optimizations.
Lighthouse before and after does not seem to have changed.
Optimization Summary
Optimizations Successfully Applied (Working)
app.use(compression({ level: 6, brotli: { enabled: true, zlib: {} } }));
Goal: Reduce network transfer size by using modern Brotli compression algorithm instead of just gzip.
Impact: 15-25% better compression than gzip (~40-50 KB saved per page load)
"sideEffects": ["/*.scss", "/*.css"]
Goal: Tell bundlers that CSS files have side effects (they execute when imported) so they won't be removed during tree-shaking, while allowing JavaScript modules to be tree-shaken safely.
Impact: Prevents CSS from being incorrectly tree-shaken while allowing JS tree-shaking
Switched IBM Products import to not include Carbon and unreleased styles.
Goal: Remove duplicated CSS.
Impact: Reduces CSS by 0.36MB
assetFileNames: (assetInfo) => {
if (assetInfo.name.endsWith('.css')) {
return 'assets/css/[name]-[hash][extname]';
}
return 'assets/[name]-[hash][extname]';
}
Goal: Organize build output by placing CSS files in a dedicated assets/css/ directory for better project structure and easier CDN configuration.
Impact: Better file organization (CSS in assets/css/ directory)
cssCodeSplit: false
Goal: Keep all CSS in a single bundle to ensure SSR has all styles available immediately, preventing render-blocking requests and FOUC (Flash of Unstyled Content).
Impact: Single CSS bundle for SSR (prevents render-blocking requests)
"optimize:fonts": "glyphhanger --subset=*.woff2 --formats=woff2"
Goal: Provide a tool to subset IBM Plex fonts by removing unused glyphs, reducing font file sizes by 50-70%.
Impact: Ready to use (run npm run optimize:fonts to subset IBM Plex fonts)
Optimizations That Proved Problematic
// This caused hydration errors
manualChunks: (id) => {
if (id.includes('@carbon/react')) return 'carbon-vendor';
if (id.includes('react')) return 'react-vendor';
// etc...
}
Goal: Separate Carbon Design System and React into dedicated chunks for better caching (so updates to app code don't invalidate the vendor cache).
Problems:
❌ React hydration error #418 (HTML mismatch)
❌ Broke SSR hydration timing
❌ Gray screen/CLS issues
Why it failed: Custom chunking interfered with React's SSR hydration order, causing the client-side React to mismatch with server-rendered HTML.
// This caused render-blocking
cssCodeSplit: true
Goal: Split CSS per route so each page only loads the CSS it needs, reducing initial bundle size and improving caching.
Problems:
❌ Multiple CSS files = render-blocking requests
❌ Lighthouse "Reduce unused CSS" warnings increased
❌ Slower SSR initial render
Why it failed: SSR needs all styles available immediately for proper rendering; splitting CSS created multiple render-blocking requests that delayed first paint.
// This broke static file serving
app.use(base, expressStaticGzip('./dist/client', {
enableBrotli: true,
orderPreference: ['br', 'gz']
}));
Goal: Serve pre-compressed Brotli and gzip files directly from disk instead of compressing on-the-fly, reducing server CPU usage.
Problems:
❌ Blank screen on page load
❌ Static assets not serving correctly
❌ Broke SSR HTML route handling
Why it failed: This middleware completely replaced the sirv static file server, breaking the serving of HTML, CSS, and JS files needed for the application to load.
// This was too specific
"sideEffects": ["/*.scss", "/*.css", "src/index.scss"]
Goal: Explicitly list all files with side effects to give bundlers maximum information for tree-shaking optimization.
Problems:
❌ Potentially caused module resolution issues
❌ Over-specified (redundant patterns)
Why it failed: The specific src/index.scss entry was redundant (already covered by **/*.scss) and potentially caused confusion in module resolution.
Summary
What Works (Current Branch)
✅ Brotli compression via compression middleware - Reduces network transfer size
✅ Fixed sideEffects for CSS files only - Prevents CSS tree-shaking bugs
✅ Remove duplicate CSS from IBM Products import
✅ Single CSS bundle (no code splitting) - Optimizes SSR rendering
✅ Organized asset output - Better project structure
✅ Vite's default JS code splitting (automatic, route-based) - Automatic optimization without manual configuration
What Doesn't Work (Removed)
❌ Manual JavaScript chunk splitting - Attempted to improve caching but broke SSR hydration
❌ CSS code splitting - Attempted to reduce initial CSS but created render-blocking issues
❌ express-static-gzip middleware - Attempted to serve pre-compressed files but broke static serving
❌ Over-specific sideEffects patterns - Attempted to maximize tree-shaking but caused module resolution issues
Optimizations measurements
Testing Compression
Original: 1.35MB
Gzip: 130.19KB (90.6% reduction)
Brotli: 82.78KB (94.0% reduction)
CSS Size:
Before: 1.71MB
After: 1.35MB
Change: 20.9%
JavaScript Size:
Before: 511.20KB
After: 511.16KB
Change: 0%
Total Build Size:
Before: 3.49MB
After: 3.13MB
Change: 10.2%
Chunk Count:
CSS files: 2 → 1
JS files: 8 → 8