🚀 98/100 Lighthouse – Our SSR & Performance Strategy (NodeJS)

Hello Wappler Community,

We recently optimized our Mobile Lighthouse Score from a shaky ~80 to a stable >85 points (Desktop is 99). Since many developers struggle with LCP (Largest Contentful Paint) and Render-Blocking resources, I wanted to share our technical approach using Wappler (NodeJS/EJS).

1. Strategy: Server-Side Rendering (SSR) & Backend Assembly

Instead of loading an empty shell and fetching content client-side via dmx-serverconnect (AJAX), we assemble the HTML completely in the backend.

  • Logic: Modules (Header, Content, Footer) are retrieved as prepared HTML strings from the database or generated server-side.

  • Benefit: The browser receives the finished source code immediately via EJS (<%= content %>). Google and users see content instantly without waiting for JSON requests.

2. The "Fake Hero" Trick (LCP Gamechanger)

Despite SSR, the browser still waited for the initialization of dmxAppConnect.js and dmxSwiper.js before rendering the main slider (~2-3s Render Delay).

  • Solution: We inject a static "Fake" image server-side at the exact same position.

  • CSS Strategy:

/* Fake Hero (visible immediately, z-index 1) */
#fake-hero { display: block; position: absolute; z-index: 1; }

/* Real Swiper (hidden until fully loaded) */
#swiper2:not(.swiper-initialized) { 
    opacity: 0 !important; 
    visibility: hidden !important; /* Crucial so Lighthouse ignores it! */
}

/* Swapping (Real Swiper covers Fake Hero) */
#swiper2.swiper-initialized { 
    opacity: 1 !important; 
    visibility: visible !important; 
    z-index: 10; 
}
  • Result: The LCP element is visible in < 200ms.

3. Intelligent Resource Management

  • Preload: The first hero image is dynamically preloaded in the Head using rel="preload" and fetchpriority="high" (matching the specific imagesrcset).

  • JS Defer: We use defer for all Wappler scripts to avoid render blocking, but we prioritize the download of dmxAppConnect.js via <link rel="preload" as="script">.

  • Cleanup: Removed jQuery and Moment.js. Moved non-critical CSS (Datepicker, Validator, etc.) to the very end of the Body.

4. Stability & Accessibility (Score 99)

  • CLS (0.0): Containers have fixed aspect-ratio values in CSS (and specific media queries) so the layout doesn't shift at all during loading.

  • Validation: Eliminated nested links (<a> inside <a>). Instead, we use Bootstrap's .stretched-link class on the image to make the whole card clickable.

  • Aria: All image-based links receive server-side generated attributes like aria-label="<%= title %> view" for screen readers.

Conclusion: The combination of server-side assembly and the Fake-Hero Pattern made the biggest difference. The page feels "instant" now.

Happy optimizing! :rocket:

2 Likes

Great tips! We were actually thinking of integrating something similar for the dmx-serverconnect component - to an inner initial json data that is rendered server side with the same query.

Also good tip with the fake hero, we also planed to have similar skeleton layouts

@patrick should definitely check it out

3 Likes