Skip to content

Excessive modulepreload Links in Angular 18.0.0-next.4 Affecting Performance Metrics #27490

Closed

Description

@manzonif

Which @angular/* package(s) are the source of the bug?

platform-server

Is this a regression?

No

Description

I recently upgraded my Angular application to version 18.0.0-next.4 and migrated to esbuild and the new integrated SSR. However, I've noticed a significant impact on performance metrics, particularly First Contentful Paint (FCP) and Largest Contentful Paint (LCP), when using the new version.

Upon further investigation, I found that Angular now splits all JavaScript code into chunks and adds a <link rel="modulepreload"> for each chunk in the <head> of the page. Although the scripts are loaded asynchronously with @defer method, the preloaded scripts seem to be affecting the rendering of critical content, delaying FCP and LCP.

I've conducted tests using Lighthouse, and the results show that removing the modulepreload links improves FCP and LCP metrics. This issue seems to be similar to what was reported in these GitHub discussions:

GoogleChrome/lighthouse#11960
vitejs/vite#5991
In both discussions, users reported improvements in FCP and LCP metrics after disabling or reducing the number of modulepreload links.

Unfortunately, I couldn't find a way to selectively disable modulepreload links in Angular 18.0.0-next.4 based on specific use cases. Therefore, I would like to seek clarification and guidance on how to address this issue to improve performance.

Steps to Reproduce:

Upgrade an Angular application to version 18.0.0-next.4.
Enable SSR and use esbuild.
Analyze performance metrics using Lighthouse with and without the modulepreload links.
Expected Behavior:
I expect to see improved FCP and LCP metrics when excessive modulepreload links are removed or selectively disabled.

Additional Information:
I have attached screenshots of the Lighthouse test results, showing the impact of modulepreload links on performance metrics.

Screenshots:

Screenshot 1 - Lighthouse Test with modulepreload links

Screenshot 2024-04-17 132612

Screenshot 2 - Lighthouse Test without modulepreload links
Performed by removing all modulepreload links server side:

        html = html.replace(
          /<link .?rel="modulepreload" .*?href="(?<href>.+?\.js)".*?>/g,
          ''
        );

Screenshot 2024-04-17 132254

Your assistance in resolving this issue would be greatly appreciated.

Thank you.

Please provide a link to a minimal reproduction of the bug

No response

Please provide the exception or error you saw

No response

Please provide the environment you discovered this bug in (run ng version)

Angular CLI: 18.0.0-next.2
Node: 20.11.1
Package Manager: yarn 1.22.21
OS: win32 x64

Angular: 18.0.0-next.4
... animations, cdk, common, compiler, compiler-cli, core, forms
... google-maps, language-service, localize, material
... platform-browser, platform-browser-dynamic, platform-server
... router, service-worker

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.1800.0-next.2
@angular-devkit/build-angular     18.0.0-next.2
@angular-devkit/build-optimizer   0.1302.1
@angular-devkit/core              18.0.0-next.2
@angular-devkit/schematics        17.1.2
@angular/cli                      18.0.0-next.2
@angular/ssr                      18.0.0-next.2
@schematics/angular               18.0.0-next.2
rxjs                              7.8.1
typescript                        5.4.5
zone.js                           0.14.4

Anything else?

No response

Activity

  1. added this to the needsTriage milestone on Apr 17, 2024
  2. transferred this issue fromangular/angularon Apr 17, 2024
  3. alan-agius4 commented on Apr 18, 2024

    @alan-agius4
    Collaborator

    You can disable to preload tags by using the preloadInitial option.

      "projects": {
        "test": {
          "projectType": "application",
          "schematics": {},
          "root": "",
          "sourceRoot": "src",
          "prefix": "app",
          "architect": {
            "build": {
              "builder": "@angular-devkit/build-angular:application",
              "options": {
                "outputPath": "dist/",
                "index": {
                  "input": "src/index.html",
                  "preloadInitial": false
                },
  4. manzonif commented on Apr 18, 2024

    @manzonif
    Author

    @alan-agius4 Thank you for your response and for providing the solution.

    I would appreciate it if you would consider implementing support for selective filtering of module preloading links. I believe a feature like this would be incredibly valuable, especially for larger applications where fine-grained control over resource loading can have a significant impact on performance.

    The implementation described in the Vite.js pull request #9938 seems to provide a robust solution by allowing users to define a resolveDependencies function to filter or modify the list of dependencies. This level of flexibility would empower developers to optimize resource loading based on specific use cases and performance requirements.

    Are you planning to incorporate this functionality into Angular in the near future?
    You might, for example, think about implementing it as part of the @defer block.

  5. alan-agius4 commented on Apr 18, 2024

    @alan-agius4
    Collaborator

    @manzonif, I'm curious, how many preload links do you use?

    Generally, offering such options doesn't really align with our design goals. Instead, we aim to provide a more responsive default that can cater to applications of all sizes.

    There's definitely a correlation between Core Web Vitals (CWV) and preload tags. The more preload tags, the poorer the performance tends to becomes at least based on https://almanac.httparchive.org/en/2021/resource-hints#correlation-with-core-web-vitals

  6. manzonif commented on Apr 18, 2024

    @manzonif
    Author

    I count about 50 modulepreload links, plus an image that is part of the LCP. Of course it depends on the page being examined.

    Furthermore, some of these modules, in turn, require the loading of third-party javascript (with the intention that they should be loaded late),perhaps making the situation even worse.

  7. manzonif commented on Apr 19, 2024

    @manzonif
    Author

    @alan-agius4, I also noticed that Angular preloaded modules take precedence in the document head over those inserted inside a component. If what @patrickhulce reports here is correct, the order of the preload hints is also important. Therefore, it would be necessary to ensure that the insertion of the module preloads is postponed, at least until after the ngOnInit lifecycle hook of the components.

    If I add a preload for an LCP image, it should have high priority, IMO.

  8. sod commented on May 6, 2024

    @sod
    Contributor

    Esbuild can dump a stats.json via ng build --stats-json that contains the information which files are required to load lazy loaded chunks. See https://esbuild.github.io/api/#metafile (the outputs part)

    Ssr could use the stats.json to only load the chunks required for the current route.

  9. alan-agius4 commented on May 6, 2024

    @alan-agius4
    Collaborator

    The most recent RC version contains a change to only generate shallow preload links for initial files. You can find more details in PR #27581.

    @manzonif, could you please confirm if this resolves your issue?

    @sod, in this situation, the issue lies not in the absence of preloads, but rather in having more than approximately 5 preloads, even for files essential to the route. Essentially, including a substantial number of preload links could create a performance bottleneck, more so when using SSR, as it might disrupt the browser's fetch priority.

  10. added and removed
    needs: investigationRequires some digging to determine if action is needed
    on May 6, 2024
  11. manzonif commented on May 14, 2024

    @manzonif
    Author

    Hi @alan-agius4,

    Sorry for the delay in replying.

    The PR certainly addresses the issue of excessive preload links. However, I have a couple of remaining concerns:

    1. If the order of links is important, I believe that those inserted by the component should take precedence over others. For example, in the case of an LCP (Largest Contentful Paint) image.

    2. Limiting the preloads to the first three chunks might sometimes produce unexpected results. In my case, importing utility functions from other .ts files into the main component results in these small files being the three preload links. While I imagine this issue could be resolved by restructuring the imports, I wanted to bring it to your attention.

    Below are some example images:

    Screenshot 2024-05-14 114606
    Screenshot 2024-05-14 114652

  12. alan-agius4 commented on Aug 30, 2024

    @alan-agius4
    Collaborator

    I believe that the issue with the order of links has likely been resolved. However, if the problem persists, please provide a reproduction. As for the issue with small chunks, it is currently being tracked here: #27715.

  13. angular-automatic-lock-bot commented on Sep 30, 2024

    @angular-automatic-lock-bot

    This issue has been automatically locked due to inactivity.
    Please file a new issue if you are encountering a similar or related problem.

    Read more about our automatic conversation locking policy.

    This action has been performed automatically by a bot.

  14. locked and limited conversation to collaborators on Sep 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions