EPiServer.CloudPlatform.Cms 1.8.0 renders the Application Insights snippet twice

Summary

Since EPiServer.CloudPlatform.Cms 1.8.0, every CMS 12 page whose layout renders @Html.RequiredClientResources("Header") gets the Application Insights browser snippet twice. The new internal AppInsightsHeadTagHelperComponent adds a copy before </head>, while the existing AppInsightsClientResource still renders one through the "Header" client resources. No option turns the tag helper off. RequiredClientResources("Header") is the standard CMS 12 layout hook (the Alloy template uses it), so this likely affects most sites that upgrade to 1.8.0 with Application Insights configured.

Environment

Component

Version

EPiServer.CloudPlatform.Cms

1.8.0 (also pulls EPiServer.Cms.Warmup and EPiServer.Cms.HealthCheck 1.8.0)

EPiServer.CMS

12.34.8

EPiServer.Azure

11.2.2

.NET

net10.0

Hosting

Azure, services.AddCmsCloudPlatformSupport(configuration) in Startup, Application Insights connection string configured

Last version without the bug

1.7.4

The CMS 13 line (checked 13.5.1) has no AppInsightsHeadTagHelperComponent, so the bug is specific to 1.8.0 on CMS 12.

Steps to reproduce

  1. Take a CMS 12 site whose layout renders the header client resources inside <head>, as in the Alloy template:
<head>
    ...
    @Html.RequiredClientResources("Header")
</head>
  1. Reference EPiServer.CloudPlatform.Cms 1.8.0 and call services.AddCmsCloudPlatformSupport(configuration).
  2. Configure an Application Insights connection string. Leave APPINSIGHTS_CLIENTRESOURCE_ENABLED unset (it defaults to true).
  3. Request any page rendered by a CMS content controller and view the HTML source.

Expected: the snippet (!function(T,l,y){...} loading ai.2.gbl.min.js) appears once in <head>, as with 1.7.4.

Actual: the snippet appears twice:

  • once where RequiredClientResources("Header") is rendered, from AppInsightsClientResource
  • once more just before </head>, from AppInsightsHeadTagHelperComponent

Both copies carry the CSP nonce. 1.7.4 has no tag helper, so the same setup renders one copy there.

Root cause

The tag helper checks whether the client resource was registered, never whether it was rendered. From the decompiled 1.8.0 assembly:

  1. AddApplicationInsightsClientResource() now also registers ITagHelperComponent → AppInsightsHeadTagHelperComponent (Order = 100).
  2. AppInsightsClientResource.RegisterResources() calls requiredResources.Require("epi.cloudplatform.cms.applicationInsights").AtHeader(), as in 1.7.4, and now also sets HttpContext.Items["AppInsights_ClientResourceRegistered"] = true.
  3. AppInsightsHeadTagHelperComponent.ProcessAsync() runs for <head> through ASP.NET Core's HeadTagHelper. If that flag is set, it appends the same InlineContent to output.PostContent.
  4. The layout's @Html.RequiredClientResources("Header") still renders the required "Header" resource.

The flag says the resource was required for the request, which is true on every content page, so steps 3 and 4 both output the snippet. The tag helper's doc comment says it exists so the snippet works "without requiring partner layout changes". That is the case for layouts without the "Header" hook, but layouts that already have it get a duplicate.

InstrumentationOptions has only DisableCookieUsage and DisableUnloadEvent, so no setting turns the tag helper off. Its type is internal, so sites can't remove it by type either.

Impact

The second copy turned out to be a no-op at runtime, so the impact is duplicated markup and a confusing page source. Telemetry is unaffected. We loaded the production snippet once and twice in Chromium (Playwright), with the ingestion endpoint pointed at a stub:

Copies in <head>

DisableUnloadEvent

SDK script loads

unload listener

PageviewData sent

1

false

1

yes

1

2

false

1

yes

1

1

true

1

no

1

2

true

1

no

1

  • The second copy finds the existing window.appInsights and does nothing, so no extra SDK load or page view.
  • Each page carries about 4.1 KB of extra inline JavaScript (2.2 KB gzipped).
  • Anyone inspecting the page sees two Application Insights initialisations and has to work out that one is harmless.

Suggested fix

Any one of these would solve it:

  • Have the tag helper skip the page when the "Header" resources were already rendered. For example, set a second HttpContext.Items flag when epi.cloudplatform.cms.applicationInsights is rendered and check it in ProcessAsync(). Note that <head> is processed before its child content, so the check may need to happen after the <head> content has rendered.
  • Add an option, e.g. InstrumentationOptions.InjectSnippetInHead or an APPINSIGHTS_HEADTAGHELPER_ENABLED setting, so sites with the "Header" hook can turn the tag helper off.
  • Make AppInsightsHeadTagHelperComponent public so sites can remove the registration by type.

Current workaround

We remove the registration by type name after AddCmsCloudPlatformSupport:

var duplicates = services
    .Where(d => d.ServiceType == typeof(ITagHelperComponent)
                && d.ImplementationType?.Name == "AppInsightsHeadTagHelperComponent")
    .ToList();

foreach (var descriptor in duplicates)
{
    services.Remove(descriptor);
}

This depends on an internal type name, so we would prefer a supported option.

#343711
Edited, Oct 30, 2026 10:26
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.