Skip to main content
The Flash Prevention plugin eliminates the jarring flash of unstyled content (FOUC) that can occur when Angular hydrates a pre-rendered page. It creates a seamless transition from static to dynamic content by displaying the rendered content until Angular is fully loaded.

Overview

The Flash Prevention plugin:
  • Prevents flash of unstyled content (FOUC)
  • Creates a duplicate app root element
  • Shows static content until Angular loads
  • Smoothly transitions to the live app
  • Preserves scroll position during transition
  • Automatically removes the duplicate after hydration

The Problem

When Angular hydrates a pre-rendered page:
  1. Browser loads and displays static HTML
  2. Angular starts bootstrapping
  3. Brief moment where Angular hasn’t rendered yet
  4. Flash: Content disappears or appears unstyled
  5. Angular finishes loading and displays properly
This flash creates a poor user experience, especially on slower connections.

The Solution

The Flash Prevention plugin solves this by:
  1. Creating a mock <app-root-scully> with the static content
  2. Hiding the real <app-root> until Angular loads
  3. Showing the mock root while Angular bootstraps
  4. Swapping visibility when Angular fires the AngularReady event
  5. Removing the mock element after transition

Installation

The Flash Prevention plugin is built into Scully and requires no separate installation.

Configuration

Import and configure the plugin in your scully.config.ts:
import { ScullyConfig } from '@scullyio/scully';
import { getFlashPreventionPlugin } from '@scullyio/scully-plugin-flash-prevention';

export const config: ScullyConfig = {
  defaultPostRenderers: [getFlashPreventionPlugin()]
};

Configuration Options

Customize the plugin behavior with these options:
appRootSelector
string
default:"app-root"
The selector for your Angular app root element. Change this if you’ve customized your app’s root selector.
getFlashPreventionPlugin({
  appRootSelector: 'my-app'
})
appLoadedClass
string
default:"loaded"
The CSS class added to <body> when Angular finishes loading.
getFlashPreventionPlugin({
  appLoadedClass: 'angular-ready'
})
displayType
string
default:"inherit"
The CSS display value for the app root elements.
getFlashPreventionPlugin({
  displayType: 'block'
})
appRootAttributesBlacklist
string[]
HTML attributes to remove from the real app root when copying to the mock. By default, Angular-specific attributes like _nghost and ng-version are removed.
getFlashPreventionPlugin({
  appRootAttributesBlacklist: ['data-version', 'custom-attr']
})
mockAttributesBlacklist
string[]
HTML attributes to remove from the mock app root element.
getFlashPreventionPlugin({
  mockAttributesBlacklist: ['ng-version']
})

Usage Examples

Basic Setup

Minimal configuration with defaults:
import { ScullyConfig } from '@scullyio/scully';
import { getFlashPreventionPlugin } from '@scullyio/scully-plugin-flash-prevention';

export const config: ScullyConfig = {
  defaultPostRenderers: [
    getFlashPreventionPlugin()
  ]
};

Custom App Root Selector

If your Angular app uses a custom selector:
export const config: ScullyConfig = {
  defaultPostRenderers: [
    getFlashPreventionPlugin({
      appRootSelector: 'my-custom-app'
    })
  ]
};

Custom Loaded Class

Use a custom class name for styling:
export const config: ScullyConfig = {
  defaultPostRenderers: [
    getFlashPreventionPlugin({
      appLoadedClass: 'app-hydrated'
    })
  ]
};
Then use it in your CSS:
body:not(.app-hydrated) {
  /* Styles while loading */
}

body.app-hydrated {
  /* Styles after Angular loads */
}

Block Display

Force block display for app roots:
export const config: ScullyConfig = {
  defaultPostRenderers: [
    getFlashPreventionPlugin({
      displayType: 'block'
    })
  ]
};

Custom Attribute Filtering

Remove additional attributes from the app root:
export const config: ScullyConfig = {
  defaultPostRenderers: [
    getFlashPreventionPlugin({
      appRootAttributesBlacklist: [
        'data-server-rendered',
        'data-build-id'
      ]
    })
  ]
};

How It Works

HTML Transformation

The plugin transforms your HTML from:
<body>
  <app-root>
    <!-- Your static content -->
  </app-root>
</body>
To:
<body>
  <app-root></app-root>
  <app-root-scully>
    <!-- Your static content -->
  </app-root-scully>
</body>

CSS Injection

The plugin adds CSS to control visibility:
/* Before Angular loads */
body:not(.loaded) app-root { display: none; }
body:not(.loaded) app-root-scully { display: inherit; }

/* After Angular loads */
body.loaded app-root { display: inherit; }
body.loaded app-root-scully { display: none; }

JavaScript Injection

A script handles the transition:
  1. Scroll Preservation: Captures scroll position on scroll events
  2. Event Listening: Waits for the AngularReady event
  3. Class Addition: Adds the loaded class to <body>
  4. Scroll Restoration: Restores the user’s scroll position
  5. Cleanup: Removes the mock element and event listeners
  6. Event Dispatch: Fires FlashPreventionSwitchDone event

Angular Integration

Fire the AngularReady event in your Angular app:
import { ApplicationRef, Component } from '@angular/core';
import { filter, first } from 'rxjs/operators';

@Component({
  selector: 'app-root',
  template: `<router-outlet></router-outlet>`
})
export class AppComponent {
  constructor(private appRef: ApplicationRef) {
    // Wait for app to be stable
    this.appRef.isStable
      .pipe(
        filter(stable => stable),
        first()
      )
      .subscribe(() => {
        // Dispatch the AngularReady event
        document.dispatchEvent(new Event('AngularReady'));
      });
  }
}

Events

The plugin works with these custom events:

AngularReady

Dispatch from Angular when your app is fully loaded:
document.dispatchEvent(new Event('AngularReady'));

FlashPreventionSwitchDone

Listen for this event to know when the transition completes:
window.addEventListener('FlashPreventionSwitchDone', () => {
  console.log('Transition complete!');
});

When to Use

Use the Flash Prevention plugin when:
  • You notice a flash when Angular hydrates
  • Content briefly disappears during page load
  • You want seamless transitions between static and dynamic content
  • User experience during hydration matters
  • You’re using Scully for pre-rendering
The Flash Prevention plugin is especially important for content-heavy pages where the flash would be very noticeable.

Best Practices

1

Dispatch AngularReady correctly

Make sure to dispatch the AngularReady event after your Angular app is stable, not just after bootstrap.
2

Test on slow connections

The flash is most noticeable on slower connections. Test with network throttling.
3

Style both states

Use the loaded class to style loading and loaded states differently if needed.
4

Monitor scroll behavior

Verify that scroll position is maintained correctly during the transition.

Styling with Loaded Class

Use the loaded class for conditional styling:
/* Show loading indicator */
body:not(.loaded) .loading-spinner {
  display: block;
}

/* Hide loading indicator after load */
body.loaded .loading-spinner {
  display: none;
}

/* Fade in content */
body.loaded {
  animation: fadeIn 0.3s ease-in;
}

@keyframes fadeIn {
  from { opacity: 0.95; }
  to { opacity: 1; }
}

Troubleshooting

  • Verify you’re dispatching the AngularReady event
  • Check that the plugin is in defaultPostRenderers
  • Ensure Angular app root selector matches configuration
  • Check that AngularReady event is being fired
  • Verify the event is dispatched after app is stable
  • Look for JavaScript errors preventing the transition
  • Ensure scroll events are not being prevented
  • Check browser console for JavaScript errors
  • Verify the scroll script is running correctly
  • Add them to appRootAttributesBlacklist
  • Or add to mockAttributesBlacklist as appropriate

Performance Impact

The Flash Prevention plugin has minimal impact:
  • HTML size: Increases by ~2-3KB (compressed)
  • Render time: No measurable impact
  • User experience: Significantly improved
The small size increase is worth it for the dramatically improved user experience.

Critical CSS Plugin

Inline critical CSS for faster rendering

Post Processors

Learn about other post-processing plugins

Build docs developers (and LLMs) love