Page Loader
ArchitectUI ships with a configurable page loader to prevent Flash of Unstyled Content (FOUC) on slow networks. This page covers configuration, environment overrides, customization, and when to disable it.
What the Loader Does
When enabled, ArchitectUI injects a full-screen overlay with a spinner and your loading text into every page before any of the dashboard content is visible. The overlay fades out and removes itself once the configured duration elapses, revealing the rendered app.
The mechanism has three pieces working together:
- Inline critical CSS in
base.hbshides.app-containeruntil the body has the.app-loadedclass - Inline JavaScript renders the spinner overlay before any external scripts load, then schedules the fade-out
- Emergency fallback timer removes the loader if the primary fade-out logic fails for any reason
Net result: users never see a flash of unstyled markup, even on cold cache or slow connections.
Configuration File
The loader's master config lives in src/config/loader.config.js:
// src/config/loader.config.js
module.exports = {
// Enable/disable the page loader
enabled: false,
// Loader display duration in milliseconds
duration: 800,
// Emergency timeout in milliseconds (failsafe)
timeout: 3000,
// Loader style options
style: {
backgroundColor: "#ffffff",
spinnerColor: "#3f6ad8",
textColor: "#6c757d",
text: "Loading..."
}
};
These values are read by webpack.config.js at build time and passed into base.hbs via HtmlWebpackPlugin's template context. Edit the config, restart the dev server, and the next build picks up your changes.
The current default is enabled: false because most projects don't need the loader. Set it to true to opt in.
Three Ways to Disable
1. Edit the Config File
Set enabled: false in src/config/loader.config.js. This is the persistent setting that applies to every build.
2. Environment Variable
Override the config at build time without editing files:
DISABLE_LOADER=true npm start
DISABLE_LOADER=true npm run build
3. Dedicated npm Scripts
ArchitectUI exposes two scripts that set DISABLE_LOADER=true for you:
npm run start:no-loader
npm run build:no-loader
Use these when you want to compare loader-on and loader-off builds without changing files.
Customization Examples
Faster Load
module.exports = {
enabled: true,
duration: 400, // shown for 400ms instead of 800ms
timeout: 2000,
style: {
backgroundColor: "#ffffff",
spinnerColor: "#3f6ad8",
textColor: "#6c757d",
text: "Loading..."
}
};
Brand Colors
style: {
backgroundColor: "#f8f9fa",
spinnerColor: "#28a745", // green spinner
textColor: "#495057",
text: "Please wait..."
}
Dark Theme Loader
style: {
backgroundColor: "#1a1a1a",
spinnerColor: "#ffffff",
textColor: "#cccccc",
text: "Loading..."
}
How It Renders
The loader markup is injected as the first child of <body> by an inline script in base.hbs. It looks roughly like this:
<div id="app-init-loader" class="app-init-loader">
<div class="loader-wrapper">
<div class="loader-logo">
<svg width="50" height="50" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="45" fill="none"
stroke="#3f6ad8" stroke-width="4" opacity="0.2"/>
<circle cx="50" cy="50" r="45" fill="none"
stroke="#3f6ad8" stroke-width="4"
stroke-dasharray="283" stroke-dashoffset="283">
<animate attributeName="stroke-dashoffset"
values="283;0" dur="2s" repeatCount="indefinite"/>
</circle>
</svg>
</div>
<div class="loader-text">Loading...</div>
</div>
</div>
The SVG circle uses a 283-unit dash array (the circumference of the circle) animated from full offset to zero, which produces a clockwise progress-ring effect.
Hide Logic
Two timers run in parallel:
- Primary fade-out at
durationmilliseconds — adds.fade-outclass for the CSS transition, then removes the element after 300ms. - Emergency fallback at
timeoutmilliseconds — force-removes the loader and logs a console warning. This catches edge cases where the primary timer is delayed by a long-running synchronous script.
Both timers add app-loaded to document.body, which is the signal the content opacity transition uses to fade in the app.
When to Disable the Loader
- Internal apps on fast networks. If you control the network and bandwidth, FOUC is usually invisible.
- Development. The loader adds ~800ms to every dev refresh. Turn it off via
npm run start:no-loaderwhile iterating. - You have your own loading solution. Some apps integrate skeleton screens, suspense boundaries, or progress bars that conflict with a full-screen overlay.
- Pages that load instantly. If your hosting and bundle size already deliver first paint in < 100ms, the loader is overhead.
Performance Impact
With the loader enabled, every page picks up roughly 3 KB of inline CSS and 1.5 KB of inline JavaScript — uncached but immediate. With it disabled, that overhead is gone but pages may briefly show unstyled content while CSS and JS load.
On a fast connection the difference is invisible. On a 3G connection, the loader hides ~500–1500ms of FOUC.
Troubleshooting
Loader stays visible too long
Reduce duration in the config.
Loader flashes too quickly
Increase duration. The minimum useful value is around 200ms — anything shorter is jarring.
Loader doesn't appear at all
Check enabled: true in the config, then make sure neither DISABLE_LOADER=true nor the :no-loader script is being used.
Content appears before the loader hides
This is normal when assets load faster than the configured duration. The loader still hides at its scheduled time. To eliminate the gap, reduce duration to match your typical load time.
Loader sticks on a specific page
A JavaScript error before the loader's hide timer fires can prevent the fade-out. The emergency fallback at timeout ms should still remove it; check the browser console for the underlying error.