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
Related
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>
Greetins, I'm trying to build a webconstructor. Right now my application works like that:
I check the url the user is on (for example localhost:3000)
I get his project name on my webconstructor (localhost:3000 -> projectName: project1)
I fetch the user's website data (for example favicon and title) (project1: {favicon: 'url', ...}
Is it possible to render the favicon and title before the user enters the page so that it shows the right favicon and title in the browser. Right now I can only get it via useEffect in the main App component (but it's not good for seo). I have tried with getInitialProps but it doesn't seemd to do the job.
Thank you in advance
Yeah it is possible to render dynamic favicon and title. Using the Head component from next/head. You can also create a GenericHead component that you can use whenever you want. Something like:
import Head from 'next/head'
const GenericHead: FC<{ title?: string; favicon?: string }> = (props) => {
const { title, favicon } = props
return (
<Head>
{title ? <title>{title}</title> : <title>Document</title>}
<meta
name='description'
content='your description'
/>
<link rel='icon' href={favicon ? favicon : '/favicon.ico'} />
</Head>
)
}
You can add more things to this GenericHead, like OpenGraph meta tags. And use it whenever you want
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?
I'm trying to generalize my code by keeping a .js file containing only React components in one file and then utilizing these components in an HTML file. Here is my simple component:
component.js
'use strict'
class MyComponent extends React.Component {
render() {
return (
<div className="MyComponent">
<p>Text goes here.</p>
</div>
);
}
}
If in my component.js file I add: ReactDOM.render(<MyComponent/>, document.querySelector('#div-1')); and then, in my HTML, add <script src="component.js" type="text/jsx"></script> the React component shows in my page as expected.
However, my end goal is to be able to add the ReactDOM.render into my HTML within a script tag, that way I can have multiple pages utilizing the component.js components while doing all the assigning in the HTML page. Something like:
mypage.html (simplified)
<!DOCTYPE html>
<html>
<script src="component.js" type="text/jsx"></script> //import my components (no assigning done in this file)
<div id="div-1"><div>
<script>
ReactDOM.render(<MyComponent/>, document.querySelector('#div-1')); //assign to div
</script>
</html>
However this above code fails, with many errors regarding Uncaught SyntaxError: Unexpected token '<'
With that, how would I go about carrying something like this out? Any help is greatly appreciated.
The issue you're facing is that JSX isn't recognized by default in a browser.
Uncaught SyntaxError: Unexpected token '<' that's what this error means.
React docs have following help regarding that: quickly try JSX
you need to add babel in script tags and add type="text/babel" in whichever script you're using JSX.
<div id="counter_container"></div>
<!-- add babel support -->
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<!-- Load our React component. -->
<script src="components.js"></script>
<script type="text/babel">
(() => {
const Counter = window.Counter;
const counterContainerEl = document.querySelector('#counter_container');
ReactDOM.render(<Counter/>, counterContainerEl);
})();//this is just to avoid polluting global scope
</script>
I've put together a short example here github-repo
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>
}