Skip to content

Commit

Permalink
Script examples (#31181)
Browse files Browse the repository at this point in the history
* Add script component examples

* Update script examples

* Refactor code

* Remove unused files

* Fix linter

* Fix lint error

* Fix prettier
  • Loading branch information
goncy committed Nov 16, 2021
1 parent b79591c commit d550b13
Show file tree
Hide file tree
Showing 13 changed files with 338 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/basic-features/script.md
Expand Up @@ -7,6 +7,7 @@ description: Next.js helps you optimize loading third-party scripts with the bui
<details>
<summary><b>Examples</b></summary>
<ul>
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/script-component">Script Component</a></li>
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/with-google-tag-manager">Google Tag Manager</a></li>
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/with-google-analytics">Google Analytics</a></li>
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/with-facebook-pixel">Facebook Pixel</a></li>
Expand Down
34 changes: 34 additions & 0 deletions examples/script-component/.gitignore
@@ -0,0 +1,34 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local

# vercel
.vercel
33 changes: 33 additions & 0 deletions examples/script-component/README.md
@@ -0,0 +1,33 @@
# Script component

This example shows different strategies that can be used for the [`next/script` component](https://nextjs.org/docs/basic-features/script):

- [Loading Polyfills](./pages/polyfill.js)
- [Lazy loading](./pages/lazy.js)
- [Executing code after loading](./pages/onload.js)
- [Inline scripts](./pages/inline.js)
- [Forwarding attributes](./pages/attributes.js)

## Preview

Preview the example live on [StackBlitz](http://stackblitz.com/):

[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/script-component)

## Deploy your own

Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/script-component&project-name=script-component&repository-name=script-component)

## How to use

Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:

```bash
npx create-next-app --example script-component script-component-app
# or
yarn create next-app --example script-component script-component-app
```

Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
13 changes: 13 additions & 0 deletions examples/script-component/package.json
@@ -0,0 +1,13 @@
{
"private": true,
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
},
"dependencies": {
"next": "latest",
"react": "^17.0.2",
"react-dom": "^17.0.2"
}
}
20 changes: 20 additions & 0 deletions examples/script-component/pages/_app.js
@@ -0,0 +1,20 @@
import Link from 'next/link'

import '../styles/global.css'

const MyApp = ({ Component, pageProps, router }) => {
const pathname = router.pathname

return (
<>
<Component {...pageProps} />
{pathname !== '/' && (
<Link href="/">
<a>See all examples</a>
</Link>
)}
</>
)
}

export default MyApp
22 changes: 22 additions & 0 deletions examples/script-component/pages/attributes.js
@@ -0,0 +1,22 @@
import Script from 'next/script'

export default function Inline() {
return (
<>
{/* Attributes are forwarded */}
<Script
src="https://www.google-analytics.com/analytics.js"
id="analytics"
nonce="XUENAJFW"
data-test="analytics"
/>

<main>
<h1>Forwarded attributes</h1>
<h5>
Open devtools and check that attributes has been forwarded correctly.
</h5>
</main>
</>
)
}
36 changes: 36 additions & 0 deletions examples/script-component/pages/index.js
@@ -0,0 +1,36 @@
import Link from 'next/link'

export default function Index() {
return (
<main>
<h1>Script component examples</h1>
<ul>
<li>
<Link href="/polyfill">
<a>Polyfill</a>
</Link>
</li>
<li>
<Link href="/lazy">
<a>Lazy Loading</a>
</Link>
</li>
<li>
<Link href="/onload">
<a>Executing code after loading</a>
</Link>
</li>
<li>
<Link href="/inline">
<a>Inline scripts</a>
</Link>
</li>
<li>
<Link href="/attributes">
<a>Forwarding attributes</a>
</Link>
</li>
</ul>
</main>
)
}
29 changes: 29 additions & 0 deletions examples/script-component/pages/inline.js
@@ -0,0 +1,29 @@
import Script from 'next/script'

export default function Inline() {
return (
<>
{/* Execute arbitrary code */}
<Script id="show-banner" strategy="lazyOnload">
{`document.getElementById('banner').classList.remove('hidden')`}
</Script>

{/* Or */}

{/* <Script
id="show-banner"
dangerouslySetInnerHTML={{
__html: `document.getElementById('banner').classList.remove('hidden')`
}}
/> */}

<main>
<h1>Inline scripts</h1>
<h5 id="banner" className="hidden">
This is initially hidden but its being shown because the `Script`
component removed the `hidden` class.
</h5>
</main>
</>
)
}
42 changes: 42 additions & 0 deletions examples/script-component/pages/lazy.js
@@ -0,0 +1,42 @@
import { useCallback, useEffect, useState } from 'react'
import Script from 'next/script'

export default function Lazyload() {
const [log, setLog] = useState([])

const addLog = useCallback(
(text) => {
setLog((log) => log.concat({ time: new Date(), text }))
},
[setLog]
)

useEffect(() => {
addLog(`Page loaded window.FB is undefined`)
}, [addLog])

return (
<>
{/* We lazy load the FB SDK */}
<Script
src="https://connect.facebook.net/en_US/sdk.js"
strategy="lazyOnload"
onLoad={() =>
addLog(`script loaded correctly, window.FB has been populated`)
}
/>

<main>
<h1>Lazy Loading FB sdk</h1>
<h5>You can check `window.FB` on browser console</h5>
<ul>
{log.map(({ time, text }) => (
<li key={+time}>
{time.toISOString()}: {text}
</li>
))}
</ul>
</main>
</>
)
}
46 changes: 46 additions & 0 deletions examples/script-component/pages/onload.js
@@ -0,0 +1,46 @@
import { useMemo, useState } from 'react'
import Script from 'next/script'

export default function Onload() {
const [stripe, setStripe] = useState(null)
const methods = useMemo(
() =>
stripe
? Object.entries(stripe.stripe).filter(
([_key, value]) => typeof value === 'function'
)
: [],
[stripe]
)

function handleLoad() {
const stripe = window.Stripe('pk_test_1234')

console.log('Stripe loaded: ', stripe)

setStripe({ stripe })
}

return (
<>
{/* We load Stripe sdk */}
<Script
id="stripe-js"
src="https://js.stripe.com/v3/"
onLoad={handleLoad}
/>

<main>
<h1>Executing code after loading</h1>
<div>
<p>Stripe methods: </p>
<ul>
{methods.map(([method]) => (
<li key={method}>{method}</li>
))}
</ul>
</div>
</main>
</>
)
}
48 changes: 48 additions & 0 deletions examples/script-component/pages/polyfill.js
@@ -0,0 +1,48 @@
import { useEffect, useRef, useState } from 'react'
import Script from 'next/script'

import s from '../styles/polyfill.module.css'

export default function Polyfill() {
const ref = useRef()
const [lastIntersection, setIntersection] = useState(new Date())

useEffect(() => {
const observer = new IntersectionObserver(
(intersections) => {
const isIntersecting = intersections[0]?.isIntersecting

if (isIntersecting) {
setIntersection(new Date())
}
},
{
rootMargin: '45px',
}
)

observer.observe(ref.current)

return () => observer.disconnect()
}, [])

return (
<>
{/* We ensure that intersection observer is available by polyfilling it */}
<Script
src="https://polyfill.io/v3/polyfill.min.js?features=IntersectionObserverEntry%2CIntersectionObserver"
strategy="beforeInteractive"
/>

<main className={s.container}>
<h1>IntersectionObserver Polyfill</h1>
<h5>Scroll down to see when was the last intersection</h5>
<section className={s.section}>
<span ref={ref}>
Last intersection at {lastIntersection.toTimeString()}
</span>
</section>
</main>
</>
)
}
8 changes: 8 additions & 0 deletions examples/script-component/styles/global.css
@@ -0,0 +1,8 @@
* {
font-family: sans-serif;
box-sizing: border-box;
}

.hidden {
display: none;
}
6 changes: 6 additions & 0 deletions examples/script-component/styles/polyfill.module.css
@@ -0,0 +1,6 @@
.section {
height: 400vh;
display: flex;
align-items: center;
justify-content: center;
}

0 comments on commit d550b13

Please sign in to comment.