Skip to content

Framework Structure

The NETWRIX website is built on Terra’s custom JavaScript framework (terra-js), an opinionated, performance-focused architecture designed to handle complex web applications with seamless page transitions, optimized asset loading, and modular code organization.

This framework leverages modern tools like SWUP, GSAP, Boostify, and a custom Manager system to deliver a fast, maintainable, and scalable codebase. The architecture is specifically tailored for projects requiring smooth animations, lazy-loaded components, and efficient memory management across page navigations.


The NETWRIX JavaScript framework is built on three core pillars:

  1. Modular Structure - Components, handlers, and utilities organized for reusability
  2. Performance Optimization - Strategic code splitting and lazy loading via Boostify
  3. Smooth Transitions - SWUP-powered page transitions with coordinated animations

The framework encompasses:

  • Main Files: Project.js, Core.js, Main.js - The initialization and orchestration layer
  • Handlers: Reusable controllers that manage component initialization and lifecycle
  • Classes: Individual component logic following Terra’s class structure conventions
  • Animations: GSAP-powered motion design for heroes, reveals, and transitions
  • Manager System: Centralized instance and library management
  • Asset Management: Smart loading and preloading strategies

SWUP is the backbone of our page transition system, enabling SPA-like navigation without full page reloads while maintaining SEO-friendly server-rendered HTML.

  • Seamless Navigation - Instant page transitions without reload flicker
  • SEO-Friendly - Works with server-rendered HTML, no client-side routing required
  • Progressive Enhancement - Falls back gracefully when JavaScript is disabled
  • Plugin Ecosystem - Extensible architecture for custom behaviors

SWUP is initialized in Core.js with a carefully configured plugin stack:

this.swup = new Swup({
linkSelector: "a[href]:not([href$='.pdf']), area[href], svg a[*|href]",
containers: ["#swup"],
plugins: [
new SwupHeadPlugin({ persistAssets: true }),
new SwupBodyClassPlugin(),
new SwupScriptsPlugin({ head: true, body: true }),
new SwupDebugPlugin({ globalInstance: true }), // Only in debug mode
new SwupJsPlugin(createTransitionOptions({...})),
new SwupIgnoreInjectedScriptsPlugin(),
],
});
PluginPurpose
HeadPluginManages <head> updates, persisting critical assets
BodyClassPluginUpdates body classes on navigation
ScriptsPluginExecutes scripts found in new page content
JsPluginCustom transition animations (our In/Out system)
DebugPluginDevelopment debugging (enabled with ?debug param)
IgnoreInjectedScriptsPluginPrevents re-execution of third-party scripts (GTM, etc.)

The framework leverages SWUP’s lifecycle hooks to manage content and instances:

// When content is replaced (new page loaded)
this.swup.hooks.on("content:replace", () => {
this.contentReplaced();
});
// Before content replacement (cleanup phase)
this.swup.hooks.before("content:replace", () => {
this.willReplaceContent();
});
// After page view (analytics tracking)
this.swup.hooks.on("page:view", async (data) => {
// GTM virtual pageview tracking
window.dataLayer.push({
event: "VirtualPageview",
virtualPageURL: window.location.href,
virtualPageTitle: document.title,
virtualPagePath: window.location.pathname,
});
});

SWUP requests include custom headers for optimal caching:

fetchOptions: {
headers: {
"X-Swup": "1",
"CDN-Cache-Control": "public, s-maxage=3600, stale-while-revalidate=86400",
"Netlify-CDN-Cache-Control": "public, durable, s-maxage=300",
// Security headers...
},
}

The framework’s foundation consists of three primary files that work together to initialize and manage the application.

Purpose: Application bootstrap, library preloading, and initial animation orchestration.

Key Responsibilities:

  • Initialize the Manager system
  • Preload critical libraries (GSAP, ScrollTrigger, Boostify)
  • Execute preloader animation
  • Trigger initial hero and reveal animations
  • Bootstrap the Main class

Structure:

class Project {
constructor() {
this.DOM = { /* Critical DOM references */ };
this.Manager = new Manager({ terraDebug: this.terraDebug });
this.Manager.allocateInstances([/* Instance pools */]);
this.init();
}
async init() {
// 1. Initialize AssetManager
// 2. Load libraries
// 3. Initialize Boostify
// 4. Register GSAP plugins
// 5. Import and initialize Main
// 6. Execute preloader timeline
// 7. Trigger hero animations
// 8. Initialize reveal animations
}
}

Why Project.js is First:

  • Controls the critical rendering path
  • Ensures GSAP is loaded before any animations
  • Manages the preloader → content reveal flow
  • Only runs once on initial page load

Purpose: Manage page transitions, lazy loading, and lifecycle events.

Key Responsibilities:

  • Initialize and configure SWUP
  • Handle content:replace lifecycle
  • Manage Blazy (lazy image loading)
  • Coordinate cleanup before transitions
  • Track page views for analytics

Structure:

class Core {
constructor(payload) {
const { boostify, blazy, Manager, emitter } = payload;
this.swup = new Swup({/* config */});
}
events() {
// Listen for DOMContentLoaded
this.swup.hooks.on("content:replace", () => this.contentReplaced());
this.swup.hooks.before("content:replace", () => this.willReplaceContent());
this.swup.hooks.on("page:view", (data) => {/* Analytics */});
}
contentReplaced() {
// Re-initialize Blazy for new images
}
willReplaceContent() {
// Cleanup before page change
}
}

Why Core.js is Crucial:

  • Abstracts SWUP configuration from business logic
  • Provides hooks that Main.js extends
  • Centralized transition management
  • Ensures proper cleanup to prevent memory leaks

Purpose: Initialize all handlers and components for the application.

Key Responsibilities:

  • Extend Core.js functionality
  • Initialize all Handler instances
  • Manage component lifecycle across page transitions
  • Import and initialize global components (Header)

Structure:

class Main extends Core {
constructor(payload) {
super({/* Core config */});
this.handler = {/* Shared handler payload */};
this.init();
this.events();
}
async init() {
super.init();
// Initialize 30+ handlers
new SliderHandler({ ...this.handler, name: "SliderHandler" });
new ModalHandler({ ...this.handler, name: "Modal" });
// ... etc
// Import global Header component
const { default: Header } = await import("@scripts/module/header/Header.js");
new Header({/* config */});
}
events() {
super.events();
}
contentReplaced() {
super.contentReplaced();
this.emitter.emit("MitterContentReplaced");
}
willReplaceContent() {
super.willReplaceContent();
this.emitter.emit("MitterWillReplaceContent");
}
}

Why Main.js Extends Core:

  • Separates SWUP logic from application logic
  • Allows Core to be reusable across projects
  • Main handles project-specific handlers
  • Emits custom events for handlers to listen to

1. Project.js
↓ Loads libraries (GSAP, Boostify)
↓ Initializes Manager
↓ Runs preloader animation
2. Main.js (imported by Project)
↓ Extends Core.js
↓ Initializes SWUP
↓ Initializes all Handlers
↓ Triggers contentReplaced()
3. Handlers
↓ Listen for emitter events
↓ Initialize components (via Boostify or instantly)
User clicks link
SWUP intercepts
willReplaceContent() - Cleanup phase
↓ Handlers destroy instances
↓ Remove event listeners
Out transition plays
↓ Smooth scroll to top
↓ Fade out animation
SWUP fetches new page
↓ Replace #swup container
↓ Update <head>
In transition plays
↓ Hero animations
↓ Reveal animations
contentReplaced()
↓ Handlers re-initialize
↓ New components instantiated

The framework uses mitt for event-driven communication between Main.js and Handlers:

// In Main.js
this.emitter.emit("MitterContentReplaced");
this.emitter.emit("MitterWillReplaceContent");
// In Handlers
this.emitter.on("MitterContentReplaced", () => {
// Re-initialize components
});
this.emitter.on("MitterWillReplaceContent", () => {
// Destroy instances
});
  • Decoupling: Handlers don’t need direct references to Main
  • Scalability: Easy to add new handlers without modifying Main
  • Timing: Ensures all handlers respond to lifecycle changes
  • Debugging: Easy to track when events fire with ?debug

The Manager is a centralized registry for:

  1. Library References - GSAP, ScrollTrigger, Boostify, etc.
  2. Component Instances - Sliders, Modals, Accordions, etc.
  3. Instance Pools - Pre-allocated arrays for each component type

In Project.js, instance pools are pre-allocated:

this.Manager.allocateInstances([
"Slider",
"Modal",
"Collapsify",
"RevealIt",
"RevealStack",
// ... etc
]);

This creates empty arrays: Manager.instances.Slider = []

Libraries are stored for reuse:

this.Manager.addLibrary({ name: "GSAP", lib: gsapModule });
const gsap = this.Manager.getLibrary("GSAP");

Handlers add/remove instances via Manager:

// Add instance
this.Manager.addInstance("Slider", new Slider({...}));
// Clean instances (clear array)
this.Manager.cleanInstances("Slider");

Benefits:

  • Centralized memory management
  • Easy debugging (inspect Manager.instances in console)
  • Prevents duplicate instances
  • Clean destruction on page transitions

Enable debug mode by adding ?debug to the URL:

https://netwrix.com/page?debug

Features:

  • SWUP debug plugin enabled
  • Console logs for library loading
  • Terra debugger UI for QA feedback
  • Visibility into instance creation/destruction
this.terraDebug = getQueryParam("debug");
if (this.terraDebug) {
console.log(`✅ library ${this.libraryName} was in manager`);
console.log(`🚧 loading library ${this.libraryName}`);
}

Components are split into categories (defined in extraAssets.js):

  1. getTerraInternal() - Critical libraries (GSAP, Boostify)
  2. getAnimations() - Hero and reveal animations
  3. getModules() - Component classes (Slider, Modal, etc.)
  4. getDeferred() - Non-critical features (PerlinAnimation)
  5. getThirdParty() - External services

Components only load when needed:

// Component in viewport? Load instantly
if (inViewport) {
this.createInstance({ element, config });
}
// Component below fold? Load on scroll
else {
this.boostify.scroll({
distance: 30,
callback: async () => {
const lib = await loadLibrary({ libraryName });
this.createInstance({ element, config });
}
});
}

Result: First-load JavaScript is minimal, components load as users scroll.


  • Use the Manager for all library references and instances
  • Listen to emitter events in handlers for lifecycle management
  • Destroy instances in willReplaceContent to prevent memory leaks
  • Use debug mode during development
  • Follow the three-file structure - don’t bypass Project/Core/Main
  • Allocate instance pools in Project.js before use
  • Use Boostify for below-fold components
  • Don’t initialize components in Project.js - that’s Main.js’s job
  • Don’t store instances globally - use Manager
  • Don’t forget destroy methods - SWUP requires cleanup
  • Don’t load all libraries upfront - leverage code splitting
  • Don’t bypass the Handler pattern - it ensures consistency
  • Don’t manipulate SWUP directly in Main - extend Core instead

The NETWRIX JavaScript framework is a battle-tested architecture that prioritizes:

  • Performance through strategic code splitting and lazy loading
  • Maintainability via consistent patterns and centralized management
  • User Experience with smooth SWUP transitions and GSAP animations
  • Scalability through the Handler system and Manager registry

By understanding how Project.js bootstraps, Core.js manages transitions, and Main.js orchestrates components, developers can confidently build and maintain complex features while adhering to Terra’s established patterns.