How do I load mapbox in Next.js? - javascript

I'm using Next.js and their next/script component to load mapbox in a <Map /> component. This seems to work.
Map.js
import Script from 'next/script';
export default Map() {
const createMap = () => {
// set access token
mapboxgl.accessToken = 'xxxxxxxxxxxxxxx...';
// create map
const map = new mapboxgl.Map({...});
}
return (
<>
<Script
onLoad={() => {
createMap();
}}
src="https://api.mapbox.com/mapbox-gl-js/v2.3.1/mapbox-gl.js"
/>
<div id="map"></div>
</>
);
}
1 - If I have multiple instances of <Map /> on a page, will Next.js load this script multiple times?
Is there a better way to load this script once, globally and synchronously? It doesn't seem to work if I load it in my _document.js page. Or is this not an issue?
2 - I also have to load the CSS file. Right now I have it loaded on the page that has the map on it.
pages/contact.js
import Head from 'next/head';
export default function PageContact() {
return (
<Head>
<link href="https://api.mapbox.com/mapbox-gl-js/v2.3.1/mapbox-gl.css" rel="stylesheet" />
</Head>
);
}
If I add maps to other pages, I'd have to include this CSS file on all those other pages, resulting in a lot of duplication. Should I abstract this <Head> to its own component and include that on each page?
Or should I be moving this <Head> component into the <Map> component? It makes sense to centralize this style in one place, but at that point, just like the <Script>, I'm guessing Next.js would load the CSS file each time the component is instantiated, so if a page had more than one map on it, the CSS would be loaded multiple times.
What is the best way to load Mapbox in a Next.js project?

Related

Next Js fails to load third party script in _document.js [duplicate]

I try to understand how the next.js Script tag with the strategy beforeInteractive works. For testing i just used lodash. But i keep getting a ReferenceError: _ is not defined. I thought when a script is loaded with beforeInteractive it should be globally available inside my page Component since it get injected into the initial Html from the server and i could use it for example in the useEffect hook to alter a div.
Can someone explain to me why it's not working or what i'm doing wrong?
I don't installed it via npm because im trying to figure out how it works.
I have a simple _document.js and i added a Next.js script tag with the strategy beforeInteractive to this _document.js. The next.js docs says:
This strategy only works inside _document.js and is designed to load scripts that are needed by the entire site (i.e. the script will load when any page in the application has been loaded server-side).
import { Html, Head, Main, NextScript } from 'next/document'
import Script from 'next/script'
export default function Document() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
<Script
src="https://unpkg.com/lodash#4.17.20"
strategy="beforeInteractive"
></Script>
</body>
</Html>
)
}
Then i have a simple page Component inside the pages folder. I added the getServerSideProps function to use ServerSideRendering.
If you export a function called getServerSideProps (Server-Side Rendering) from a page, Next.js will pre-render this page on each request using the data returned by getServerSideProps.
import Head from 'next/head';
import {useEffect, useState} from 'react';
const TestComponent = () => {
const [change,setChange] = useState('not changed');
useEffect(()=> {
console.log(_);
setChange(_.join(['one','two'],' - '));
});
return (
<>
<Head>
<title>Test</title>
</Head>
<div>{change}</div>
</>
);
};
export async function getServerSideProps(context) {
return {
props: {},
}
}
export default TestComponent;
Update
Seems like it is indeed a bug which is fixed but not released yet
https://github.com/vercel/next.js/discussions/37098
Putting aside the fact that you should be importing Lodash as a node module, there does seem to be an issue when using next/script in _document (no matter what the external script actually is).
It turns out this is a Next.js bug that has been addressed in this PR in pre-release version v12.1.7-canary.8. To fix the issue in your project simply update Next.js to version >=12.2.0 (npm install next#latest).
As an alternative, you can use the <script> tag directly in the _document's <Head> with the defer property. This closely matches what the next/script would output.
import { Html, Head, Main, NextScript } from 'next/document'
export default function Document() {
return (
<Html>
<Head>
<script
type="text/javascript"
src="https://unpkg.com/lodash#4.17.20/lodash.js"
defer
></script>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
First and foremost, I'm failing to see virtually any reason you'd want to do this, when you can (and should) simply use install it to node_modules. You're also going to possibly run the risk of the bundle having issues if the library type isn't a module and the next configuration requires a module.
Solution based on the question:
There's two ways.
Firstly, see the docs on this exact thing.
Please use the above method mentioned in the docs.
If that's not an option for whatever reason...
The second is a less than ideal, but working solution.
Create a folder for your static files. Ex: <root>/static/js/hello.js. Then in your _document file,
<script type="text/javascript" src="/static/hello.js"></script>

React/NextJS passing both children and other arguments to component

I'm using NextJS with a global PageLayout wrapper for all of my pages that sets the head and creates the wrapping divs for the page. However I am now trying to set a custom title tag for each page, which requires me to pass an argument to the PageLayout component with the page title I want to set. However when trying to pass both the PageLayout children and the page_title argument - the page_title property does not get passed in.
Here is what I have for the PageLayout component:
import Head from 'next/head'
import styles from "../../../styles/layout/PageLayout.module.scss"
const PageLayout = ({children, page_title}) => {
console.log(`-------------------------------------------------------`)
console.log(`Page Title: ${page_title}`)
console.log('PAGE Children (NEXT LINE):')
console.log(children)
return (
<div className={styles.container}>
<Head>
<title>{page_title}</title>
<meta name="description" content="JWS Fine Art" />
<link rel="icon" href="/JWS_ICON.png" />
<link rel="preconnect" href="https://fonts.gstatic.com"/>
<link href="https://fonts.googleapis.com/css2?family=Alegreya+Sans+SC:wght#300&family=Lato:wght#300&display=swap" rel="stylesheet"/>
</Head>
<main className={styles.main}>
{children}
</main>
</div>
)
}
export default PageLayout;
And here is an example of how I am passing in the page_title and children for one of my pages:
return (
<PageLayout page_title={"Orders"}>
<div className={styles.main_container}>
<div className={styles.main_body}>
<h2 className={styles.module_title}>Order Management:</h2>
{page_jsx}
</div>
</div>
</PageLayout>
)
And when I try to access that page, I see the following console output from the PageLayout logging:
This all being said, I'm wondering if it is just not possible to pass both children and other arguments? I haven't been able to find any info on this issue online, and no method I have tried has worked, so am coming here. Would greatly appreciate any input anyone might have!
Issue ended up being due to attempting to use "title" as a var name which seems to be a global variable name that can't be used - and also due to me failing to update both the mobile and desktop JSX for the page.
The functionality does work as expected, and you can pass both children and additional props. Thanks to #PrinceAgrawal for making me check my work properly :P
For a working example of this functionality, you can view this example repo:
https://github.com/tsmith165/page_layout_example

External script not loading on page change

I am trying to add a Trustpilot widget to my Gatsby.js website. It is required to load an external script from Trustpilot CDN.
<script type="text/javascript" src="//widget.trustpilot.com/bootstrap/v5/tp.widget.bootstrap.min.js" async></script>
I have tried multiple ways to add this script to my component. The first thing I tried was React Helmet. I added using the following code:
<Helmet>
<script type="text/javascript" src="//widget.trustpilot.com/bootstrap/v5/tp.widget.bootstrap.min.js" async></script>
The script seems to load when I initially load a page. Once I navigate to a different page, the styling goes away. As I reload, it comes back.
I tried adding the script inside componentDidMount()
componentDidMount() {
var addScript = document.createElement('script');
addScript.setAttribute('src', '//widget.trustpilot.com/bootstrap/v5/tp.widget.bootstrap.min.js');
document.body.appendChild(addScript);
}
If you want your script (or any other component) to be persistent through your site, you need to use wrapPageElement or wrapRootElement APIs. Both APIs are suggested to be placed in gatsby-browser.js as well as in gatsby-ssr.js
Disclaimer: componentDidMount() will be triggered every time the DOM tree is loaded, it won't work for your use-case.
The issue here is that you are adding a non-React asset, not a component. You can try:
export const wrapPageElement = ({ element, props }) => {
return <SomeWrapper {...props}>{element}</SomeWrapper>;
};
Then, create a component called SomeWrapper and place your <Helmet>:
const SomeWrapper = (props) =>{
return <div>
<Helmet>
<script type="text/javascript" src="//widget.trustpilot.com/bootstrap/v5/tp.widget.bootstrap.min.js" async />
</Helmet>
{props.children}
</div>
}

Next.js Loads <script> tags but it doesn't execute them

I'm integrating an existing React app into Next.js for mainly SEO features.
i pasted the css files links inside the <Header> tag in Next.js and they seem to work just fine. when i tried to do the same with the javascript files using the <script> tag, it doesn't execute the code inside those scripts. but i can see them loaded with http 200 in the chrome dev tools.
I tried using a library called Loadjs from npm to load the scripts inside componentDidMount but i got the exact same result as using <script> tag.
is there a proper way to do such simple thing in Next.js that i'm not aware of ?
Here's the code included in the pages/index.js file.
import React from "react"
import Head from 'next/head'
import MyAwesomeComponent from "../components/mycomponent.js"
export default () => (
<div>
<Head>
<link type="text/css" rel="stylesheet" href="static/css/chatwidget.css" />
<link type="text/css" rel="stylesheet" href="static/css/download.css" />
<script type="text/javascript" src="static/libs/jquery.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/malihu-custom-scrollbar-plugin#3.1.5/jquery.mCustomScrollbar.concat.min.js"></script>
<script type="text/javascript" src="static/libs/bootstrap.min.js"></script>
<script type="text/javascript" src="static/libs/owl.carousel.min.js"></script>
<script type="text/javascript" src="static/scripts/chatHead.js"></script>
<script type="text/javascript" src="static/libs/jquery.magnific-popup.js"></script>
</Head>
<MyAwesomeComponent /> {/* a simple React component that returns : <p>Hello World </p>*/}
</div>
)
Sorry for the late answer.
it turned out that all the scripts i linked missed one script that would actually run the functions for each action.
This works to me:
Create a folder for your static files:
<root>/public/static/script.js
in your index.js at <root>/pages/index.js
import Head from 'next/head';
import MyAwesomeComponent from '../components/mycomponent';
export default () => (
<div>
<Head>
<script type="text/javascript" src="/static/script.js"></script>
</Head>
<MyAwesomeComponent />
</div>
)
Note that static is the name I used in this example, it's not a requirement, it would work with any other folder name.
With the below approach you can easily put a script file's raw script text into a generated Next.js HTML page's <head> without screwing around with character escaping, formatting and general pretending that we aren't actually just building an HTML page in the end anyways.
There are many use cases you may want a script to run without going to network. Ex: 3rd party scripts, monitoring / analytics scripts that expect to be in the <head> without a separate network load. Many of these come minified, mangled, you-name-it and are just supposed to be copy, paste, move on with life.
Next.js makes this very hard pretending that everything with web development is magically React and Webpack all the time now (I wish right?)
The best developer experience way I've found is to do this:
_document.js
...
<Head>
<script type="text/javascript" dangerouslySetInnerHTML={{ __html: process.env.rawJsFromFile }}></script>
</Head>
...
next.config.js
https://github.com/vercel/next.js/blob/canary/packages/next/next-server/server/config.ts#L33
module.exports = {
env: {
rawJsFromFile: fs.readFileSync('./rawJsFromFile.js').toString()
}
}
rawJsFromFile.js
alert('Freedom!!!!!!!!!!!!!!!');
// and other 3rd party script junk, heck even minified JS is fine too if you need
Hope this saves someone from hours of frustration that I endured coming up with this... 😭
You can also run js code this
<script
dangerouslySetInnerHTML={{
__html: `
let a = 1;
functionCall();
`,
}}
></script>
With Next.js v11 and onward, you can use the Next component Script
https://nextjs.org/blog/next-11#script-optimization
<Script
src="..."
strategy="beforeInteractive"
/>
May this helps you Nextjs public folder
Move your static folder into public folder in your root directory
export default () => (
<div>
<Head>
<link type="text/css" rel="stylesheet" href="/static/css/chatwidget.css" />
<link type="text/css" rel="stylesheet" href="/static/css/download.css" />
<script type="text/javascript" src="/static/libs/jquery.min.js"></script>
...
</Head>
<MyAwesomeComponent />
</div>
)
This is what I tried and it worked for me.
I used two files entry-script.js and main-script.js. I put these like this
<root>/static/entry-script.js and <root>/static/main-script.js
The content of entry-script.js is below.
(function (d, t) {
t = d.createElement("script");
t.setAttribute("src", "/static/main-script.js");
d.getElementsByTagName("head")[0].appendChild(t);
})(document);
and the main logic is in the file main-script.js.
In the file _doucment.js of NextJS I included my file entry-script.js in body like below
class MyDocument extends Document {
render() {
return (
<Html>
<Head>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
/>
</Head>
<body>
<script
type="text/javascript"
src="/static/entry-script.js"
></script>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default MyDocument;
MyDocument.getInitialProps = async (ctx) => {
// Resolution order
//
// On the server:
// 1. app.getInitialProps
// 2. page.getInitialProps
// 3. document.getInitialProps
// 4. app.render
// 5. page.render
// 6. document.render
//
// On the server with error:
// 1. document.getInitialProps
// 2. app.render
// 3. page.render
// 4. document.render
//
// On the client
// 1. app.getInitialProps
// 2. page.getInitialProps
// 3. app.render
// 4. page.render
// Render app and page and get the context of the page with collected side effects.
const sheets = new ServerStyleSheets();
const originalRenderPage = ctx.renderPage;
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) => sheets.collect(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
// Styles fragment is rendered after the app and page rendering finish.
styles: [
...React.Children.toArray(initialProps.styles),
sheets.getStyleElement(),
],
};
};
I wrote an article elaborating on this question, hopefully it comes in handy:
https://www.devtwins.com/blog/how-to-add-a-third-party-script-to-a-nextjs-website
Or if you want to try another way to import Js file like I did
import { useEffect } from "react";
function MyApp({ Component, pageProps }) {
useEffect(() => {
import("../styles/bootstrap.bundle.min.js");
}, []);
return <></>
};

React - insertion of third party script tag inside render rather than page head causes app failure

I'm writing a React component to use Mozilla's PDF.js project to render a document in HTML. I've gotten it working successfully when the example's Create-React-App project includes
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/1.8.527/pdf.min.js"></script>
in the index.html head block. If I remove it and insert it in the component render method, the application breaks. Is there a way to add a <script> tag to the body of the application
Background
According to the examples, there are two ways you can add a document to a page: render each page with the canvas tag, or use pdfjs-dist/web/pdf-viewer.js
Code
Following the Webpack examples (since this is a React project), I came up with the following code:
import React, {Component} from 'react';
import pdflib from 'pdfjs-dist';
import {PDFJS} from 'pdfjs-dist/web/pdf_viewer'
import 'pdfjs-dist/web/pdf_viewer.css';
export default class PDF extends Component {
constructor(props) {
super(props);
pdflib.PDFJS.workerSrc = require('file-loader!pdfjs-dist/build/pdf.worker');
this.file = props.file;
this.pdfViewer = null;
this._containerId = 'viewer_container';
this.pagesInit = this.pagesInit.bind(this);
}
componentDidMount() {
this._loadDocument();
}
pagesInit(e) {
this.pdfViewer.currentScaleValue = 'page-width';
}
_loadDocument() {
const container = document.getElementById(this._containerId);
this.pdfViewer = new pdflib.PDFJS.PDFViewer({
container: container
});
container.addEventListener('pagesinit', this.pagesInit);
const loadingTask = pdflib.getDocument(this.file);
loadingTask.promise.then((document) => {
this.pdfViewer.setDocument(document);
}).catch((err) => {
console.log(err);
});
}
render() {
return(
<div id={this._containerId}>
<div className="pdfViewer" id="viewer"></div>
</div>
)
}
}
However, for a reason unknown to me, I need to include the build of pdf.js in the head of the index page of the application so it is loaded before the bundle.
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/1.8.527/pdf.min.js"></script>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Without it, the document will not render. This seems directly related to script ordering.
Part of the convenience of an external React component is its self-contained, and this script requirement means this component certainly is not self contained.
I've attempted to use react-async-script-loader to wrap the component in a Higher Order Component and retrieve the script, but the same error happens.
Is there another solution I should look at?

Categories