A better approach to include GPT Ads in a Vue.js Single-Page Application
It’s been a while since I wrote Google Publisher Tag for DFP in a Vue.js Single-Page Application . Since the writing of the article I’ve refactored (and simplified my solution). The main trigger was a wrong assumption I made, as stated in the final disclaimer of the article:
The setup I tried to explain in this article worked well for me but it might probably be improved (e.g. I suspect there’s no actual reason to destroy all slots when changing routes, some of them maybe you could just reuse).
Well, it turned out there was a reason. Even though it was not intentional, I actually reused some Ad slots. I was
destroying slots on beforeCreate
hook for my page component and creating them again on created
. Finally, calling
display on mounted
. Remember:
import { destroySlots as beforeCreate } from "@/common/ads";
import { slotsResultsPage as created } from "@/common/ads";
import { displayAds as mounted } from "@/common/ads";
export default {
name: "ResultsPageComponent",
components: {
// ...
},
beforeCreate,
created,
mounted,
// ...
}
The thing is ResultsPageComponent
also allows the user to perform a new search, and of course, the result for this new
search will reuse the ResultsPageComponent
to present the new results. So, neither destroySlots()
,
slotsResultsPage()
, nor displayAds()
will be called. Apparently this is not a problem, Ads will be there, but the fact
is reusing slots is a bad idea because you’ll lose impressions per PV.
So how can we be sure Ad slots will be destroyed and created again every time we navigate to a new URL even if the new URL is reusing components? Easy, move this Ad destroy/create logic to Vue’s router.
router.beforeEach((to, from, next) => {
destroySlots();
next();
});
router.afterEach((to, from) => {
createPageSlots(to.name);
});
destroySlots()
is exactly the same function I was hooking to page components beforeCreate
. createPageSlots()
just
takes the name of the route we’re navigating to and calls createSlots()
passing the Ads definitions required for this
particular URL.
This way we’ve fixed this issue with page components reuse and we’ve lighten up all of our page components. But there’s
a problem now. On the first load everything is fine, but if we do a second search, displayAds()
won’t be called. It’s
still hooked to mounted
for the page component. So, of course, it won’t be called when Vue reuses the page component.
How do we fix this? We cannot call displayAds()
right after createPageSlots()
in router’s afterEach
navigation
guard, because at this point the Ad element might not be present in the DOM yet. The solution is in the App Component:
<div id="app">
<Advertising :id="'MySite_Home_Leaderboard'" class="leaderboard"/>
<Header/>
<router-view/>
<Footer/>
</div>
Whether next route reuses the page component or not, some component inside the router-view
is going to be updated.
Therefore, the main App
component will also be updated, so let’s call displayAds()
at this point. We just have to
keep in mind that at the first load updated
won’t be called, but mounted
will. So, we’ll do:
import { displayAds } from "@/common/ads";
export default {
name: "App",
components: {
Advertising,
Footer,
Header,
},
mounted() {
displayAds();
},
updated() {
displayAds();
}
};
Or just:
import { displayAds as mounted, displayAds as updated } from "@/common/ads";
export default {
name: "App",
components: {
Advertising,
Footer,
Header,
},
mounted,
updated
};
You can check a working example on the repo: jordinebot/vuejs-gpt-demo
As always, comments, questions, doubts, other approaches/theories/experiences as well as spell, grammar or any kind of language corrections are more than welcome! Thanks for reading!