Exploring NextJS a bit for its server side rendering features. It looks really nice and easy to use. I already explored the _document.js file which we can include to overwrite the default. I found the following code in an example:
import Document, { Head, Main, NextScript } from 'next/document'
export default class MyDocument extends Document {
render() {
return (
<html>
<Head>
<link rel="stylesheet" href="/_next/static/style.css" />
</Head>
<body>
<Main />
<NextScript />
</body>
</html>
)
}
}
Now I get it that we are overwriting the current HTML template. But I'm a bit confused regarding the functionality of the Main and Nextscript.
Is Main just a page? What is Nextscript (which script)?
NextScript Class is here
and Main Class is here and I copied the same below. Main renders html/ errorHtml from this.context._documentProps
export class Main extends Component {
static contextTypes = {
_documentProps: PropTypes.any
}
render () {
const { html, errorHtml } = this.context._documentProps
return (
<Fragment>
<div id='__next' dangerouslySetInnerHTML={{ __html: html }} />
<div id='__next-error' dangerouslySetInnerHTML={{ __html: errorHtml }} />
</Fragment>
)
}
}
you can find actual documentation on Custom Document here
For those who will look for an answer to this question in the future...
Component NextScript from 'next/document' takes a list of files from context._documentProps and returns each of them as a element like this:
<script
key={file}
src={`${assetPrefix}/_next/${file}`}
nonce={this.props.nonce}
async
/>
It also takes dynamic imports from context._documentProps and returns them in a similar way:
<script
async
key={bundle.file}
src={`${assetPrefix}/_next/${bundle.file}`}
nonce={this.props.nonce}
/>
Related
I am working with Reactjs (Nextjs),I have "Home page" and few other pages (about,services...etc),For integrate in Nextjs, I created "_document.js",Problem is there is no class attached with" tag" in home page but class added on "about, service and rest of page", so how can i add "class" according to page? In other words i want to add class "main-layout inner_page" on all pages except "home page",How can i do this ? Here is my "_document.js" file
import { Html, Head, Main, NextScript,link } from 'next/document'
import { fileURLToPath } from 'url'
export default function Document() {
return (
<Html>
<Head>
<link rel="stylesheet" href="css/bootstrap.min.css" />
<link rel="stylesheet" href="css/style.css" />
</Head>
<body>
<Main />
<NextScript />
<script src="/js/jquery.min.js"></script>
</body>
</Html>
)
}
I would specify the layout that I use per page:
NextJS Layout per-page
But you can also pass props to your Layout from _app or from your page, example:
(Not so recommended approaches)
const CustomApp: FC<AppProps> = ({ Component, pageProps, router }) => {
return (
<MainLayout
hasContainer={pageProps.hasContainer}
hasSecondClass={['/', '/company'].includes(router.pathname)}
>
<Component {...pageProps} />
</MainLayout>
);
};
And your page can change the hasContainer
export const getStaticProps: GetStaticProps = async ({ locale }) => {
return {
props: { hasContainer: false },
};
};
My Current Setup
I have a Google Tag Manager script tag placed in the _document.tsx file nested under the <Head> component:
class MyDocument extends Document {
render() {
return (
<Html>
<Head>
<script
dangerouslySetInnerHTML={{
__html: `<!-- Google Tag Manager -->`,
}}
/>
</Head>
<body>
<noscript
dangerouslySetInnerHTML={{
__html: `<!-- Google Tag Manager (noscript) -->`,
}}
/>
<style
dangerouslySetInnerHTML={{
__html: (this.props as any).globalStyles,
}}
></style>
<Main />
<NextScript />
</body>
</Html>
)
}
}
My Goal
We have certain pages set up that are meant for international customers, and those pages all have a "-INTL" at the end of the url (for example: https://www.mystore.com/product--INTL).
My goal is to have Google Tag Manager NOT render if it is an international link, which contains the "--INTL" string in the URL.
Ideally, I want to wrap the entire Google Tag Manager script into its own component, then conditionally render that based on the URL of the page.
Ideal Setup that doesn't work
GTM Component Code:
class GTMComponent extends React.Component {
let currentUrl;
componentDidMount(){
if(typeof window !== "undefined"){
currentUrl = window.location.href;
}
}
render(){
if(!currentUrl.contains("--INTL")){
return (
<script dangerouslySetInnerHTML={{__html: `<!--
Google Tag Manager Script -->`}}/>
)
}
else {
return (
<></>
)
}
}
}
export default GTMComponent
_document.tsx file with GTMComponent:
import GTMComponent from '#components/common/GTMComponent'
class MyDocument extends Document {
render(){
return(
<HTML>
<Head>
<GTMComponent/>
</Head>
<body>
{/* body code */}
</body>
</HTML>
)
}
}
export default MyDocument
My Problem
I want to be able to access the window object, but due to Server Side Rendering, the window object is never defined.
Additionally, I can't use useEffect since _document.tsx is a class-based component, and I weirdly can't get componentDidMount to work either.
I'm under the impression that the whole point of using SSR in Next.js is to have instant DOM render while react and other scripts are bootstrapped. However, when I throttle my connection, the page is just blank/white until the JS completes loading.
Here you can see the network preview (while throttling) shows the simple <div>LOADING</div> in the preview pane:
But the browser sees nothing until scripts finish loading. It has the same behavior even if we render _app.tsx content (with router, lazy-loaded components etc). CURL to same address shows the desired HTML.
Here's the minimal _document.tsx that's used:
import { GetStaticPaths, GetStaticProps } from 'next';
import Document, { Html, Head, Main, NextScript } from 'next/document'
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
static getStaticProps: GetStaticProps = async () => {
return { props: {} };
};
render() {
return (
<Html lang="en">
<Head />
<body className="bg-gray-100">
<span style={{color: 'black', display: 'block', visibility: 'visible !important'}}>LOADING</span>
<Main />
<NextScript />
</body>
</Html>
);
}
}
Is something hiding the body's elements while scripts load? Am I not understanding how _document works?
Issue persists with all three fallbacks, too true false and blocking
In NextJs application, I want to add <script> code but not getting where to add and how? I tried by adding it in .js file but didn't recognise.
In react, we have index.html to add these things, what about in Nextjs app?
To edit the index.html, the equivalent in NextJS is the _document.js file.
A custom Document is commonly used to augment your application's <html> and <body> tags.
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
render() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
<script>...</script>
</body>
</Html>
)
}
}
If you only want to append elements to <head> tag (for a specific page), use next/head:
import Head from 'next/head'
function IndexPage() {
return (
<>
<Head>
<script>....</script>
</Head>
</>
)
}
It works perfectly because I haven't included JSX, but when I replace the script type with text/babel, it doesn't work because the module fails to load. browser.js the Babel compiler.
Here... JSX works only when i replace script type with text/babel but the problem is module fails to load since the script is not module. Any idea how make it work with JSX?
<div id="root">
</div>
<script type="module">
import './react.min.js';
import './react-dom.min.js';
import './browser.js';
class Hello extends React.Component {
render() {
return React.createElement('div', null, `Hello ${this.props.toWhat}`);
}
}
ReactDOM.render(
React.createElement(Hello, {toWhat: 'World'}, null),
document.getElementById('root')
);
</script>
UPDATE July 2021
As per mh sattarian's answer you now don't need data-plugins="transform-es2015-modules-umd" to use native es6 module's import/export etc. Instead you simply add data-type="module"
Original Answer
Just in case if some one comes here looking for answer
There is a support for data-plugins and data-presets in babel standalone
<script data-plugins="transform-es2015-modules-umd" type="text/babel">
see more here
Babel standalone
As mentioned in the docs, it's added in: v7.10.0.
If you want to use your browser's native support for ES Modules, you'd
normally need to set a type="module" attribute on your script tag.
With #babel/standalone, set a data-type="module" attribute instead,
like this:
<script type="text/babel" data-type="module">
I think the question was if it is possible to use a script tag with two or more types at the same time (e.g. something like type="module, txt/babel"). As far as I know the answer is NO.
JonDotsoy's answer helps with reduce typing React.createElement over and over again but even with such a "variable shortcut" it's not as comfortable as JSX when using larger templates with nested elements because h('div', {}, 'hello!!')... is difficult to maintain in such cases.
The only way I found to combine native browser module support and in-browser Babel for JSX without using any build tool is this ... a rather dirty hack that uses eval and should not be used for production apps:
index.html
<body>
<div id="app"></div>
<!-- Scripts ------- -->
<script src="vendor/js/babel.min.js"></script>
<script src="vendor/js/react.development.js"></script>
<script src="vendor/js/react-dom.development.js"></script>
<script src="app/app.js" type="module"></script>
</body>
app/app.js
import ComponentOne from "./ComponentOne.js";
let template = `
<div>
<h1>Heading</h1>
<hr />
<ComponentOne msg="MsgText-ComponentOne" />
</div>
`;
const App = () => {
return (
eval(Babel.transform(template, { presets: ['es2017', 'react'] }).code)
);
};
ReactDOM.render(
React.createElement(App, null),
document.getElementById("app")
);
app/ComponentOne.js
import ComponentTwo from "./ComponentTwo.js";
let template = `
<div>
<h2>This is ComponentOne</h2>
<p key="2">Property "msg" content: {props.msg}</p>
<ComponentTwo msg="MsgText-ComponentTwo" />
</div>
`;
const ComponentOne = (props) => {
return(
eval(Babel.transform(template, { presets: ['es2017', 'react'] }).code)
);
};
export default ComponentOne;
app/ComponentTwo.js
let template = `
<div>
<h2>This is ComponentTwo</h2>
<p key="2">Property "msg" content: {props.msg}</p>
</div>
`;
const ComponentTwo = (props) => {
return(
eval(Babel.transform(template, { presets: ['es2017', 'react'] }).code)
);
};
export default ComponentTwo;
you may prefer to use an alias to createElement. This way is faster for the engine.
For example use h or e.
const { createElement: h } = React;
const App = () => {
return h('div', {}, 'hello!!');
}
Otherwise, it is possible to use #babel/standalone module, please see more on here https://babeljs.io/docs/en/next/babel-standalone.html.
<div id="app"></div>
<!-- Load Babel -->
<script src="https://unpkg.com/#babel/standalone/babel.min.js"></script>
<!-- Your custom script here -->
<script type="text/babel">
const { render } = ReactDOM;
const App = () => {
return <div>hello!!</div>
}
render(<App />, document.getElementById('app'));
</script>