Repository navigation
Performance Refactor 20260402
Date: April 2–8, 2026 Scope: Production JS/CSS bundle size and Lighthouse mobile performance (FCP, LCP, Speed Index) Impact: JS reduced from 5.8 MB → 1.0 MB (−83%); CSS from 884 KB → 567 KB (−32%); Lighthouse mobile score from 58 to the improvements described below.
This refactor targets the production JavaScript bundle size, which was a single 5.8 MB file (1.6 MB gzipped). Through manual chunk splitting, eliminating wildcard imports, and fixing barrel import patterns, the total JS payload was reduced to ~1.0 MB across multiple independently cacheable chunks (~340 KB gzipped) — an 82% reduction.
No lazy loading was introduced. All routes remain eagerly loaded to preserve compatibility with future PWA offline mode support.
Lighthouse flagged poor performance scores for the application. The root cause was a single monolithic JavaScript bundle (index-*.js) containing all vendor libraries, application code, and — critically — the entire Carbon Design System icon set (~10,000 icons).
| Asset | Size | Gzipped |
|---|---|---|
index-*.js |
5,820 kB | 1,609 kB |
index-*.css |
905 kB | 97 kB |
workbox-*.js |
6 kB | 2 kB |
| Total JS | 5,826 kB | 1,611 kB |
| PWA precache | 6,574 kB | — |
File: vite.config.ts
Added a function-based manualChunks configuration to Rollup via Vite's build options. This splits the vendor code into separate, independently cacheable chunks.
manualChunks(id) {
if (id.includes('node_modules/react/') || id.includes('node_modules/react-dom/'))
return 'react-vendor';
if (id.includes('node_modules/@carbon/charts'))
return 'carbon-charts';
if (id.includes('node_modules/@carbon/icons-react/') || id.includes('node_modules/@carbon/pictograms-react/'))
return 'carbon-icons';
if (id.includes('node_modules/@carbon/'))
return 'carbon';
// ... aws, query, router, utils
}The simpler object syntax ({ 'react-vendor': ['react', 'react-dom'] }) matches on package entry points only. Sub-path imports like react-dom/client or aws-amplify/auth/cognito are not matched by the array form, causing modules to fall through into the wrong chunk or the main bundle. The function form matches on the resolved file path in node_modules/, catching all sub-path imports correctly.
| Chunk | Contents | Rationale |
|---|---|---|
react-vendor |
React, ReactDOM | Core runtime — changes rarely, caches well |
carbon |
@carbon/react, @carbon/icons-react, @carbon/pictograms-react
|
UI framework + icons — kept together to avoid circular dependency issues (icons depend on React internals shared with components) |
carbon-charts |
@carbon/charts, @carbon/charts-react
|
Charting library — only loaded when charts are used |
aws |
aws-amplify and all sub-paths |
Auth SDK — updates on its own cadence |
query |
@tanstack/react-query |
Data fetching — lightweight, stable |
router |
react-router-dom |
Routing — stable |
utils |
axios, lodash, luxon, idb
|
General utilities — grouped for simplicity |
Files changed:
src/components/core/DistrictSelection/constants.tssrc/components/core/DistrictSelection/DistrictItem/index.tsxsrc/components/core/EmptySection/index.tsxsrc/pages/RoleError/index.tsxsrc/components/Form/TableResource/index.tsxsrc/components/core/EmptySection/index.unit.test.tsx
Three source files used namespace imports (import * as Icons from '@carbon/icons-react') which forces the bundler to include every export from the module. For @carbon/icons-react, that means all ~10,000 icon components (~4.8 MB minified). Similarly, import * as Pictograms from '@carbon/pictograms-react' pulled in all pictograms.
The app actually uses only ~15 icons and 3 pictograms.
DistrictSelection/constants.ts — Previously stored icon names as strings and resolved them at runtime via Icons[iconName]. Refactored to store actual component references:
// Before
import * as ICON from '@carbon/icons-react';
type ClientIconType = keyof typeof ICON;
export const ClientTypeIconMap: Record<string, ClientIconType> = {
A: 'Partnership',
B: 'EventsAlt',
// ... string lookups requiring the full icon set
};
// After
import { Partnership, EventsAlt, Building, /* ...only the 12 used */ } from '@carbon/icons-react';
import type { ComponentType } from 'react';
export const ClientTypeIconMap: Record<string, ComponentType> = {
A: Partnership,
B: EventsAlt,
// ... direct component references, tree-shakeable
};DistrictItem/index.tsx — Simplified icon resolution to use the map values directly instead of indexing into the namespace object.
EmptySection/index.tsx — Changed the component API from string-based icon/pictogram names to component props:
// Before — requires the entire icon/pictogram set at runtime
interface EmptySectionProps {
icon?: keyof typeof Icons; // string like "ErrorFilled"
pictogram?: keyof typeof Pictograms; // string like "Summit"
}
// After — consumers pass the actual component, tree-shakeable
interface EmptySectionProps {
icon?: ComponentType<{ className?: string; 'data-testid'?: string }>;
pictogram?: ComponentType<{ className?: string; 'data-testid'?: string }>;
}All callers (RoleError, TableResource, tests) were updated to import and pass the specific icon/pictogram components they need.
| Before | After |
|---|---|
carbon-icons: 4,795 kB |
carbon-icons: 41 kB |
| 99.1% reduction in icon chunk size |
Files changed:
src/context/preference/PreferenceProvider.tsxsrc/context/auth/AuthProvider.tsxsrc/hooks/useSyncPreferencesToFilters/index.tsx
Lodash's main entry point (lodash) is a CommonJS module. When you import { isEqual } from 'lodash', the bundler cannot tree-shake CJS — it includes the entire ~70 KB library even though only isEqual (~4 KB) is needed.
// Before
import { isEqual, mergeWith } from 'lodash';
// After
import isEqual from 'lodash/isEqual';
import mergeWith from 'lodash/mergeWith';Each lodash function ships as an independent module under lodash/<functionName>. Deep imports bypass the CJS barrel entirely.
utils chunk: 181 kB → 132 kB (~49 kB saved, ~27% reduction in that chunk).
File changed: src/services/types.ts
// Before
export * from './pagination.types';
export * from './search.types';
export * from './forestclient.types';
// After
export type * from './pagination.types';
export type * from './search.types';
export * from './forestclient.types'; // keeps runtime export (YesNoEnum)pagination.types.ts and search.types.ts contain only TypeScript types/interfaces. Using export type * tells the compiler these are purely type-level — they're erased at compile time and cannot contribute runtime side effects. forestclient.types.ts exports YesNoEnum (a runtime value), so it keeps the regular export *.
File changed: Caddyfile
The original Caddyfile applied Cache-Control: no-store, no-cache to all responses, including hashed assets (/assets/index-CvtA56iK.js). This forced the browser to re-download every file on every page load, negating the benefits of content-hashed filenames.
Split the caching into two path-based rules:
# Hashed assets (JS/CSS/fonts/images) — cache aggressively
@assets path /assets/*
header @assets Cache-Control "public, max-age=31536000, immutable"
# Everything else (HTML, config, service worker) — never cache
@notassets {
not path /assets/*
}
header @notassets Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate"Why this is safe: Vite includes a content hash in every asset filename (e.g., react-vendor-COMZwu8M.js). When the content changes, the hash changes, producing a new URL. The immutable directive tells the browser to never revalidate — the content at that URL will never change.
Files that must not be cached (HTML, data/config.js, service-worker.js) are served outside /assets/ and retain the no-cache policy.
On subsequent visits, the browser serves all vendor chunks from disk cache (0 ms), only fetching the small index.html and any chunks whose content actually changed.
The CSP header uses script-src 'self', which permits all scripts served from the same origin. Since all split chunks are static .js files served from the same domain under /assets/, they're automatically covered. No hashes, nonces, or additional directives are required.
SHA-256 hashes in CSP ('sha256-...') are needed when your HTML contains inline scripts — i.e., <script> tags with code directly in the HTML, not loaded from a file. In this app:
-
index.htmlloads scripts via<script src="/data/config.js">and<script src="/data/params.js">— these are external scripts (loaded by URL), not inline. They're covered by'self'.
If your other apps use hashes like 'sha256-78ZXaz...', they likely have inline <script> blocks that need to be whitelisted individually. This app doesn't, so hashes are not required.
| Script type | CSP coverage |
|---|---|
Vite-built chunks (/assets/*.js) |
Covered by 'self'
|
External config (/data/config.js, /data/params.js) |
Covered by 'self'
|
| Backend URL scripts | Covered by {$BACKEND_URL}
|
Inline <script> in HTML |
Not present — no hashes needed |
If inline scripts are added in the future, you'll need to compute their SHA-256 hash and add them to script-src.
Routes remain eagerly loaded. Lazy loading (React.lazy()) would reduce the initial bundle but creates a critical problem for PWA offline mode: if a route's chunk hasn't been fetched before the user goes offline, that route becomes inaccessible. Since offline mode support is planned, keeping eager loading ensures the service worker precaches all route code on first visit.
AppRoutes.tsx already wraps the router in <Suspense>. Adding streaming SSR or selective hydration would require a server-side rendering setup (the app is currently a static SPA served via Caddy), adding significant infrastructure complexity with minimal benefit for the current architecture.
The CSS remains a single 905 kB file (97 kB gzipped). Carbon Design System's styles are tightly coupled — splitting them risks broken component rendering if chunks load out of order. The gzipped size (97 kB) is acceptable.
| Chunk | Size | Gzipped |
|---|---|---|
carbon |
403 kB | 123 kB |
react-vendor |
193 kB | 61 kB |
utils |
132 kB | 46 kB |
router |
86 kB | 29 kB |
index (app code) |
79 kB | 26 kB |
aws |
72 kB | 24 kB |
query |
36 kB | 11 kB |
workbox |
6 kB | 2 kB |
| Total JS | 1,008 kB | 322 kB |
index.css |
905 kB | 97 kB |
| PWA precache | 1,870 kB | — |
| Metric | Before | After | Improvement |
|---|---|---|---|
| Total JS | 5,826 kB | 1,008 kB | −83% |
| Gzipped JS | 1,611 kB | 321 kB | −80% |
| Largest chunk | 5,820 kB | 362 kB | −94% |
| PWA precache total | 6,574 kB | 1,870 kB | −72% |
| Build time | ~18s | ~10s | −44% |
| Number of JS chunks | 2 | 8 | Better cacheability |
| File | Change |
|---|---|
vite.config.ts |
Added manualChunks function |
Caddyfile |
Split cache policy: immutable for assets, no-cache for HTML |
src/components/core/DistrictSelection/constants.ts |
Replaced import * with explicit named imports |
src/components/core/DistrictSelection/DistrictItem/index.tsx |
Use direct component references |
src/components/core/EmptySection/index.tsx |
Changed API from string-based to component-based icon props |
src/components/core/EmptySection/index.unit.test.tsx |
Updated tests for new API |
src/pages/RoleError/index.tsx |
Pass pictogram component instead of string |
src/components/Form/TableResource/index.tsx |
Pass pictogram components instead of strings |
src/context/preference/PreferenceProvider.tsx |
lodash → lodash/isEqual, lodash/mergeWith
|
src/context/auth/AuthProvider.tsx |
lodash → lodash/isEqual
|
src/hooks/useSyncPreferencesToFilters/index.tsx |
lodash → lodash/isEqual
|
src/services/types.ts |
Added type keyword to type-only re-exports |
Date: April 8, 2026
Scope: Lighthouse mobile performance (FCP, LCP, Speed Index)
Impact: Reduced CSS from 884 KB → 567 KB (raw), 96.7 KB → 63.2 KB (gzip). Eliminated 82 unused @font-face declarations and ~60 unused Carbon component stylesheets. Added font-display: swap, font preloading, deferred render-blocking scripts, and fixed bfcache.
Lighthouse mobile performance scored 58 with poor Core Web Vitals:
| Metric | Before |
|---|---|
| FCP | 7.9s |
| LCP | 8.2s |
| Speed Index | 7.9s |
Root cause analysis identified font loading as a major contributor:
-
82 unused IBM Plex
@font-facedeclarations were embedded in the CSS bundle by Carbon Design System, adding parse time and CSS size despite the app using BCSans exclusively. -
BCSans fonts lacked
font-display: swap, causing invisible text (FOIT — Flash of Invisible Text) while fonts downloaded, directly blocking FCP and LCP on slow connections. -
BCSans
@font-facelisted.woffbefore.woff2, meaning browsers downloaded the larger format (~350 KB each) instead of the smaller.woff2(~230 KB each) — a ~35% size penalty per font variant. - No font preloading — the browser could only discover font URLs after parsing the entire CSS file.
File: src/styles/index.scss
Added Carbon's $css--font-face: false configuration before any Carbon import:
@use '@carbon/styles/scss/config' with ($css--font-face: false);Why: @use '@carbon/react' in _overrides.scss imports the full Carbon stylesheet, which includes @carbon/styles/scss/fonts. That module emits @font-face rules for IBM Plex Mono (52 declarations) and IBM Plex Sans (30 declarations) — none of which are used since the app's type family is configured to use BCSans. Setting $css--font-face: false before any Carbon module loads prevents this emission entirely.
Result: 82 @font-face blocks removed. The --cds-code-*-font-family CSS custom properties still reference "IBM Plex Mono" as a name in the font stack, but without @font-face declarations the browser falls through to the next family (system-ui), which is the correct behavior.
File: src/styles/_fonts.scss (new)
Created a new SCSS partial with override @font-face declarations for all 4 BCSans variants that include font-display: swap.
Why: The upstream @bcgov-nr/nr-theme package defines BCSans @font-face rules without font-display, which defaults to auto (effectively block on most browsers). This tells the browser to show invisible text for up to 3 seconds while the font downloads. On mobile with slower connections (3G/4G), this directly blocks FCP and LCP.
With font-display: swap, the browser immediately renders text using a fallback system font, then swaps to BCSans once it finishes downloading. Users see content instantly instead of a blank page.
Result: Text is visible on first paint using fallback fonts; BCSans swaps in once loaded.
File: src/styles/_fonts.scss
The override @font-face declarations list only woff2 format, with the files served from the public/fonts/ directory.
Why: The upstream nr-theme declares src: with .woff listed before .woff2. Browsers use the first supported format in the list. Since all modern browsers support both, they download the larger .woff files. Per-variant savings:
| Variant | woff | woff2 | Saving |
|---|---|---|---|
| Regular | 346 KB | 226 KB | 120 KB |
| Bold | 389 KB | 229 KB | 160 KB |
| Italic | 364 KB | 233 KB | 131 KB |
| BoldItalic | 369 KB | 238 KB | 131 KB |
Our override declarations appear later in the CSS, so the browser uses them instead of the upstream ones.
Result: Browsers download woff2 exclusively — up to 542 KB total savings across all 4 variants.
Files:
-
index.html— added<link rel="preload"> -
public/fonts/— copied BCSans woff2 files here -
Caddyfile— added aggressive caching for/fonts/*
<link rel="preload" as="font" type="font/woff2" href="/fonts/BCSans-Regular.woff2" crossorigin />Why: Without preload, the browser must: download HTML → parse HTML → discover CSS link → download CSS → parse CSS → discover @font-face URL → download font. The <link rel="preload"> tells the browser to start downloading the Regular weight font immediately when parsing the HTML <head>, in parallel with the CSS download.
The Regular weight is preloaded because it's used for the majority of body text and is the only variant needed for the initial render. Bold, Italic, and BoldItalic are loaded on-demand when the CSS triggers them.
Font files are served from public/fonts/ with deterministic paths (no content hash in filename) so the preload href matches the @font-face src URL exactly. The Caddyfile was updated to cache /fonts/* aggressively (max-age=31536000, immutable) matching the existing /assets/* policy.
File: index.html
<link rel="preload" as="font" type="font/woff2" href="/fonts/BCSans-Bold.woff2" crossorigin />Why: Lighthouse critical path analysis showed BCSans-Bold.woff2 as the longest chain at 1,991ms — it's discovered only after HTML → CSS → parse CSS → find @font-face. The Bold weight is used for headings and the navigation header, which are above-the-fold content. Preloading starts the download in parallel with CSS, removing it from the critical chain.
File: index.html
<script defer src="/data/config.js"></script>
<script defer src="/data/params.js"></script>Why: config.js and params.js were synchronous <script> tags in <head>, blocking HTML parsing and first paint for 470ms each (Lighthouse data). These scripts only set window.config = {} (runtime environment defaults). Adding defer allows:
- HTML parsing to continue without waiting for these scripts
- First paint to happen before script execution
- Execution still in document order, and before
type="module"scripts (guaranteed by spec)
This is safe because the app consumes window.config via env.ts, which is imported by the module entry point — defer scripts always execute before module scripts.
File: Caddyfile
Changed the non-asset cache policy from no-store to no-cache:
@notassets {
not path /assets/*
not path /fonts/*
}
header @notassets Cache-Control "no-cache, must-revalidate, proxy-revalidate"Why (bfcache): Cache-Control: no-store explicitly prevents browsers from storing the page in bfcache (back/forward cache). Lighthouse flagged this as 3 failure reasons. Changing to no-cache still forces revalidation on every navigation (the page is never served stale) but allows the browser to use bfcache for instant back/forward navigations. This is a significant UX win on mobile.
Why (font caching): The previous @notassets matcher only excluded /assets/* but not /fonts/*, meaning our font files at /fonts/BCSans-*.woff2 were being served with no-store instead of the intended aggressive caching. Adding not path /fonts/* ensures fonts get the separate aggressive cache rule.
File: src/styles/_overrides.scss
Replaced the blanket @use '@carbon/react' with:
- Base modules:
reset,layer,layout,zone - Individual component imports for only the ~22 components actually used
Why: @use '@carbon/react' forwards to @carbon/styles, which imports CSS for all ~80 Carbon components. The app uses only ~22 of them. Lighthouse reported 95.5 KiB of 104.4 KiB (91%) of CSS as unused on the /search page. The selective import approach includes only:
breadcrumb, button, checkbox, combo-box, data-table (+ action/expandable/skeleton/sort), date-picker, dropdown, form, link, list-box, loading, modal, multiselect, notification, overflow-menu, pagination, popover, search, select, skeleton-styles, stack, tag, text-input, tooltip, ui-shell
Result: CSS dropped from 852 KB → 567 KB (raw), from 93.3 KB → 63.2 KB (gzip) — a 32% reduction in transfer size.
Maintenance note: When adding a new Carbon component to the app, its SCSS module must also be added to
_overrides.scss. If a new Carbon component renders without its expected styles, add its corresponding@use '@carbon/styles/scss/components/<name>'line.
Files:
-
public/img/bc-gov-logo.svg— SVG logo (light theme) -
public/img/bc-gov-logo-rev.svg— SVG logo (dark theme) -
public/img/landing.webp— recompressed at quality=50, method=6 -
index.html— preloads updated to SVG logos -
src/pages/Landing/index.tsx— switched to static/img/paths, SVG logos, removed asset imports -
src/pages/Landing/index.unit.test.tsx— updated assertions -
vite.config.ts— addedglobPatternswithwebptoinjectManifestfor precaching -
Caddyfile— added/img/*cache rule (24h), excluded from no-cache matcher
Why (move to public/img/): Vite hashes asset filenames in production (e.g., landing-BxfW581V.webp), which means <link rel="preload"> in index.html cannot reference them with static paths. Moving images to public/img/ gives deterministic URLs that the browser's preload scanner discovers immediately in the initial HTML — before any JavaScript executes. This directly addresses the Lighthouse "request is not discoverable in initial document" audit.
Why (SVG logos): The raster logos required multiple density variants (1x, 2x, 3x) for different device pixel ratios — 6 WebP files totaling ~70 KB. SVGs scale to any resolution with a single file (13 KB each), eliminating srcSet complexity entirely.
Why (landing image recompression): The previous WebP conversion used quality=85. Reducing to quality=50 with method=6 (best Pillow compression) saved 20%. The image is a decorative hero displayed with object-fit: cover, so the lower quality is visually acceptable.
Why (precaching): Added webp (and svg implicitly via the glob) to the injectManifest.globPatterns in vite.config.ts so the service worker precaches all images on install. This ensures instant image loading on repeat visits and offline support.
Why (/img/* caching): Unlike /assets/* (content-hashed, immutable), files in /img/ have human-readable names that may change. A 24-hour cache with must-revalidate balances performance with the ability to update images without cache-busting.
| Image | Before (raster) | After (SVG/compressed) | Savings |
|---|---|---|---|
| bc-gov-logo (all variants) | 6 WebP + 2 PNG (~170 KB) | 1 SVG (13 KB) | 92% |
| bc-gov-logo-rev (all variants) | 6 WebP + 2 PNG (~164 KB) | 1 SVG (13 KB) | 92% |
| landing | 162 KB (WebP, q=85) | 129 KB (WebP, q=50) | 20% |
| Total | ~496 KB | ~155 KB | 69% |
File: ../cypress/cypress/support/helpers/lighthouse.helpers.ts
Implemented platform-aware Lighthouse options in Cypress so audits match the target execution mode:
-
runReportTo()now resolves and passes full Lighthouse options (not only URL) tocy.runLighthouseAudit(...). - Added
resolveLighthouseFormFactor()to infermobileordesktopfrom Cypress runtime context in this order:lighthouseFormFactorformFactorplatformtargetPlatformdeviceType- fallback: viewport width (
<= 768=> mobile, else desktop)
- Added
resolveLighthouseRunOptions()to keepformFactorandscreenEmulationaligned:- mobile profile:
formFactor: "mobile",screenEmulation.mobile: true(390x844, dpr 2) - desktop profile:
formFactor: "desktop",screenEmulation.mobile: false(1920x1080, dpr 1)
- mobile profile:
Why: Lighthouse warns when formFactor and screenEmulation.mobile disagree (for example, desktop + mobile: true). Aligning both values removes configuration mismatch noise and ensures metrics are collected against the intended platform profile.
Result: The same test steps can run against desktop or mobile targets without code changes in step definitions. Platform is controlled by Cypress env/viewport and propagated automatically to Lighthouse.
Files:
Added three runtime/reporting changes to reduce score drift between Chrome DevTools Lighthouse and Cypress Lighthouse runs and make differences inspectable:
-
Option-aware Lighthouse report cache key in
cypress.config.ts- Previously, in-memory caching used only normalized URL.
- Now cache key is URL + stable-serialized Lighthouse options, preventing desktop/mobile profile collisions for the same route.
-
Desktop preset loading when
formFactor === "desktop"incypress.config.ts-
lighthouse/core/config/desktop-config.jsis loaded and passed to the Lighthouse Node API for desktop runs. - This aligns Cypress closer to explicit desktop audits instead of relying only on flag-level
formFactorandscreenEmulationoverrides.
-
-
Effective config visibility in reports
-
lighthouseConfigSettingsfromlhr.configSettingsis now included in report payloads/events. -
lighthouseModule.jsnow prints anEffective Configline per path/profile in the markdown summary and groups by bothlighthouseOptionsandlighthouseConfigSettings.
-
Why: We observed significant score mismatch (e.g., Chrome ~91 vs Cypress ~57). Main causes were mixed profile reuse by URL-only caching and insufficient visibility into effective Lighthouse runtime settings.
Result: Desktop/mobile runs no longer overwrite each other in cache, Cypress desktop audits use the desktop config preset, and summary output now shows the concrete settings used to compute each score.
| Metric | Before | After |
|---|---|---|
| CSS raw size | 884 KB | 567 KB |
| CSS gzipped | ~96.7 KB | ~63.2 KB |
@font-face blocks in CSS |
86 (82 IBM Plex + 4 BCSans) | 8 (4 BCSans original + 4 BCSans override) |
font-display: swap |
0 | 4 |
| Font format on first match | woff (~350 KB/variant) | woff2 (~230 KB/variant) |
| Render-blocking scripts | 2 (config.js + params.js) | 0 (deferred) |
| bfcache support | Blocked (no-store) | Enabled (no-cache) |
| Image assets total | 434 KB (PNG) | 155 KB (SVG + WebP) |
| File | Change |
|---|---|
src/styles/index.scss |
Added @use '@carbon/styles/scss/config' with $css--font-face: false; added @use './fonts'
|
src/styles/_fonts.scss |
New — BCSans @font-face overrides with font-display: swap and woff2-only |
src/styles/_overrides.scss |
Replaced @use '@carbon/react' with selective base + component imports |
index.html |
Added <link rel="preload"> for fonts, SVG logos, and landing image; added defer to config/params scripts |
public/fonts/ |
New directory — BCSans woff2 files for preload-compatible serving |
public/img/ |
New directory — SVG logos + compressed landing.webp (moved from src/assets/img/) |
src/assets/.gitkeep |
New — placeholder to keep the empty assets directory in git |
Caddyfile |
Added /fonts/* and /img/* caching; excluded both from no-cache rule; changed no-store → no-cache for bfcache |
vite.config.ts |
Added globPatterns with webp to injectManifest for precaching |
src/pages/Landing/index.tsx |
Switched to static /img/ paths with SVG logos; removed asset imports; added height and fetchPriority="high"
|
src/pages/Landing/index.unit.test.tsx |
Updated assertions for SVG paths |
cypress/cypress/support/helpers/lighthouse.helpers.ts |
Added automatic Lighthouse profile resolution from Cypress env/viewport and passed aligned formFactor + screenEmulation into runReportTo
|
cypress/cypress.config.ts |
Added option-aware Lighthouse cache keys, desktop preset loading, and lighthouseConfigSettings emission in report payloads |
cypress/scripts/modules/lighthouseModule.js |
Added profile/config reporting (Effective Config) and grouping/dedupe by both Lighthouse options and config settings |
- The upstream
@bcgov-nr/nr-themepackage should ideally be updated to includefont-display: swapand woff2-first ordering. Once that happens, the_fonts.scssoverride file can be removed. -
@carbon/chartsand@carbon/charts-reactare listed as dependencies inpackage.jsonbut are not imported anywhere in the source code. Removing them would reducenode_modulessize and eliminate potential accidental future imports. - When adding new Carbon components, remember to add their SCSS module to
_overrides.scss(see maintenance note in Part 2, section 8). - Further JS reduction is possible by lazy-loading route-level chunks (e.g., splitting the
/searchpage and landing page into separate dynamic imports viaReact.lazy). The Lighthouse report flagged 473 KiB of unused JS, primarily fromcarbon.js(77 KiB savings) andutils.js(28 KiB savings) — these are components and utilities loaded eagerly but not used on the initial page. - The Lighthouse "Modern HTTP" suggestion (HTTP/2 or HTTP/3) is an infrastructure concern — Caddy supports HTTP/2 natively over TLS, but
auto_httpsis disabled in favor of OpenShift route-level TLS termination. Enabling HTTP/2 at the route level would allow multiplexing all the JS/CSS/font requests over a single connection.
This wiki documents NR Waste Plus for users and developers. Verify security and deployment guidance against the current application configuration before applying it elsewhere.
- Report an issue or ask a question in the GitHub repository.
- Follow the Contributing Guidelines to propose an update.
- Read the Code of Conduct.
Collection indexes support human browsing and machine retrieval. Keep both current when adding or moving wiki pages.
NR Waste Plus Wiki | GitHub Repository