When developing a React or Next.js app, you might need to import external JavaScript files through the HTML <script>
tag. This is especially true when dealing with ads.
The problem here is that those external scripts frequently use the document.write()
statement. This does not only come with restrictions but its usage is discouraged and reported with a warning in the browser console. In some cases, it may even be blocked by the browser for speed reasons, making the imported external script broken.
Since you typically cannot change the code of these external scripts, this can easily become a headache. Luckily, there is a solution!
Let’s see how the PostScribe library can help solve the issue in React or Next.js, in both JavaScript and TypeScript.
Why document.write() Should Be Avoided
In this 2016 article, Google delved into why document.write()
represents an issue for users on slow connections. In detail, this statement can slow down the loading time of a web page by dozens of seconds. This happens because before the browser can render a web page, it needs to generate the entire DOM tree. And if the HTML parser encounters a script, it has to stop and launch it before continuing.
The problem is that if the script dynamically injects another script with document.write()
, the parser is forced to wait for the new external resource to be downloaded. This can lead to one or more network roundtrips and delay the rendering time of the web page.
This is such a problem that modern browsers can choose to prevent document.write()
from being executed based on the user’s connection speed. In other words, you may not rely on it entirely.
When used and not blocked, the Chrome DevTools Console will warn you with the following message:
[Violation] Avoid using document.write().
Likewise, this is what the Firefox DevTools Console prints when using it:
An unbalanced tree was written using document.write() causingdata from the network to be reparsed.
Google Lighthouse does not like it as well and will report the non-blocked document.write()
statements with the following message:
The solution to document.write()
is virtually easy to find. You should simply replace any document.write()
statements with explicit DOM manipulations statements, such as .appendChild()
. On the other hand, you may not have access to these external scripts and cannot change their code. In this case, PostScribe is the solution.
document.write()
Issues Addressing the
Remote scripts, especially ads, block the page from doing anything else while they load. They contribute a large % to load times which affects your bottom line. Asynchronous ads do not block the page and can be delivered after core content.
Why is it so hard to deliver ads asynchronously? Because they may contain calls to
document.write
, which expects to be handled synchronously. PostScribe lets you deliver a synchronous ad asynchronously without modifying the ad code. — PostScribe’s official GitHub page
Let’s assume that https://external-domain.com/ads.js
is your external script file using document.write()
. This will take care of loading an ad in the <div id="#adv"></div>
HTML element.
Adding <div id="#adv"></div>
to your DOM and importing the external script with the following line would lead to the aforementioned problems:
<script src={"https://external-domain.com/ads.js"} />
Let’s now see how to avoid them.
First of all, you need to install PostScribe with the following command:
npm install --save postscribe
This way, the postscribe
npm library will be added to your project’s dependencies. Learn more about postscribe
here.
Now, you can define an ExternalAd
React component loading the external script asynchronously with postscribe
as follows:
import React, { useEffect, useRef } from "react" import postscribe from "postscribe" export default function ExternalAd() { const advRef = useRef(null) useEffect(() => { if (advRef) { postscribe( advRef.current, "https://external-domain.com/ads.js", ) } }, [advRef]) return <div id="adv" ref={advRef}></div> }
This component will load the https://external-domain.com/ads.js
external script inside the <div id="#adv"></div>
DOM element only when the <div id="adv"></div>
element is loaded. This is because it uses its React reference as a flag inside the useEffect()
hook. Learn more about React references here.
Now, you can use this component whenever you want, with the following JSX statement:
<ExternalAd />
Since the external resource is now loaded asynchronously thanks to postscribe
, the browser should not block it anymore, although it may still complain about document.write()
.
Making PostScribe Work in TypeScript
The following import statement will not work on TypeScript:
import postscribe from "postscribe"
This is because PostScribe seems not to support TypeScript at the time of writing. Fortunately, tackling this issue is very simple. All you need to do is create a postscribe.d.ts
file as follows:
declare module "postscribe"
This will help TypeScript load postscribe
with no problem. Learn more about this workaround here.
Conclusion
In this article, we learned why document.write()
represents a problem for users on slow connections, why it should be use used as a result, and how to prevent modern browsers from blocking it when loading external resources. All of this is possible thanks to PostScribe, which allows you to load synchronous resources asynchronously and solve the issues coming with document.write()
without having to modify the code of the external resources, which is typically not an option.
Thanks for reading! I hope that you found this article helpful.