Overriding html.js not working in GatsbyJS - javascript

I'm following the directions here on Gatsby's website to customize the <head> tag. I want to add Modernizr and Google's WebFont libraries. I copied .cache/default-html.js and put it in src/html.js, and then I added the script tags the libraries, but they aren't showing up on my localhost:8000.
Is there some Gatsby cache that needs cleared when trying to do a custom html.js? Or is there another way to add these to the <head> tag?
EDIT: It turns out I was trying to put a <script></script> tag in the JSX of the html.js.

Use React Helmet to customize the <head> tag. Check out their documentation for more examples.
import React from "react";
import {Helmet} from "react-helmet";
class Application extends React.Component {
render () {
return (
<div className="application">
<Helmet>
<meta charSet="utf-8" />
<title>My Title</title>
<link rel="canonical" href="http://example.com/example" />
</Helmet>
...
</div>
);
}
};
install with:
npm i --save gatsby-plugin-react-helmet react-helmet
update gatsby-config.js:
plugins: ['gatsby-plugin-react-helmet']
For a working example, create a new build with gatsby new gatsby-site and
have look at the component in src/layouts/index.js
<Helmet
title="Gatsby Default Starter"
meta={[
{ name: 'description', content: 'Sample' },
{ name: 'keywords', content: 'sample, something' },
]}
/>

Related

How to load Google Tag Manager with next/script component (Next.js 11)?

Next.js v11 released a new Script component which has different strategies.
It is recommended to load Google TagManager with afterInteractive strategy.
I've tried
// _app.js
import Script from 'next/script';
class MyApp extends App {
public render() {
const { Component, pageProps } = this.props;
return (
<>
<Script strategy="afterInteractive">
{`(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':` +
`new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],` +
`j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=` +
`'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);` +
`})(window,document,'script','dataLayer','GTM-XXXXXXX');`}
</Script>
<Component {...pageProps} />
</>
);
}
}
export default MyApp;
It works fine, it loads google tag manager, but the problem is that it injects the same script on every page nav, which makes duplicate tags.
How to utilize the new Script component?
You must set an id to your <Script> component because it has inline content (no src attribute).
Next can check if it has been already rendered or not and you will not have these duplications.
This is the eslint rule associated from Next :
next/script components with inline content require an id attribute to be defined to track and optimize the script.
See: https://nextjs.org/docs/messages/inline-script-id
So your solution could be :
<Script id="gtm-script" strategy="afterInteractive">{`...`}</Script>
You should probably also install eslint for your next project :
Either run next lint or install eslint next config :
yarn add -D eslint eslint-config-next
and define the file .eslintrc.json with this content at least :
{
"extends": ["next/core-web-vitals"]
}
Information about next.js eslint configuration.
My final solution was to break apart the GTM script.
Putting the initial dataLayer object on the window in _document page.
// _document.js
import Document, { Head, Html, Main, NextScript } from 'next/document';
export default class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head>
<meta charSet="utf-8" />
<script
dangerouslySetInnerHTML={{
__html:
`(function(w,l){` +
`w[l] = w[l] || [];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});` +
`})(window,'dataLayer');`,
}}
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
Loading the GMT script with Script component (which is not allowed to be used in the _document page)
// _app.js
import Script from 'next/script';
class MyApp extends App {
public render() {
const { Component, pageProps } = this.props;
return (
<>
<Script src={`https://www.googletagmanager.com/gtm.js?id=GMT-XXXXXXX`} />
<Component {...pageProps} />
</>
);
}
}
export default MyApp;
Your inline scripts require an "id" parameter, so that Next can internally check and avoid loading the scripts again.
The documentation mentioned it but they missed this in the first release.
It was later added as a patch so upgrading your Next would solve this issue
Patch - https://github.com/vercel/next.js/pull/28779/files/11cdc1d28e76c78a140d9abd2e2fb10fc2030b82
Discussion Thread - https://github.com/vercel/next.js/pull/28779

How do I use Next JS's Head tag

I've only just started looking at Next JS for my project but am having issues with the Head tag not working. According to the documentation, I should just be able to import head from next/head and insert the title tag. However, it's not working for me, be it using the Layout component which gets imported to each page or directly injecting it.
Here is the code as a layout (/components/layout.jsx)
import Link from 'next/link';
import Head from 'next/head';
export default function Layout({
children,
title = 'Default Title'
}) {
return(
<div>
<Head>
<title>{title}</title>
<meta charSet='utf-8' />
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css"></link>
</Head>
<nav>
<div className='nav-wrapper'>
<ul class='right hide-on-med-and-down'>
<li>
<Link href='/'>
<a>Home</a>
</Link>
</li>
<li>
<Link href='/about'>
<a>About</a>
</Link>
</li>
</ul>
</div>
</nav>
{children}
{/* Footer to go in here */}
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
</div>
)
}
And this is the about page (/pages/about.jsx)
import React from 'react';
import Layout from '../components/layouts';
function About() {
return(
<Layout>
<div>
<h1>Test Page</h1>
</div>
</Layout>
);
}
export default About;
It just doesn't work at all. The title is not updated and if I inspect the components, I can't see anything that I've added. I originally thought that it's because I set up the Next app myself and I did something wrong. However, I tried bootstrapping it with npx create-next-app and I am seeing the same issue.
Any help is appreciated!
Cheers
First, create a new component, eg. seo.jsx
import Head from "next/head";
const SEO = ({ pageTitle, pageDescription }) => (
<Head>
<title>{pageTitle}</title>
<meta name="description" content={pageDescription} />
...
</Head>
);
export default SEO;
Second, import to your page (eg. home.jsx):
import SEO from "#components/seo"; //change to your path
const Home = () => {
...
return (
<div>
<SEO pageTitle="Homepage" pageDescription="Welcome to my website" />
...
</div>
);
};
export default Home;
In this scenario you will be able to change title and description (or any other props you need) in any page of your app, without worrying about meta key duplicates.
More info here.
I took your exact code and created a sandbox and I can see the title working as expected provided you use .jsx in your import
import Layout from '../components/layouts';
to
import Layout from '../components/layouts.jsx';
or if you want to use without extension, use .js instead
**Code Sandbox - ** https://codesandbox.io/s/vigorous-wind-4e035?file=/pages/index.js
Next JS Discussion - https://github.com/vercel/next.js/issues/2391#issuecomment-311735189
I didn't do anything specific to the title but to make the sandbox work and to print the document title.
I modified paths without the folder and the default IndexPage next sandbox
Modified Index to print the document title
Instead of importing components/layout, imported components/layout.jsx
The Codesandbox crashed when i tried to import the file without the JSX extension.
Let me know if this helps or we can explore further!
I also got that the head tag is not a working issue, what I did was added a tag for cover and the layout of the body
ex:
<main>
<head>
{..head elements }
</head>
<div>
{ other elements }
<div>
</main>

How to import external html file to React app?

I have a project in a middle of development. And I need to use external landing page as a home page. Therefore, I need to import landings index.html, but it has its own folders with css and js(mainly Jquery code).
I wanted to import it as <iframe src={html}></iframe> into my project but my app doesn't seem to load htmls.
What are best ways to import html files that use own jquery code to react?
This is a bit tricky, and there might be other ways (perhaps better) to achieve the same result. Also, I would consider the performance impact of loading multiple libraries into an existing React app.
With that humble disclaimer out of the way, one way to do this would be to include jQuery directly into React's main index.html page using <script> tags, this will make $ globally available across the app:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
</body>
</html>
Once this is done, place the landing page project folder (along with its dependencies) inside the public directory:
Then, from the main app component load the desired landing page using fetch, then use .text() to transform the retrieved page into regular text.
Use setState to set the retrieved HTML into the app state, then inside render() use a regular <div> container to store the landing page and use the React attribute dangerouslySetInnerHTML to set HTML inside that target container.
Finally, I pass an anonymous function (as a second parameter to setState) and use jQuery's getScript() to load and execute the required JS libraries that the landing page depends on.
In the example, I loaded Bootstrap's JS, which is needed to power the Carousel.
Bootstrap's CSS is loaded directly from the landing page's HTML file using a standard <link> tag.
import React from "react";
class App extends React.Component {
state = {
page: null
};
componentDidMount() {
fetch("landing-page-one/index.html")
.then(result => {
return result.text();
})
.then(page => {
this.setState(
{
page: { __html: page }
},
() => {
window.$.getScript("landing-page-one/js/index.js");
}
);
});
}
render() {
const { page } = this.state;
return (
<>
<h2>
<span>Inserting project using React's </span>
<code>dangerouslySetInnerHTML</code>:
</h2>
<div
dangerouslySetInnerHTML={page && page}
/>
</>
);
}
}
export default App;
Working example here.
I'm just going to through this out there as a spitball... I've never tried it, but why not directly replace index.html and not render anything on that page? You would have to either adjust the build somehow... I have no idea how to, or manually put it after the react app finishes building. I'm sure it would be a bug nightmare, but it honestly sounds a little less of a hastle than an iframe which might give you some weird UI behavior. React-Server, even though it says server, really just takes stringified JSX and I think html translates it back to workable code. You could also try that approach, but this does sound like a nightmare project.
I was also trying to find out a solution and figured out two ways for different use cases:
If your HTML file contains <div> and other child tags (that can be nested under other <div> or <body> tags) then this approach will work for you:
First you need to configure webpack to be able to parse HTML files.
You can either do that with npm eject or use another module
react-app-rewired - and then add a HTML loader in overrides.
Then import the HTML file and use a parser. I believe it's better than using dangerouslySetInnerHTML.
Example with Method 1:
const parse = require("html-react-parser");
const docs = require("../../public/redoc-static.html").default;
const ApiDocs = (props: any) => {
return (
<Box sx={{ display: "flex", height: "100vh" }}>
<Box>{parse(docs)}</Box>
</Box>
);
};
If your HTML file contains a <html> tag (is a complete file) following the above approach would give the very obvious error <html> cannot appear as a child of <div>. In that case, the workaround I found is to open the whole HTML file in a different tab of the browser with _blank:
Example with Method 2:
window.open(filename, "_blank")
Note: The HTML file has to reside in the public folder of your project.

How to include local javascript on a Gatsby page?

I'm a total React newbie and I guess there is something fundamental I don't quite understand here. A default Gatsby page looks like this. Is there a way to use a local .js file somewhat like this?
<script src="../script/script.js"></script>
What I would like to achieve is to have react ignore script.js but still have the client side use it. A default Gatsby page looks like this, is it possible to do somerthing like that there?
import React from "react"
import { Link } from "gatsby"
import Layout from "../components/layout"
import Image from "../components/image"
import SEO from "../components/seo"
const IndexPage = () => (
<Layout>
<SEO title="Home" keywords={[`gatsby`, `application`, `react`]} />
<h1>Hi people</h1>
<p>Welcome to your new Gatsby site.</p>
<p>Now go build something great.</p>
<div style={{ maxWidth: `300px`, marginBottom: `1.45rem` }}>
<Image />
</div>
<Link to="/page-2/">Go to page 2</Link>
</Layout>
)
After several hours of frustration I finally stumbled upon discussion on GitHub that solved this for me. In Gatsby, there is a thing called static folder, for which one use case is including a small script outside of the bundled code.
Anyone else in the same situation, try proceeding as follows:
Create a folder static to the root of your project.
Put your script script.js in the folder static.
Include the script in your react dom with react-helmet.
So in the case of the code I posted in my original question, for instance:
import React from "react"
import Helmet from "react-helmet"
import { withPrefix, Link } from "gatsby"
import Layout from "../components/layout"
import Image from "../components/image"
import SEO from "../components/seo"
const IndexPage = () => (
<Layout>
<Helmet>
<script src={withPrefix('script.js')} type="text/javascript" />
</Helmet>
<SEO title="Home" keywords={[`gatsby`, `application`, `react`]} />
<h1>Hi people</h1>
<p>Welcome to your new Gatsby site.</p>
<p>Now go build something great.</p>
<div style={{ maxWidth: `300px`, marginBottom: `1.45rem` }}>
<Image />
</div>
<Link to="/page-2/">Go to page 2</Link>
</Layout>
)
Notice the imports
import Helmet from "react-helmet"
import { withPrefix, Link } from "gatsby"
and the script element.
<Helmet>
<script src={withPrefix('script.js')} type="text/javascript" />
</Helmet>
This would have saved hours of my time, hopefully this does it for someone else.
There are many ways to add scripts in GatsbyJS...
To execute a script on a specific page
create a stateless ScriptComponent.js file and place it inside your /src folder.
in your ScriptComponent.js use require() to execute the script inside useEffect() like this:
const ScriptComponent = ({
src, // if internal,put a path relative to this component
onScriptLoad = () => {}, // cb
appendToHead = false,
timeoutDuration = 10,
defer = false,
isExternal = false,
}) => {
useEffect(() => {
setTimeout(() => {
if (isExternal) {
const script = document.createElement('script');
script.src = src;
script.onload = onScriptLoad;
defer
? script.defer = true
: script.async = true;
appendToHead
? document.head.appendChild(script)
: document.body.appendChild(script);
} else { // for internal scripts
// This runs the script
const myScript = require(src);
}
}, timeoutDuration);
}, []);
return null;
};
To run it on client-side, you could check the window object inside your script.js file if you didn't run it in useEffect:
if(typeof window !== 'undefined' && window.document) {
// Your script here...
}
finally, go to the page you want to execute the script in it (e.g. /pages/myPage.js ), and add the component <ScriptComponent />
If you want to execute a script globally in (every component/page) you could use the html.js file.
first, you'll have to extract the file (in case you didn't) by running:
cp .cache/default-html.js src/html.js
inside your html.js file:
<script dangerouslySetInnerHTML= {{ __html:`
// your script here...
// or you could also reuse the same approach as in useEffect above
`}} />
Just create gatsby-ssr.js file on root folder
and add the following pattern for your scripts folder
import React from 'react'
export const onRenderBody = ({ setPostBodyComponents }) => {
setPostBodyComponents([
<script
key="https://code.jquery.com/jquery-3.2.1.slim.min.js"
src="https://code.jquery.com/jquery-3.2.1.slim.min.js"
integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
crossOrigin="anonymous"
defer
/>,
<script
key="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"
src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"
integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"
crossOrigin="anonymous"
defer
/>,
<script
key="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"
src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"
integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"
crossOrigin="anonymous"
defer
/>
])
}
Then, you at the end of dom you'll see the links to scripts
If you'd like to use a Gatsby plugin, which to me is no different from using an external library like Helmet (plugins are npm packages after all), you could use gatsby-plugin-load-script.
You can provide either the url to the src attribute or a local path. If you're going to store your JS in a local file such as some-minified-js.min.js - make sure to store in the static directory at the root of your project.
Once you do this, you can access via the global object:
global.<object or func name here>
For example, I was trying to include a very small JS library via a minified file, so I stored the file in /static/my-minified-library.min.js and then:
Installed the plugin: npm i --save gatsby-plugin-load-script
Added this to my gatsby-config.js
plugins: [
{
resolve: "gatsby-plugin-load-script",
options: {
src: "/my-minified-library.min.js",
},
},
],
Accessed in my react component like so:
useEffect(() => {
const x = new global.MyImportedLibraryObject();
}, []}
Gatsby uses html.js in the src folder. Not index.html like most react projects.
Example html.js file:
import React from "react"
import PropTypes from "prop-types"
export default class HTML extends React.Component {
render() {
return (
<html {...this.props.htmlAttributes}>
<head>
<meta charSet="utf-8" />
<meta httpEquiv="x-ua-compatible" content="ie=edge" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
{this.props.headComponents}
</head>
<body {...this.props.bodyAttributes}>
{this.props.preBodyComponents}
<div
key={`body`}
id="___gatsby"
dangerouslySetInnerHTML={{ __html: this.props.body }}
/>
{this.props.postBodyComponents}
</body>
</html>
)
}
}
HTML.propTypes = {
htmlAttributes: PropTypes.object,
headComponents: PropTypes.array,
bodyAttributes: PropTypes.object,
preBodyComponents: PropTypes.array,
body: PropTypes.string,
postBodyComponents: PropTypes.array,
}
For adding custom Javascript using dangerouslySetInnerHTML inside src/html.js:
<script
dangerouslySetInnerHTML={{
__html: `
var name = 'world';
console.log('Hello ' + name);
`,
}}
/>
You can try adding your js there but, note that your js may not work as expected. You can always look into react-helmet for more dynamic apps and adding scripts to <head>.
Gatsby Documentation: https://www.gatsbyjs.org/docs/custom-html/
You can do this very easily with the Gatsby plugin "gatsby-plugin-load-script."
Simply do this:
Create a folder named static at the root of your gatsby app
Place your script in it
Add the following configuration in gatsby-config.js
{
resolve: 'gatsby-plugin-load-script',
options: {
src: '/test-script.js', // Change to the script filename
},
},
I'm not sure if anyone still needs this answer, but here it goes:
The answer by Elliot Marques is excellent. If you need it for a local file, upload the script to Github and use a service like JSDelivr. It saves a lot of time and stress.
React works with dynamic DOM. But for rendering it by browser, your web server should send a static index page, where React will be included as another script tag.
So, take a look on your index.html page, which you can find in public folder. There you could insert your script tag in the header section, for example.

How to add a dynamic class to body tag in Gatsby.js?

Obviously, that's not an easy task, as the only thing that changes in the html.js template file by default are the head meta tags and the content.
The meta tags are handled by the Helmet component ({head.title.toComponent()} and {head.meta.toComponent()}) and the HTML changes inside the template are managed by React. (<div id="react-mount" dangerouslySetInnerHTML={{ __html: this.props.body }} />)
The body tag however is outside the scope of React, which is why I can't figure out how to change it on-the-fly when I navigate from page to page. That's exactly what I'd need though as I wanna apply a different body background to each page.
I know that I could solve this by using the exports.onRouteUpdate in gatsby-browser.js, but I would like the class to be present even if JS is disabled in the browser. Means I'd like it to be there even if I export without the bundle.js file, just generating the static site HTML.
React-helmet now supports adding attributes to body element as well.
So, if you want to add a class to a specific component/page, you can do something like this:
import Helmet from 'react-helmet'
// Inside your component
<Helmet
bodyAttributes={{
class: 'new-class-for-body'
}}
/>
// or
<Helmet>
<body className="new-class-for-body" />
</Helmet>
It does look like react-helmet supports dynamically/statically setting a class on the <html> element.
They don't want to support setting classes on the body though... https://github.com/nfl/react-helmet/issues/182
If you really need to support body classes, then this module does something very similar to react-helmet but for body classes https://github.com/iest/react-body-classname
February 2023:
Since the release of gatsby#5.5.0, the preferred way of doing this is using the Gatsby Head API. (The gatsby-plugin-react-helmet plugin has been deprecated.)
In your page file, export a <Head> component with a <body className /> element inside:
export const Head = () => <body className="your-class-name" />
You can also do this alongside other <Head> changes, e.g.:
export const Head = () => (
<>
<html lang="en" />
<title>Hello World</title>
<meta name="description" content="Hello World" />
<body className="home-page" />
</>
)

Categories

Resources