How to get pathname in NextJS /_document.js file - javascript

I'm trying to get the current pathname in /page/_document.js file. I'm using a class, and my objective is to make a conditional with that value.
Here's my code (is basically the example in NextJS' page)
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 />
</body>
</Html>
)
}
}
export default MyDocument

You can get it by ctx.req.url or ctx.asPath but getInitialProps only executed at first time you access to app
Note: 's getInitialProps function is not called during client-side transitions, nor when a page is automatically statically optimized.
Note: Make sure to check if ctx.req / ctx.res are defined in
getInitialProps. These variables will be undefined when a page is
being statically exported for next export or automatic static
optimization.
Document

Related

Next.js 13 (app dir) - Access page metadata in Layout

I am using the new App folder structure with Next.js 13 project, and I would like to display a subheader in the RootLayout containing the value of the Page title (I'm using metadata on each page to configure the head title).
Is there any way to directly access that value with SSR?
Example layout.js:
export default function RootLayout({ children) {
const pageTitle = "???"; // How to get this value?
return (
<html>
<body>
<HeaderComponent />
<div className="subheader">
<h3>{pageTitle}</h3> <------ HERE
</div>
<div className="content">{children}</div>
<FooterComponent />
</body>
</html>
)
);
Example page.js:
export const metadata = {
title: "Login"
};
export default function Login() {
return (
<div>Login form</div>
);
}
No, you cannot get that metadata in the layout with SSR, at least so far. A layout in the app folder gets passed an object that contains children and params when a dynamic route is used. That's it.
Its role is to wrap all components of a route segment, not only page.js, but also loading.js, not-found.js...
Anything specific to a component should be in it directly. A page title should be in page.js itself. Next.js decided that a project should be structured so.

Next.js throwing Error: Error: NextRouter was not mounted when using _document.js inside pages folder

I want to be able to customize the HTML based on the current locale. I have created a _document.js inside pages directory. And I used this code:
import { Html, Head, Main, NextScript } from 'next/document'
import { useRouter } from 'next/router'
export default function Document() {
const { locale } = useRouter();
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
But I get this error:
Error: Error: NextRouter was not mounted. https://nextjs.org/docs/messages/next-router-not-mounted
When I go to the URL specified, I see them saying that I'm using logic outside <Main />. Thus, how can I access locale inside _document.js?
I found NextRouter was not mounted Next.JS but even using next/navigation did not work for me, and I get this error:
Error: invariant expected app router to be mounted
useRouter() hook runs on the browser while _document.js runs on the server; that's the problem. For example, if you add a console.log('Hello Word') in Document before the return, it gets printed only in your development server, not on the browser.
You shouldn't be trying to use a logic related to hooks in _document.js, as it's not meant for, as the doc says:
React components outside of <Main /> will not be initialized by the browser. Do not add application logic here or custom CSS (like styled-jsx). If you need shared components in all your pages (like a menu or a toolbar), read Layouts instead.
It happened to me in the client component of app directory.
Instead of this
import { useRouter } from 'next/router'
import from
import { useRouter } from "next/navigation";

how do you add external javascript in Next.js?

I'm learning Next.js and have spent hours trying to add external javascript.
I want to load this external script I named query.js into /pages/index.js
console.log("script loaded!")
function hello(){
console.log("hello!")
}
The script does load when I imported the script on _app.js
import '../styles/globals.css'
import '../public/static/query.js'
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default MyApp
but calling the function like this on index.js returned "ReferenceError: hello is not defined" even though the script already loaded.
export default function Home() {
return (
/* some div */
{hello()}
/* the rest of the code */
)
what did I do wrong? I want the script to do some data processing on the browser. I have tried
it totally depends on how you plan to import your JS file.
Your current approach
your current approach is creating a side effect only module which your import statement initializes- that is why you see the console.log statement. to make this work just attach hello function to the window object which is not recommended.
Recommended approach
Usually, it's best to create modules without side effects. in that case to make this work you should export your function and import it in whatever module that you need.
here's an example:
console.log("script loaded!")
export function hello(){
console.log("hello!")
}
and in your module
import {hello} from 'wherever';
export default function Home() {
return (
/* some div */
{hello()}
/* the rest of the code */
)
To add external javascript in Next.js you can use the Script component from next/script since v11.0.0.
Example from their website:
import Script from 'next/script'
export default function Home() {
return (
<>
<Script src="https://www.google-analytics.com/analytics.js" />
</>
)
}
More info in their official docs.

Understanding imports from component pages in NextJS

I am new to the Next.JS framework and do not fully understand the logic of importing data from a component page. For example, I have created a page like example.js in my components folder, where I am running an API that pulls data to a graph. Then, inside my index.js file, where I want my graph to be displayed, it is giving me a 'Unhandled Runtime Error', because the API function is not being triggered inside of example.js.
This is how I am importing the page in /pages/index.js:
import dynamic from 'next/dynamic'
const Example = dynamic(() => import('../pages/example.js'))
//Later on inside of my return
<Example />
And from my example.js page I am exporting as follows:
function Example(props) {
return <div>
... my code
}
Then below that, I have the following inside my example.js:
export default Example
Followed by my getStaticProps function:
export async function getStaticProps() {
const res = await fetch("")
const data = await res.json()
if (!data) {
return {
notFound: true
}
}
return {
props: {
data: data.reverse(),
},
}
}
If I use this exact code on my index.js it functions properly. Any ideas as to why this doesn't run, and any solutions as to a fix?
Edit for clarity: My getStaticProps function is in my example.js file, and my issue is that it is not being triggered.
Next.js does a lot of magic to handle server-side rendering, static-site generation and code splitting, and it does it all from within the /pages folder. It's important to know that components within the /pages folder work a little differently from a regular React component. Here are some tips for you that should solve your problem.
1. Put your page components inside the /pages folder.
Components inside this folder can have a default export (the component), along with named exports (the additional Next.js-specific functions like getStaticProps). If Next.js finds the exported functions, it will run them.
2. Use next/dynamic only for dynamic imports.
Dynamic imports are lazy-loaded -- this will affect Next.js' automatic code-splitting and potentially server-side rendering. I don't know the exact inner workings, but my guess is dynamically-loading a Page component (which are special in Next) is probably what's breaking things. I tend to only use dynamic imports when I have a 3rd-party component that breaks SSR, so I need to dynamically import it so it's not server-side rendered.
The simplest way to handle this is probably:
in /pages/index.js:
import Example from '../path/to/example';
export default (props) => (
<div>
<p>My page component</p>
<Example />
</div>
);
export async function getStaticProps() {
return {
props: {
data: // ...
}
};
}
With this solution above, you can use dynamic imports as well -- what matters is that you have your Next.js-specific function exported from the file in /pages immediately:
in /pages/index.js:
import dynamic from 'next/dynamic';
const Example = dynamic(() => import('../components/example'));
export default (props) => (
<div>
<p>My page component</p>
<Example />
</div>
);
export async function getStaticProps() {
return {
props: {
data: // ...
}
};
}
Update to solve "My getStaticProps function is in my example.js file, and my issue is that it is not being triggered.":
Next.js will only call a getStaticProps function that is exported from a file in the /pages directory. If you want to define your getStaticProps method from another directory, you can do that, but you need to import it into /pages and then re-export it like so:
in /components/example.js:
export default () => (
// ... component ...
);
export function getStaticProps() {
// ... function ...
};
and in /pages/index.js:
import Example, { getStaticProps } from '../components/example';
export default Example;
export {
getStaticProps
}
This should work. However, you can't import Example using dynamic imports in this case.

How do I use the window object in ReactJS?

I want to load the Google APIs client library inside my index.html and the onLoad='someMethod' will invoke a method in a separate javascript file. That method will then print out to the console.
The client library is loaded without any problems but the message is not getting printed out the console and I think it's because the method is not getting invoked at all.
Here is my index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Welcome</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
</head>
<body>
<div id="app"></div>
<script src="lib/vendors.js"></script>
<script src="build/bundle.js"></script>
<script src="https://apis.google.com/js/client.js?onload=handleGoogleClientLoad"></script>
</body>
Here is the javascript file that contains the handleGoogleClientLoad method:
import React from 'react';
import ReactDOM from 'react-dom';
import {Button} from 'react-bootstrap';
class MyApp extends React.Component {
handleGoogleClientLoad() {
console.log('Success on load');
}
render() {
return (
<div>
<Button>Click Me</Button>
</div>
);
}
}
const app = document.getElementById('app');
ReactDOM.render(<MyApp />, app);
If this was plain javascript the method would look like this:
window.handleGoogleClientLoad = function() {
// Log to the console
};
Is there anything in es6 that is similar to the window object.
Component methods are not attached to the window object. MyApp.handleGoogleClientLoad is not going to be aliased to window.handleGoogleClientLoad which is what the google API script is likely trying to invoke.
If you want the Google API to call a method on your component you're going to have some trouble as there's no guarantee that your component will be mounted before or after your Google API script loads. If you want to guarantee that you'd have to inject the script after the component mounted and register the function in the componentDidMount method. You can use something like loadjs
componentDidMount() {
window.handleGoogleClientLoad = function() {
// log to console
}
loadjs('https://apis.google.com/js/client.js?onload=handleGoogleClientLoad')
}
the window object will still be available to you from your react component.. but the issue is that your defining your function just fine, but you're not calling it anywhere (is that jsonp syntax in the url of that script?). if you want that function to execute when you mount your component, you can make use of the component lifecycle hooks, specifically, componentDidMount
class MyApp extends React.Component {
handleGoogleClientLoad() {
console.log('Success on load');
}
componentDidMount(){
this.handleGoogleClientLoad();
}
render() {
return (
<div>
<Button>Click Me</Button>
</div>
);
}
}
if you want to keep the syntax you already have, then you can go ahead and access the window the way you suggest, but its thats definitely not a very react way of doing it
An old question but I found this while solving the problem myself. I am using React Helmet to put stuff in <head>. How I have done it is below. The key here is using defer when using <script> to load gapi. This means it does not run it until the document has been parsed, meaning our onload function window.gapiInit now exists.
From https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-defer:
This Boolean attribute is set to indicate to a browser that the script is meant to be executed after the document has been parsed, but before firing DOMContentLoaded.
import React from "react"
import { Helmet } from "react-helmet"
const HeadHelmet: React.FC = () => {
if (typeof window != "undefined") { // needed if SSR
window.gapiInit = () => {
console.log({ gapiInit: true })
// set state, what ever you need
}
}
return (
<Helmet>
<script
src="https://apis.google.com/js/api.js?onload=gapiInit"
defer={true}
></script>
</Helmet>
)
}
export default HeadHelmet

Categories