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
Take a CMS 12 site whose layout renders the header client resources inside <head>, as in the Alloy template:
Reference EPiServer.CloudPlatform.Cms 1.8.0 and call services.AddCmsCloudPlatformSupport(configuration).
Configure an Application Insights connection string. Leave APPINSIGHTS_CLIENTRESOURCE_ENABLED unset (it defaults to true).
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:
AddApplicationInsightsClientResource() now also registers ITagHelperComponent → AppInsightsHeadTagHelperComponent (Order = 100).
AppInsightsClientResource.RegisterResources() calls requiredResources.Require("epi.cloudplatform.cms.applicationInsights").AtHeader(), as in 1.7.4, and now also sets HttpContext.Items["AppInsights_ClientResourceRegistered"] = true.
AppInsightsHeadTagHelperComponent.ProcessAsync() runs for <head> through ASP.NET Core's HeadTagHelper. If that flag is set, it appends the same InlineContent to output.PostContent.
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:
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
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:
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:
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
Suggested fix
Any one of these would solve it:
Current workaround
We remove the registration by type name after AddCmsCloudPlatformSupport:
This depends on an internal type name, so we would prefer a supported option.