adding a script on Gatsby with setPostBodyComponents only on certain pages - javascript

Is it possible to render a script that has been inserted within the body tag (using setPostBodyComponents) only in certain pages other than all of them ?
Any ideas if this would be possible ?

As you can see in gatsby-ssr-js docs, onRenderBody exposes a bunch of props where there's a pathname.
pathname {string}
The pathname of the page currently being rendered.
That said, you can try something like:
const React = require("react")
exports.onRenderBody = ({ setPostBodyComponents, pathname }) => {
const SCRIPT_PAGES = ['/page-1', '/page-2'];
if(SCRIPT_PAGES.includes(pathname)){
setPostBodyComponents([
<script dangerouslySetInnerHTML={{ __html:`your script here`}} />,
]);
}
};
In that case, your SCRIPT_PAGES will hold the pages where the script will be inserted. Using that approach, you can tweak it as you want.

Related

How to dynamically add scripts received from CMS to <Head>

I have an endpoint which returns an array of scripts. These scripts can either be with a src i.e. <script src="https://www.google-analytics.com/analytics.js"><script> attribute OR they may also have body content i.e. something like; <script>(function(// do something to generate scripts))<script> which will create new scripts on the fly.
I'm currently using getServerSideProps to get the data from each individual page and then in _app.js I am getting that data from pageProps. The data is landing in fine, however I was hoping to simply return the script code in <Head> but obviously React escapes any HTML that isn't set using dangerouslySetInnerHTML so that makes this task somewhat more difficult.
I have looked through the documentation and see no feasible way to just simply put the script in string form in the <Head> tag. This would be the simplest solution.
Here's my idea;
I would check if the script has a src, if it doesn't we can assume that it has body content and thus will create new scripts on the fly.
If the script has an src, we extract it and return a JSX script tag and populate the src with what we got back from the CMS.
If the script has no src tag, we remove all script tags and then use dangerouslySetInnerHTML to set the contents of the script to a JSX script instance.
Is there an another way to do this, with fewer steps? It feels like there should be.
You can use html-to-react. Here is an example:
import Head from "next/head";
import { Parser } from "html-to-react";
const IndexPage = () => {
const { parse } = new Parser();
const scriptString = "<script>console.log('hello')</script>";
return (
<>
<Head>{parse(scriptString)}</Head>
<div>Hello World.</div>
</>
);
};
export default IndexPage;

Nextjs - first level dynamic route

I want to have user profiles in my app at domain.com/:username .How do I do it? creating a new folder in /pages will create a new URL section like /user/:username which I don't want.
Just name your file inside pages as [username].js
Creating a dynamic route
You can create a dynamic route by putting the filename in brackets. For instance:
pages/[id].js
pages/[slug].js
pages/posts/[slug].js
pages/[author]/[slug].js
pages/[author]/bio.js
Notice how the dynamic route can be at any level. It doesn't simply need to be at the root of the pages folder, and it doesn't need to be the last level of the url either. Also notice that you can name the dynamic route anything you want. You're not restricted to just using [id] or [slug].
In your case, use pages/[username].js
Accessing the route name
If you create a dynamic route at pages/posts/[slug].js, any page at https://example.com/posts/slug-of-the-post will lead to that page. However, you likely want different content on each page (i.e., that the user is on https://example.com/posts/slug-of-the-post, not https://example.com/posts/slug-of-a-different-post. For this, you need to access the contents of the route.
Inside the page
You can use the router to get the name of the route.
// pages/posts/[slug].js
import { router } from 'next/router'
export default function PostPage() {
const router = useRouter()
const slug = router.query.slug // "slug" because that was the name of the file
return <>{/* page contents */} </>
}
So on page https://example.com/posts/slug-of-the-post, the variable slug will be slug-of-the-post.
If you used [id].js, use router.query.id instead.
GetStaticProps/GetServerSideProps
Using the server-side functions, the process is very similar.
// pages/posts/[slug].js
// bottom of the file
export async function getServerSideProps(context) {
const slug = context.params.slug
return {
props: {},
}
}
Docs
By the way, I'd also recommend checking out the docs if you haven't already:
https://nextjs.org/docs/routing/dynamic-routes

Add query string to ALL links in application

Here is the situation... I have a Next.js app that has been up for a bit, we have a ton of code already writen.
Recently we started running some ads and we need to understand how they are doing...
Someone could land on our site with either of the two URLs.
www.site.com/page // user came to our site NOT from ad
www.site.com/page?ad=campaign1 // user came from one of our ads.
If a user comes to our site with the "ad" querystring, I would like to append that to ALL links on our site.
How would I go about doing that in react/nextjs? we have a ton of components already built and even some blog posts where we are just rendoring raw HTML.
Without going and editing a zillion components. How would I go about appending the query string to all links?
Thanks
You could create a custom Link component, as a wrapper to next/link, that would check for the query string and add it to the href of the Next.js Link.
// components/link.jsx
import NextLink from 'next/link';
import { useRouter } from 'next/router';
const Link = ({ children, href }) => {
const router = useRouter();
const [, queryString] = router.asPath.split('?');
const hrefWithQuery = `${href}${queryString ? `?${queryString}` : ''}`;
return <NextLink href={hrefWithQuery}>{children}</NextLink>;
};
export default Link;
Then replace all imports of next/link with the path to the new component wherever they are used (a simple search & replace would do).

Use GraphQL data in gatsby-browser?

I have an app with some route ID's (basically a bunch of sections in a long SPA) that I have defined manually. I fetch these in gatsby-browser.js and use them in conjunction with shouldUpdateScroll, checking if the route ID exist, and in that case, scroll to the position of the route/section.
Example:
export const shouldUpdateScroll = ({ routerProps: { location } }) => {
const container = document.querySelector('.site')
const { pathname } = location
const projectRoutes = [`project1`, `project2`]
if (projectRoutes.indexOf(pathname) !== -1) {
const target = document.getElementById(pathname)
container.scrollTop = target.offsetTop;
}
return false
}
This works well for my usecase.
Now I want to add something similar for a page where the content is dynamically created (fetched from Sanity). From what I understand I cannot use GraphQL in gatsby-browser.js, so what is the best way to get the ID's from Sanity to gatsby-browser.js so I can use them to identify their scroll positions?
If there's some other better way to achieve the same result I'm open to that of course.
I think that you are over complexing the issue. You don't need the gatsby-browser.js to achieve it.
First of all, because you are accessing directly to the DOM objects (using document.getElementById) and you are creating precisely a virtual DOM with React to avoid pointing the real DOM. Attacking directly the real DOM (like jQuery does) has a huge performance impact in your applications and may cause some issues since in the SSR (Server-Side Rendering) the element may not be created yet.
You are hardcoding a logic part (the ids) on a file that is not intended to do so.
I think you can achieve exactly the same result using a simple function using a few hooks.
You can get the same information as document.getElementById using useRef hook and scrolling to that position once needed.
const YourComponent= (props) => {
const sectionOne = useRef(null);
const sectionTwo = useRef(null);
useEffect(()=>{
if(typeof window !== `undefined`){
console.log("sectionOne data ",sectionOne.current)
console.log("sectionTwo data ",sectionTwo.current)
if(sectionOne) window.scrollTo( 0, 1000 ); // insert logic and coordinates
}
}, [])
return (
<>
<section ref={sectionOne}>Section 1</section>
<section ref={sectionTwo}>Section 2</section>
</>
);
}
You can isolate that function into a separate file in order to receive some parameters and return some others to achieve what you want. Basically, the snippet above creates a reference for each section and, once the DOM tree is loaded (useEffect with empty deps, []) do some stuff based on your logic.
Your document.getElementById is replaced for sectionOne.current (note the .current), initially set as null to avoid unmounting or cache issues when re-hidration occurs.

react gatsby: adding css class dynamically not working in prod build

I need to add a css class dynamically on a div. For this I've set the css class like this:-
const Container = ({ divClass }) => {
const [cssClass, setcssClass] = useState(divClass)
return (
<div id="containerDiv" className={cssClass} ></div>
{/* <div id="containerDiv" className={divClass} ></div> even this doesn't work*/}
)
}
This works as expected in development build, but doesn't work in prod build. Even without using state if I set class to divClass it's not working in prod build.
It only works after I set state using setcssClass, like this-
const Container = ({ divClass }) => {
const [cssClass, setcssClass] = useState(divClass)
return (
<div id="containerDiv" className={cssClass} onClick={() => setcssClass('testtt')}></div>
)
}
Can anypone please explain this discrepancy in development and production build?
Your code is not working on your first try because at the moment you are defining const [cssClass, setcssClass] = useState(divClass), your <div> and it's class (divClass or cssClass) is not defined yet.
The solution is to use a useEffect hook with empty deps ([]) since it's a function that is triggered once the DOM tree is loaded (and your <div> in it).
I wouldn't recommend the approach of the other answer (besides being effective) because it impacts directly the DOM and that's the reason why you are using React and not jQuery (you creating a virtual DOM to manipulate). You can achieve the same result by using a React-based approach with useRef hook.
const Container = ({ divClass }) => {
const containerDivRef = useRef(null);
useEffect(() => {
containerDivRef.current.classList.add(divClass)
}, []);
return (
<div ref={containerDivRef}></div>
)
}
Your <div> information is stored inside the containerDivRef.current exactly in the same way that using document.querySelector but without affecting the DOM.
The discrepancy that you mention between develop and build is caused by the webpack's assets treatment and Gatsby's configuration. Since with a gatsby develop (runtime) allows you to use browser's APIs (such as window, document, other global or DOM objects) in the gatsby build (buildtime) the code is processed in the server-side, where those variables are not allowed (even created), that's why you usually should wait for them. You can check for further details in the Overview of the Gatsby Build Process documentation.
const Container = ({ divClass }) => {
useEffect(() => {
const containerDiv = document.querySelector("#containerDiv");
containerDiv.classList.add(divClass);
}, []);
return (
<div id="containerDiv"></div>
)
}
Have you tried manipulating the dom manually inside useEffect?
I know this might not be the most ideal solution but maybe it solves the problem. lol

Categories

Resources