-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(docs): Gatsby script component (#35647)
- Loading branch information
Showing
2 changed files
with
294 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -67,19 +67,21 @@ For each script, it can be helpful to understand the business purpose, relative | |
|
||
There may also be relatively unimportant scripts that have a high performance costs; these are also good candidates for removal. | ||
|
||
#### Step 2: lazy load or inline scripts | ||
#### Step 2: Use the Gatsby Script component to load scripts performantly | ||
|
||
One of the lowest-hanging fruits is to set your scripts to load lazily rather than "eagerly" (the default). Any `<script>` tags being embedded manually can be set to `<script async>`. | ||
> Support for the Gatsby Script API was added in `[email protected]`. | ||
For slightly more effort, you can get additional performance gains; rather than loading third-party scripts from external sources, you can inline scripts in your code to reduce the cost of a network call. | ||
Gatsby includes a built-in `<Script>` component that unlocks the ability to load your scripts with various strategies that are beneficial for performance: | ||
|
||
There are a number of places to put an inlined script, depending whether you need it to execute immediately upon loading or can defer execution. | ||
- After your page hydrates (`post-hydrate` strategy) | ||
- After the browser's main thread becomes idle (`idle` strategy) | ||
- In a web worker, avoiding doing any work on the main thread at all (`off-main-thread` strategy, experimental) | ||
|
||
- _No deferring_: This is a good default. Put the script in [onPreRenderHTML](/docs/ssr-apis/#onPreRenderHTML) to have it added to your document tree. You can place it lower in your DOM to have parsed and evaluated later. | ||
- _Some deferring_: You can place the script in [onClientEntry](/docs/browser-apis/#onClientEntry) to have it execute after page load, but before the browser renders the page. | ||
- _More deferring_: You can place the script in [onInitialClientRender](/docs/browser-apis/#onInitialClientRender) to have it execute after the browser renders the page. | ||
Each strategy comes with its own set of considerations, but they are all more performant than the regular blocking `<script>`, `<script async>`, and `<script defer>`. | ||
|
||
Note that if you are already using [html.js](/docs/custom-html/), you should modify that file to include your snippet instead of using `onPreRenderHTML`. This will have the same behavior. | ||
The Gatsby `<Script>` component also supports inline scripts, so for your smaller scripts you can save the cost of a network request _and_ determine when the inline script is loaded. | ||
|
||
For more information, see the [Gatsby Script reference documentation](/docs/reference/built-in-components/gatsby-script/). | ||
|
||
### Reduce your JavaScript bundle cost | ||
|
||
|
284 changes: 284 additions & 0 deletions
284
docs/docs/reference/built-in-components/gatsby-script.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,284 @@ | ||
--- | ||
title: Gatsby Script API | ||
--- | ||
|
||
> Support for the Gatsby Script API was added in `[email protected]`. | ||
Gatsby includes a built-in `<Script>` component that aids in loading third-party scripts performantly. | ||
|
||
It offers a convenient way to declare different loading strategies and a default loading strategy that gives Gatsby users strong performance out of the box. | ||
|
||
## Using Gatsby Script in your site | ||
|
||
Here is an example of how you can import and use the `<Script>` component in your site's JSX or TSX source files: | ||
|
||
```jsx | ||
import * as React from "react" | ||
// highlight-next-line | ||
import { Script } from "gatsby" | ||
|
||
function MyPage() { | ||
// highlight-next-line | ||
return <Script src="https://my-example-script" /> | ||
} | ||
|
||
export default MyPage | ||
``` | ||
|
||
It can also be used in the following [Gatsby SSR](/docs/reference/config-files/gatsby-ssr/) and [Gatsby Browser](/docs/reference/config-files/gatsby-browser/) APIs: | ||
|
||
- `wrapPageElement` | ||
- `wrapRootElement` | ||
|
||
> Note - If you use one of these APIs, it is recommended that you implement it both in Gatsby SSR _and_ Gatsby Browser. A common pattern is to define a single function that you import and use in both files. | ||
Here's an example using `wrapPageElement` in both Gatsby SSR and Gatsby Browser without duplicating your code: | ||
|
||
```jsx:title=gatsby-shared.jsx | ||
import React from "react"; | ||
import { Script } from "gatsby"; | ||
|
||
export const wrapPageElement = ({ element }) => { | ||
return ( | ||
<> | ||
{element} | ||
<Script src="https://my-example-script" /> | ||
</> | ||
); | ||
}; | ||
``` | ||
|
||
```jsx:title=gatsby-ssr.jsx | ||
export { wrapPageElement } from "./gatsby-shared"; | ||
``` | ||
|
||
```jsx:title=gatsby-browser.jsx | ||
export { wrapPageElement } from "./gatsby-shared"; | ||
``` | ||
|
||
> Note - Other examples on this page will exclude the import statements and function component unless it is necessary. | ||
## Migrating existing scripts | ||
|
||
For most scripts, you can migrate to Gatsby Script by importing the `<Script>` component in your file and changing the lowercase `script` tag names to capitalized `Script` tag names: | ||
|
||
```diff | ||
import { Script } from "gatsby"; | ||
|
||
-<script src="https://my-example-script" /> | ||
+<Script src="https://my-example-script" /> | ||
``` | ||
|
||
By default, the `<Script>` component will load your script after hydration. For more information on declaring loading strategies, see the [Strategies](#strategies) section. | ||
|
||
## Scripts with sources and inline scripts | ||
|
||
There are two types of scripts that you can tell the `<Script>` component to load: | ||
|
||
- Scripts with sources | ||
- Inline scripts | ||
|
||
### Scripts with sources | ||
|
||
Scripts with sources provide a `src` property like this: | ||
|
||
```jsx | ||
<Script src="https://my-example-script" /> | ||
``` | ||
|
||
The `<Script>` component will use the value of `src` to deduplicate loading, so if you include two scripts on the same page with the same `src`, only one will load. | ||
|
||
If for some reason you need to load two scripts with the same sources on the same page, you can provide an optional, unique `id` property to each and the `<Script>` component will attempt to load both: | ||
|
||
```jsx | ||
<Script id="first-unique-id" src="https://my-example-script" /> | ||
<Script id="second-unique-id" src="https://my-example-script" /> | ||
``` | ||
|
||
### Inline scripts | ||
|
||
Inline scripts must include a unique `id` property and can be defined in two ways: | ||
|
||
- Via React's special [`dangerouslySetInnerHTML`](https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml) property | ||
- Via a template literal | ||
|
||
Here's a look at both: | ||
|
||
```jsx | ||
<Script id="first-unique-id" dangerouslySetInnerHTML={{ __html: `alert('Hello world')` }} /> | ||
<Script id="second-unique-id">{`alert('Hello world')`}</Script> | ||
``` | ||
|
||
Functionally, both of these ways of defining inline scripts are equivalent. | ||
|
||
## Strategies | ||
|
||
You can declare a loading strategy by passing a `strategy` property. These are the available loading strategies: | ||
|
||
- `post-hydrate` - Loads after the page has hydrated | ||
- `idle` - Loads after the page has become idle | ||
- `off-main-thread` (experimental) - Loads off the main thread in a web worker via [Partytown](https://partytown.builder.io) | ||
|
||
The best strategy to use depends on the functionality of the script you would like to include. | ||
|
||
A general rule of thumb is if your script can afford to be loaded later (as many analytics scripts can be), then start with `off-main-thread` or `idle` and move to earlier loading strategies if necessary. | ||
|
||
Here's how you can define these strategies in the `<Script>` component: | ||
|
||
```jsx | ||
<Script src="https://my-example-script" strategy="post-hydrate" /> | ||
<Script src="https://my-example-script" strategy="idle" /> | ||
<Script src="https://my-example-script" strategy="off-main-thread" /> | ||
``` | ||
|
||
Additionally, Gatsby exports a `ScriptStrategy` enum that you can use in TSX files if you prefer: | ||
|
||
```tsx | ||
import { Script, ScriptStrategy } from "gatsby" | ||
|
||
<Script src="https://my-example-script" strategy={ScriptStrategy.postHydrate} /> | ||
<Script src="https://my-example-script" strategy={ScriptStrategy.idle} /> | ||
<Script src="https://my-example-script" strategy={ScriptStrategy.offMainThread} /> | ||
``` | ||
|
||
### Off main thread strategy (experimental) | ||
|
||
The `off-main-thread` strategy leverages [Partytown](https://partytown.builder.io) under the hood, so it requires further configuration. See the [Partytown configuration](https://partytown.builder.io/configuration) documentation for complete details. | ||
|
||
Here is an example configuring the `<Script>` component with [Google Analytics](https://analytics.google.com/analytics/web/): | ||
|
||
```tsx | ||
import { Script, ScriptStrategy } from "gatsby" | ||
|
||
// `process.env.GTM` is your Google Analytics 4 identifier defined in your `.env.production` and `.env.development` files | ||
|
||
<Script | ||
src={`https://www.googletagmanager.com/gtag/js?id=${process.env.GTM}`} | ||
strategy={ScriptStrategy.offMainThread} | ||
forward={[`gtag`]} | ||
/> | ||
<Script id="gtag-config" strategy={ScriptStrategy.offMainThread}> | ||
{` | ||
window.dataLayer = window.dataLayer || []; | ||
window.gtag = function gtag() { window.dataLayer.push(arguments); } | ||
gtag('js', new Date()); | ||
gtag('config', ${process.env.GTM}, { send_page_view: false }) | ||
`} | ||
</Script> | ||
``` | ||
|
||
#### Forward collection | ||
|
||
Gatsby will collect all `off-main-thread` scripts on a page, and automatically merge any [Partytown forwarded events](https://partytown.builder.io/forwarding-events) defined via the `forward` property into a single configuration for each page: | ||
|
||
```jsx | ||
<Script | ||
src={`https://www.googletagmanager.com/gtag/js?id=${process.env.GTM}`} | ||
strategy={ScriptStrategy.offMainThread} | ||
// highlight-next-line | ||
forward={[`gtag`]} | ||
/> | ||
``` | ||
|
||
The `forward` property is the only Partytown-specific property that is handled by the `<Script>` component. | ||
|
||
#### Proxy configuration | ||
|
||
Many scripts [require a proxy to work in Partytown](https://partytown.builder.io/proxying-requests), so Gatsby has built-in proxy functionality to make this easier. To keep the proxy secure, you must define the absolute URLs you want proxied in your Gatsby config. | ||
|
||
Here's how you would do that for the Google Analytics example above: | ||
|
||
```js:title=gatsby-config.js | ||
import dotenv from "dotenv"; | ||
|
||
dotenv.config({ | ||
path: `.env.${process.env.NODE_ENV}`, | ||
}); | ||
|
||
module.exports = { | ||
siteMetadata: { | ||
title: `Gatsby`, | ||
}, | ||
// highlight-next-line | ||
partytownProxiedURLs: [ | ||
`https://www.googletagmanager.com/gtag/js?id=${process.env.GTM}` | ||
], | ||
} | ||
``` | ||
|
||
This works out of the box when running your site via `gatsby develop`, `gatsby serve` and Gatsby Cloud. | ||
|
||
Hosting on other providers requires support for Gatsby's [`createRedirect`](/docs/reference/config-files/actions/) action to rewrite requests from `/__third-party-proxy?url=${YOUR_URL}` to `YOUR_URL` with a 200 status code. You may need to check with your hosting provider to see if this is supported. | ||
|
||
#### Debugging | ||
|
||
You can leverage Partytown's [vanilla config](https://partytown.builder.io/configuration#vanilla-config) to enable debug mode for your off-main-thread scripts: | ||
|
||
```jsx:title=gatsby-ssr.js | ||
import React from "react"; | ||
|
||
export const onRenderBody = ({ setHeadComponents }) => { | ||
setHeadComponents([ | ||
<script | ||
key="test" | ||
dangerouslySetInnerHTML={{ | ||
__html: `partytown = { debug: true };`, | ||
}} | ||
/>, | ||
]); | ||
}; | ||
``` | ||
|
||
You may need to adjust your dev tools to the verbose log level in order to see the extra logs in your console. | ||
|
||
It is recommended that you only make use of the `debug` property in Partytown's vanilla config to avoid unexpected behavior. | ||
|
||
#### Limitations | ||
|
||
By leveraging [Partytown](https://partytown.builder.io), scripts that use the `off-main-thread` strategy must also be aware of the [limitations mentioned in the Partytown documentation](https://partytown.builder.io/trade-offs). While the strategy can be powerful, it may not be the best solution for all scenarios. | ||
|
||
In addition: | ||
|
||
- `off-main-thread` scripts load only on server-side render (e.g. initial page render, page reload, etc.) and not on client-side render (e.g. navigation via Gatsby Link) | ||
- `off-main-thread` scripts cannot use the `onLoad` and `onError` callbacks | ||
|
||
### `onLoad` and `onError` callbacks | ||
|
||
Scripts with sources loaded with the `post-hydrate` or `idle` strategies have access to two callbacks: | ||
|
||
- `onLoad` - Called once the script has loaded | ||
- `onError` - Called if the script failed to load | ||
|
||
> Note - Inline scripts and scripts using the `off-main-thread` strategy **do not** support the `onLoad` and `onError` callbacks. | ||
Here is an example using the callbacks: | ||
|
||
```tsx | ||
<Script | ||
src="https://my-example-script" | ||
onLoad={() => console.log('success') )} | ||
onError={() => console.log('sadness') )} | ||
/> | ||
``` | ||
|
||
Duplicate scripts (scripts with the same `id` or `src` attributes) will execute `onLoad` and `onError` callbacks despite not being injected into the DOM. | ||
|
||
Access to the `onLoad` and `onError` callbacks also enables the ability to load scripts dependently. Here's an example showing how to load the second script after the first: | ||
|
||
```tsx | ||
import React, { useState } from "react" | ||
import { Script } from "gatsby" | ||
|
||
function MyPage() { | ||
const [loaded, setLoaded] = useState<boolean>(false) | ||
return ( | ||
<> | ||
// highlight-next-line | ||
<Script src="https://my-example-script" onLoad={() => setLoaded(true)} /> | ||
{loaded && <Script src="https://my-other-example-script" />} | ||
</> | ||
) | ||
} | ||
|
||
export default Page | ||
``` |