How to solve disapproved google ads on your Angular site with Prerender.io – The Adsense Mystery

Suffering from disapproved Google ads for your Angular website? Well you came to the right spot to solve all your Google adsense issues using prerender.io

Why are my Google Ads not approved even though my site is working perfectly?

Angular is  not really best friends with SEO and therefore google in general. The problem is that Angular serves a single page with no content. All the content is pulled in dynamically. This works perfectly for visitors of your website but bots really hate empty pages.

You have to understand that search engines like Google, Bing, Baidu and Yandex have to spend a ton of cash on bots exploring the whole internet. They don’t have time to wait until your dynamic page is loaded. Ain’t nobody got time for that! An html page with all content ready to go, is what those bots are looking for.

Bots do not only visit your website to end up on their search engine but also to validate and monitor your website if you are using ads. And that solves the mistery: a google ad bot visits your site and sees an empty page and your ad will be disapproved as there is no content. It’s also possible that you configured something wrong on your server so that bots visiting your site get a 404 or 500 error when visiting, which will also cause the ad to be disapproved.

How to fix disapproved google ads?

So how do we make life easier for the bots? It’s pretty crazy but we will have to setup a server that visits our website and waits until the content/html is loaded and cache that resulting html page somewhere. Whenever one of the search bots is visiting our website we serve this cached html page instead of the dynamic Angular page.
One important thing we need to figure out is whether our website is visited by a bot or by normal visitor. To do that we need to look at the user agent. A normal (human) visitor will have a user agent describing the browser he or she is using:

Mozilla/5.0 (platform; rv:geckoversion) Gecko/geckotrail Firefox/firefoxversion

Bots on the other hand will identify themselves:

Googlebot

To make this work we need to use a platform like Prerender.io which can prerender dynamic pages and cache them. It consists of a middleware layer which is just some kind of interceptor logic that you have to install on the server that is hosting your Angular application to route traffic from bots to the prerender service and normal visitors to the regular Angular site.

if visitor is bot then
   route to cached prerendered version of the website
else 
   route to dynamic Angular website

Once this is setup you have to configure all the bots user agents. It’s a bit tricky as prerender.io has some predefined user agents in their code but not the ones used by google adsense. So make sure to configure all following user-agent in prerender.io:

Googlebot
APIs-Google
AdsBot-Google-Mobile
AdsBot-Google
Googlebot-Image
Googlebot-News
Googlebot-Video
Mediapartners-Google
AdsBot-Google-Mobile-Apps

More info on google crawlers and user agents. And here a list of user agents for all type of crawlers (baidu, yandex, bing, …). This is pretty helpful to make sure your site is ranked on search engines in China for example.

If you use the asp.NET MVC version of prerender.io then you will see following in the source code:

private IEnumerable GetCrawlerUserAgents()
{
	var crawlerUserAgents = new List(new[]
		{
			"googlebot", "yahoo", "bingbot", "yandex", "baiduspider", "facebookexternalhit", "twitterbot", "rogerbot", "linkedinbot", 
			"embedly", "quora link preview", "showyoubot", "outbrain", "pinterest/0.", 
			"developers.google.com/+/web/snippet", "slackbot", "vkShare", "W3C_Validator", 
			"redditbot", "Applebot", "WhatsApp", "flipboard", "tumblr", "bitlybot", 
			"SkypeUriPreview", "nuzzel", "Discordbot", "Google Page Speed", "x-bufferbot"
	});


	if (_prerenderConfig.CrawlerUserAgents.IsNotEmpty())
	{
		crawlerUserAgents.AddRange(_prerenderConfig.CrawlerUserAgents);
	}
	return crawlerUserAgents;
}

In the web.config you will have a prerender config section. One attribute is crawlerUserAgents which allows you do add extra user agents which should be identified as bots.

<prerender 
token="[YOURTOKEN]" crawlerUserAgents="Googlebot,APIs-Google,AdsBot-Google-Mobile,AdsBot-Google,Googlebot-Image,Googlebot-News,Googlebot-Video,Mediapartners-Google,AdsBot-Google-Mobile-Apps" 
 extensionsToIgnore="xml">
</prerender>

It’s also worth noting that you don’t prerender .xml documents to avoid issues with sitemap.xml. Looking at the source code it seems that most other irrelevant extensions are been excluded already:

private IEnumerable<String> GetExtensionsToIgnore()
{
	var extensionsToIgnore = new List<string>(new[]{".js", ".css", ".less", ".png", ".jpg", ".jpeg",
		".gif", ".pdf", ".doc", ".txt", ".zip", ".mp3", ".rar", ".exe", ".wmv", ".doc", ".avi", ".ppt", ".mpg",
		".mpeg", ".tif", ".wav", ".mov", ".psd", ".ai", ".xls", ".mp4", ".m4a", ".swf", ".dat", ".dmg",
		".iso", ".flv", ".m4v", ".torrent"});
	if (_prerenderConfig.ExtensionsToIgnore.IsNotEmpty())
	{
		extensionsToIgnore.AddRange(_prerenderConfig.ExtensionsToIgnore);
	}
	return extensionsToIgnore;
}

So I hope that demystifies the whole Angular, prerender and google ad combination and all the issues that you might encounter.

Feel free to drop a comment below if it worked for you or if you have any further questions.

Solving EPiServer.Framework.Initialization. InitializationEngine: Initialize action failed for ‘Initialize on class EPiServer.Enterprise.Internal. EnterpriseInitialization,

Recently I was upgrading an Episerver project to v10.9.0 using nuget package manager. I also upgraded the database by entering Update-EPiDatabase -verbose:$true in the nuget package manager console in visual studio.

When trying to run the website I got a http 500 error. In the logs I could see the stacktracke below.

ERROR EPiServer.Framework.Initialization.InitializationEngine: Initialize action failed for 'Initialize on class EPiServer.Enterprise.Internal.EnterpriseInitialization, EPiServer.Enterprise, Version=10.9.0.0, Culture=neutral, PublicKeyToken=8fe83dea738b45b7'
EPiServer.ServiceLocation.ActivationException: Activation error occurred while trying to get instance of type PropertyContentReferenceListTransform, key "" ---> StructureMap.Building.StructureMapBuildException: Error while building type EPiServer.Core.Transfer.Internal.DependentContentTransfer. See the inner exception for details
1.) new DependentContentTransfer(*Default of IContentLoader*, *Default of IContentTypeRepository*, *Default of IPermanentLinkMapper*, *Default of ISiteDefinitionRepository*, *Default of ContentRootService*)
2.) EPiServer.Core.Transfer.Internal.DependentContentTransfer
3.) Instance of EPiServer.Core.Transfer.IDependentContentTransfer (EPiServer.Core.Transfer.Internal.DependentContentTransfer)
4.) new PropertyContentReferenceListTransform(*Default of IPermanentLinkMapper*, *Default of IContentLoader*, *Default of IDependentContentTransfer*)
5.) EPiServer.Core.Transfer.Internal.PropertyContentReferenceListTransform
6.) Instance of EPiServer.Core.Transfer.Internal.PropertyContentReferenceListTransform
7.) Container.GetInstance(EPiServer.Core.Transfer.Internal.PropertyContentReferenceListTransform)

… full stack trace at the end of the post.

The solution

In the connection string the MultipleActiveResultSets=True was missing. Once I added it, the site was working again.
<add name="EPiServerDB" connectionString="Data Source=XXXX;Initial Catalog=XXXX;Integrated Security=False;User ID=XXXX;Password=XXXX;Connect Timeout=30;<strong>MultipleActiveResultSets=True</strong>" providerName="System.Data.SqlClient" /></pre>

The full stacktrace

This is the full stacktrace of the error:

ERROR EPiServer.Framework.Initialization.InitializationEngine: Initialize action failed for 'Initialize on class EPiServer.Enterprise.Internal.EnterpriseInitialization, EPiServer.Enterprise, Version=10.9.0.0, Culture=neutral, PublicKeyToken=8fe83dea738b45b7'
EPiServer.ServiceLocation.ActivationException: Activation error occurred while trying to get instance of type PropertyContentReferenceListTransform, key "" ---> StructureMap.Building.StructureMapBuildException: Error while building type EPiServer.Core.Transfer.Internal.DependentContentTransfer. See the inner exception for details
1.) new DependentContentTransfer(*Default of IContentLoader*, *Default of IContentTypeRepository*, *Default of IPermanentLinkMapper*, *Default of ISiteDefinitionRepository*, *Default of ContentRootService*)
2.) EPiServer.Core.Transfer.Internal.DependentContentTransfer
3.) Instance of EPiServer.Core.Transfer.IDependentContentTransfer (EPiServer.Core.Transfer.Internal.DependentContentTransfer)
4.) new PropertyContentReferenceListTransform(*Default of IPermanentLinkMapper*, *Default of IContentLoader*, *Default of IDependentContentTransfer*)
5.) EPiServer.Core.Transfer.Internal.PropertyContentReferenceListTransform
6.) Instance of EPiServer.Core.Transfer.Internal.PropertyContentReferenceListTransform
7.) Container.GetInstance(EPiServer.Core.Transfer.Internal.PropertyContentReferenceListTransform)
---> System.TypeInitializationException: The type initializer for 'EPiServer.Web.SiteDefinition' threw an exception. ---> System.InvalidOperationException: There is already an open DataReader associated with this Command which must be closed first.
at System.Data.SqlClient.SqlInternalConnectionTds.ValidateConnectionForExecute(SqlCommand command)
at System.Data.SqlClient.SqlCommand.ValidateCommand(String method, Boolean async)
at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, TaskCompletionSource`1 completion, Int32 timeout, Task& task, Boolean asyncWrite)
at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method)
at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior, String method)
at EPiServer.DataAccess.Internal.ContentListDB.b__8_0()
at EPiServer.Data.Providers.Internal.SqlDatabaseExecutor.<>c__DisplayClass31_0`1.b__0()
at EPiServer.Data.Providers.SqlTransientErrorsRetryPolicy.Execute[TResult](Func`1 method)
at EPiServer.DataAbstraction.Internal.DefaultContentRootRepository.EnsureSystemRoots()
at EPiServer.DataAbstraction.Internal.DefaultContentRootRepository.Load(String rootName)
at EPiServer.Web.SystemDefinition.get_Current()
at EPiServer.Web.SiteDefinition.get_SiteAssetsRoot()
at EPiServer.Web.SiteDefinition.MakeReadOnly()
at EPiServer.Web.SiteDefinition..cctor()
--- End of inner exception stack trace ---
at EPiServer.DataAccess.Internal.SiteDefinitionDB.SiteDefinitionFromReader(IDataRecord r)
at EPiServer.DataAccess.Internal.SiteDefinitionDB.b__4_0()
at EPiServer.Data.Providers.Internal.SqlDatabaseExecutor.<>c__DisplayClass31_0`1.b__0()
at EPiServer.Data.Providers.SqlTransientErrorsRetryPolicy.Execute[TResult](Func`1 method)
at EPiServer.Web.Internal.DefaultSiteDefinitionRepository.List()
at EPiServer.Core.Transfer.Internal.DependentContentTransfer..ctor(IContentLoader contentLoader, IContentTypeRepository contentTypeRepository, IPermanentLinkMapper permanentLinkMapper, ISiteDefinitionRepository siteRepository, ContentRootService contentRootService)
at lambda_method(Closure , IBuildSession , IContext )
--- End of inner exception stack trace ---
at lambda_method(Closure , IBuildSession , IContext )
at StructureMap.Building.BuildPlan.Build(IBuildSession session, IContext context) in c:\BuildAgent\work\a395dbde6b793293\src\StructureMap\Building\BuildPlan.cs:line 151
at StructureMap.SessionCache.GetObject(Type pluginType, Instance instance, ILifecycle lifecycle) in c:\BuildAgent\work\a395dbde6b793293\src\StructureMap\SessionCache.cs:line 93
at StructureMap.SessionCache.GetDefault(Type pluginType, IPipelineGraph pipelineGraph) in c:\BuildAgent\work\a395dbde6b793293\src\StructureMap\SessionCache.cs:line 68
at StructureMap.Container.GetInstance(Type pluginType) in c:\BuildAgent\work\a395dbde6b793293\src\StructureMap\Container.cs:line 337
at EPiServer.ServiceLocation.ServiceLocatorImplBase.GetInstance(Type serviceType, String key)
--- End of inner exception stack trace ---
at EPiServer.ServiceLocation.ServiceLocatorImplBase.GetInstance(Type serviceType, String key)
at EPiServer.ServiceLocation.ServiceLocatorImplBase.GetInstance[TService]()
at EPiServer.Enterprise.Internal.EnterpriseInitialization.RegisterImportingEventHandlers(IDataImportEvents importerEvents, IServiceLocator factory)
at EPiServer.Enterprise.Internal.EnterpriseInitialization.Initialize(InitializationEngine context)
at EPiServer.Framework.Initialization.Internal.ModuleNode.Execute(Action a, String key)
at EPiServer.Framework.Initialization.Internal.ModuleNode.Initialize(InitializationEngine context)
at EPiServer.Framework.Initialization.InitializationEngine.InitializeModules()
EPiServer.ServiceLocation.ActivationException: Activation error occurred while trying to get instance of type PropertyContentReferenceListTransform, key "" ---> StructureMap.Building.StructureMapBuildException: Error while building type EPiServer.Core.Transfer.Internal.DependentContentTransfer. See the inner exception for details
1.) new DependentContentTransfer(*Default of IContentLoader*, *Default of IContentTypeRepository*, *Default of IPermanentLinkMapper*, *Default of ISiteDefinitionRepository*, *Default of ContentRootService*)
2.) EPiServer.Core.Transfer.Internal.DependentContentTransfer
3.) Instance of EPiServer.Core.Transfer.IDependentContentTransfer (EPiServer.Core.Transfer.Internal.DependentContentTransfer)
4.) new PropertyContentReferenceListTransform(*Default of IPermanentLinkMapper*, *Default of IContentLoader*, *Default of IDependentContentTransfer*)
5.) EPiServer.Core.Transfer.Internal.PropertyContentReferenceListTransform
6.) Instance of EPiServer.Core.Transfer.Internal.PropertyContentReferenceListTransform
7.) Container.GetInstance(EPiServer.Core.Transfer.Internal.PropertyContentReferenceListTransform)
---> System.TypeInitializationException: The type initializer for 'EPiServer.Web.SiteDefinition' threw an exception. ---> System.InvalidOperationException: There is already an open DataReader associated with this Command which must be closed first.
at System.Data.SqlClient.SqlInternalConnectionTds.ValidateConnectionForExecute(SqlCommand command)
at System.Data.SqlClient.SqlCommand.ValidateCommand(String method, Boolean async)
at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, TaskCompletionSource`1 completion, Int32 timeout, Task& task, Boolean asyncWrite)
at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method)
at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior, String method)
at EPiServer.DataAccess.Internal.ContentListDB.b__8_0()
at EPiServer.Data.Providers.Internal.SqlDatabaseExecutor.<>c__DisplayClass31_0`1.b__0()
at EPiServer.Data.Providers.SqlTransientErrorsRetryPolicy.Execute[TResult](Func`1 method)
at EPiServer.DataAbstraction.Internal.DefaultContentRootRepository.EnsureSystemRoots()
at EPiServer.DataAbstraction.Internal.DefaultContentRootRepository.Load(String rootName)
at EPiServer.Web.SystemDefinition.get_Current()
at EPiServer.Web.SiteDefinition.get_SiteAssetsRoot()
at EPiServer.Web.SiteDefinition.MakeReadOnly()
at EPiServer.Web.SiteDefinition..cctor()
--- End of inner exception stack trace ---
at EPiServer.DataAccess.Internal.SiteDefinitionDB.SiteDefinitionFromReader(IDataRecord r)
at EPiServer.DataAccess.Internal.SiteDefinitionDB.b__4_0()
at EPiServer.Data.Providers.Internal.SqlDatabaseExecutor.<>c__DisplayClass31_0`1.b__0()
at EPiServer.Data.Providers.SqlTransientErrorsRetryPolicy.Execute[TResult](Func`1 method)
at EPiServer.Web.Internal.DefaultSiteDefinitionRepository.List()
at EPiServer.Core.Transfer.Internal.DependentContentTransfer..ctor(IContentLoader contentLoader, IContentTypeRepository contentTypeRepository, IPermanentLinkMapper permanentLinkMapper, ISiteDefinitionRepository siteRepository, ContentRootService contentRootService)
at lambda_method(Closure , IBuildSession , IContext )
--- End of inner exception stack trace ---
at lambda_method(Closure , IBuildSession , IContext )
at StructureMap.Building.BuildPlan.Build(IBuildSession session, IContext context) in c:\BuildAgent\work\a395dbde6b793293\src\StructureMap\Building\BuildPlan.cs:line 151
at StructureMap.SessionCache.GetObject(Type pluginType, Instance instance, ILifecycle lifecycle) in c:\BuildAgent\work\a395dbde6b793293\src\StructureMap\SessionCache.cs:line 93
at StructureMap.SessionCache.GetDefault(Type pluginType, IPipelineGraph pipelineGraph) in c:\BuildAgent\work\a395dbde6b793293\src\StructureMap\SessionCache.cs:line 68
at StructureMap.Container.GetInstance(Type pluginType) in c:\BuildAgent\work\a395dbde6b793293\src\StructureMap\Container.cs:line 337
at EPiServer.ServiceLocation.ServiceLocatorImplBase.GetInstance(Type serviceType, String key)
--- End of inner exception stack trace ---
at EPiServer.ServiceLocation.ServiceLocatorImplBase.GetInstance(Type serviceType, String key)
at EPiServer.ServiceLocation.ServiceLocatorImplBase.GetInstance[TService]()
at EPiServer.Enterprise.Internal.EnterpriseInitialization.RegisterImportingEventHandlers(IDataImportEvents importerEvents, IServiceLocator factory)
at EPiServer.Enterprise.Internal.EnterpriseInitialization.Initialize(InitializationEngine context)
at EPiServer.Framework.Initialization.Internal.ModuleNode.Execute(Action a, String key)
at EPiServer.Framework.Initialization.Internal.ModuleNode.Initialize(InitializationEngine context)
at EPiServer.Framework.Initialization.InitializationEngine.InitializeModules()

Solving System.OutOfMemoryException and Arithmetic operation resulted in an overflow in Sitecore

Recently I bumped into these 2 .NET errors again after starting a Sitecore site: System.OutOfMemoryException and Arithmetic operation resulted in an overflow. The good news: it’s easy to fix them.

How to fix System.OutOfMemoryException

When you use IIS Express to run your website (using CTRL+F5 in Visual Studio) you might see a yellow page of death saying System.OutOfMemoryException. This could be a 32 / 64 bit conflict, so how can you fix this?
In Visual Studio, go to tools -> Projects and solutions -> Web projects -> check “Use the 64 bit version of IIS Express for web sites and projects”

How to fix Arithmetic operation resulted in an overflow

When I tried running the website again (CTRL+F5) a new error appeared: Arithmetic operation resulted in an overflow. After searching for a bit I bumped into this post on the Sitecore community with the solution

Strangely enough we found a WebCompanion Service Lavasoft that was causing a conflict.

Resolution

1. Open the services window: services.msc.

2. Stop the LavasoftTcpService (from services.msc)

3. Uninstall WebCompanion from Program and Features

4. Rename the two dlls (LavasoftTcpService.dll and LavasoftTcpService64.dll in C:\Windows\System32 and C:\Windows\SysWOW64) if still present

5. Reboot the machine

I hope this saves you some time 🙂

Sitecore Project Architecture – Part 1 Structure of Sitecore Tree

We will have a look at how to organize a multi site solution in Sitecore. In this part we’ll focus on the Structure in Sitecore, in a later post I will talk about the structure in Visual Studio.

There are multiple ways of structuring your solution and it all depends on the project requirements , best practices and your preferences. So feel free to drop your approach in the comments and mention why it works for you.

The basic approach is to have a Global folder and Site specific folders: “Site1”, “Site2”, …

The Global folder will contain templates and items that are not site specific or can be shared by multiple sites.

The Site specific folders will contain templates and items that are specific for that site.

Besides avoiding chaos tree horror and angry content editors, a good structure will also help you to apply more fine grained security settings as you can apply different security settings on each folder and therefore restrict access to certain folders for certain roles and users.

There are 4 main areas that can benefit from using the Global and Site specific folders: Content, Layout, Media Library and Templates.

sitecore_structure

Content

sitecore_structure_content

In the Global folder you can store

  • item to configure the whole platform
  • global dictionary (“buy now” call to action can be used on all the sites)
  • (datasource) items that can be referenced by all sites: you can have all your office items in the global folder and use them the sites you like.

In the Site specific folders you can store

  • item to configure the specific site
  • dictionary that only contains site specific translations
  • (datasource) items specific for this site

Layout

sitecore_structure_layout

It’s a good idea to replicate the same structure in the file system as this will avoid confusion and flexibility to deploy site specific components. So check the path field when you add a layout/rendering/sublayout.

Media Library

sitecore_structure_media_library

Templates

sitecore_structure_templates

The “YourClientName” folder is useful to differentiate templates that you created and templates folders that are automatically created when you install certain Sitecore modules

In the Global folder you can create templates (often for datasource items) used by components that can be used in all the sites.

In the Global/Fields folder you can create basic building blocks: each templates has just a few fields. A lot of templates need “description” field, so instead of adding a description field to each templates you can use Sitecore’s inheritance model. Even if your building block only contains 1 field, it means that the field will be consistently the same everywhere you inherit it. This is useful to avoid situations where you would name your field “summary” in one template and “short description” in another template and confuse content editors.
It will also simplify your (possibly code generated) models.

There is a nice best practices webinar that also talks about Sitecore structure:

Feel free to drop your approach, comments and feedback.

Hello world!

No need to change the title of this post. Traditionally it’s the first program that every developer writes in a new language. This blog is mainly here to make it very easy to google my own notes and share them with the community.

What about the title? Well “syntactic sugar is syntax within a programming language that is designed to make things easier to read or to express. It makes the language “sweeter” for human use: things can be expressed more clearly, more concisely, or in an alternative style that some may prefer. (Wikipedia)

What will it be about, well anything tech related, I worked with Adobe Flex, Developed an app for Microsoft Surface Table, Created BeTrains for Windows Phone, javascript, HTML, a foosball table powerd by Raspberry Pi, ASP.NET Webforms/MVC …
The last couple of years I mainly focused on 2 big CMS systems: Sitecore and EPiServer.

I studied and worked in Belgium, done projects in London and Germany and currently leading EPiServer and Sitecore solutions in Dubai.

When I am not exploring technologies I am traveling the world so if you want a break from all the tech stuff, check out my adventures on GingerAroundTheWorld.com