Framework Structure
Introduction
Section titled “Introduction”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.
Framework Architecture
Section titled “Framework Architecture”The NETWRIX JavaScript framework is built on three core pillars:
- Modular Structure - Components, handlers, and utilities organized for reusability
- Performance Optimization - Strategic code splitting and lazy loading via Boostify
- Smooth Transitions - SWUP-powered page transitions with coordinated animations
Key Components
Section titled “Key Components”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 Integration
Section titled “SWUP Integration”SWUP is the backbone of our page transition system, enabling SPA-like navigation without full page reloads while maintaining SEO-friendly server-rendered HTML.
Why SWUP?
Section titled “Why SWUP?”- 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 Configuration
Section titled “SWUP Configuration”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(), ],});Key SWUP Plugins
Section titled “Key SWUP Plugins”| Plugin | Purpose |
|---|---|
| HeadPlugin | Manages <head> updates, persisting critical assets |
| BodyClassPlugin | Updates body classes on navigation |
| ScriptsPlugin | Executes scripts found in new page content |
| JsPlugin | Custom transition animations (our In/Out system) |
| DebugPlugin | Development debugging (enabled with ?debug param) |
| IgnoreInjectedScriptsPlugin | Prevents re-execution of third-party scripts (GTM, etc.) |
SWUP Lifecycle Hooks
Section titled “SWUP Lifecycle Hooks”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, });});Cache Control
Section titled “Cache Control”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 Three Core Files
Section titled “The Three Core Files”The framework’s foundation consists of three primary files that work together to initialize and manage the application.
1. Project.js - The Entry Point
Section titled “1. Project.js - The Entry Point”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
2. Core.js - The SWUP Controller
Section titled “2. Core.js - The SWUP Controller”Purpose: Manage page transitions, lazy loading, and lifecycle events.
Key Responsibilities:
- Initialize and configure SWUP
- Handle
content:replacelifecycle - 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
3. Main.js - The Application Controller
Section titled “3. Main.js - The Application Controller”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
How They Work Together
Section titled “How They Work Together”Initialization Flow
Section titled “Initialization Flow”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)Page Transition Flow
Section titled “Page Transition Flow”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 instantiatedEvent System
Section titled “Event System”The framework uses mitt for event-driven communication between Main.js and Handlers:
// In Main.jsthis.emitter.emit("MitterContentReplaced");this.emitter.emit("MitterWillReplaceContent");
// In Handlersthis.emitter.on("MitterContentReplaced", () => { // Re-initialize components});
this.emitter.on("MitterWillReplaceContent", () => { // Destroy instances});Why Use Events?
Section titled “Why Use Events?”- 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
Manager System Overview
Section titled “Manager System Overview”The Manager is a centralized registry for:
- Library References - GSAP, ScrollTrigger, Boostify, etc.
- Component Instances - Sliders, Modals, Accordions, etc.
- Instance Pools - Pre-allocated arrays for each component type
Allocating Instance Pools
Section titled “Allocating Instance Pools”In Project.js, instance pools are pre-allocated:
this.Manager.allocateInstances([ "Slider", "Modal", "Collapsify", "RevealIt", "RevealStack", // ... etc]);This creates empty arrays: Manager.instances.Slider = []
Adding Libraries
Section titled “Adding Libraries”Libraries are stored for reuse:
this.Manager.addLibrary({ name: "GSAP", lib: gsapModule });const gsap = this.Manager.getLibrary("GSAP");Managing Instances
Section titled “Managing Instances”Handlers add/remove instances via Manager:
// Add instancethis.Manager.addInstance("Slider", new Slider({...}));
// Clean instances (clear array)this.Manager.cleanInstances("Slider");Benefits:
- Centralized memory management
- Easy debugging (inspect
Manager.instancesin console) - Prevents duplicate instances
- Clean destruction on page transitions
Debug Mode
Section titled “Debug Mode”Enable debug mode by adding ?debug to the URL:
https://netwrix.com/page?debugFeatures:
- 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}`);}Performance Strategy
Section titled “Performance Strategy”Code Splitting
Section titled “Code Splitting”Components are split into categories (defined in extraAssets.js):
- getTerraInternal() - Critical libraries (GSAP, Boostify)
- getAnimations() - Hero and reveal animations
- getModules() - Component classes (Slider, Modal, etc.)
- getDeferred() - Non-critical features (PerlinAnimation)
- getThirdParty() - External services
Lazy Loading with Boostify
Section titled “Lazy Loading with Boostify”Components only load when needed:
// Component in viewport? Load instantlyif (inViewport) { this.createInstance({ element, config });}// Component below fold? Load on scrollelse { 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.
Best Practices
Section titled “Best Practices”✅ Do’s
Section titled “✅ Do’s”- Use the Manager for all library references and instances
- Listen to emitter events in handlers for lifecycle management
- Destroy instances in
willReplaceContentto 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’ts
Section titled “❌ Don’ts”- 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
Summary
Section titled “Summary”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.
Related Topics
Section titled “Related Topics”- 🎯 Classes Structure - Learn Terra’s class conventions
- 🔄 Handlers System - Master lifecycle management
- 🎬 Animations - GSAP integration patterns
- 📦 Libraries - Available tools and utilities