I am using react-device-detect package to detect whether the request is from a mobile or desktop device, it uses user agent of course to do that.
In my NextJS project I want to use it on SSG pages.
Imported the package
import { isMobile, getUA, getSelectorsByUserAgent } from 'react-device-detect';
And using the getStaticProps like this
export async function getStaticProps(context) {
// Device React
const deviceIsMobile = isMobile;
const deviceType = deviceIsMobile ? 'Yes, I am a mobile' : 'Nope, Desktop!';
return {
props: {
mobileDevice: deviceType
}, // will be passed to the page component as props
}
}
And finally in my page's function
function AwesomePage( { mobileDevice } ) {
return(
<h1>{ mobileDevice }</h1>
)
}
No matter what I always see 'Nope, Desktop!'.
In some examples of react-device-detect I noticed that we may use the userAgent from the req param, but does getStaticProps even provide that, I checked and it seems it don't? What's the way around this? I simply want to get user agent on an SSG page through getStaticProps.
Thanks!
Related
I am using NextJS 12. I am trying to get local storage object. When I use localstorage inside getServerSideProps I get an error like this ReferenceError: localStorage is not defined. I tried to use it outside the function as well but I still get this error. Is there any way to use it inside getServerSideProps.
export async function getServerSideProps({ query }) {
const id = query.id;
const getData = JSON.parse(localStorage.getItem("form"));
console.log(getData)
return {
props: {},
}
Welcome to StackOverflow, as it refers in the documentation
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.
Localstorage is only available on the client side and you are trying to access it in a server side only function , you can use something like
if (typeof window !== 'undefined') {
// your code
const id = query.id;
const getData = JSON.parse(localStorage.getItem("form"));
console.log(getData)
}
Please review this article to get more information on running client side only code.
Another approach would be to use a dynamic import where the hello3 component would contain the code accessing local storage.
import dynamic from 'next/dynamic'
const DynamicComponentWithNoSSR = dynamic(
() => import('../components/hello3'),
{ ssr: false }
)
function Home() {
return (
<div>
<Header />
<DynamicComponentWithNoSSR />
<p>HOME PAGE is here!</p>
</div>
)
}
export default Home
Another way would be using cookies instead of using localstorage, I had the same problem when I developed my last application and I solved it using the nookies package
Nookies: A collection of cookie helpers for Next.js
I am trying to create dynamic pages that shows individual book details (i.e. title/author etc) on a separate page based on a query string of the "id" of each book. However, I am having difficulty in understanding how to make a request to a API endpoint using NextJS that will get the book details based on its "id". I would like to use Material UI as a UI Framework.
ISSUE: When I run npm run dev the book page loads but the book's "props" are not being passed along to the BookAttributes component. The console.log(book) I added in the book page is undefined and the console.log(title) in BookAttributes is undefined as well.
I've tested the API endpoint in POSTMAN and it appears to work.
When I refactor the same code using Semantic UI-React instead of Material UI, the book pages load correctly.
I am using the NextJS Material UI starter template from the Material UI website as a baseline.
I am fairly new to NextJS and Material UI so your assistance and guidance would be greatly appreciated. Thank you for your help on this!
Here is the code I have so. I have tried to keep in clean and simple.
BOOK PAGE (within 'pages' directory)
import axios from 'axios';
import BookAttributes from '../components/Book/BookAttributes';
function Book({ book }) {
console.log(book)
return (
<>
<h1>Book Page</h1>
<BookAttributes {...book} />
</>
)
}
Book.getInitalProps = async ({ query: { _id } }) => {
const url = 'http://localhost:3000/api/book';
const payload = { params: { _id }}
const response = await axios.get(url, payload)
return { book: response.data }
}
export default Book;
BOOK API ENDPOINT (within 'pages/api' directory)
import Book from '../../models/Book';
import connectDb from '../../utils/connectDb';
connectDb()
export default async (req, res) => {
const { _id } = req.query
const book = await Book.findOne({ _id })
res.status(200).json(book);
}
BOOK ATTRIBUTE COMPONENT (within 'components' directory)
import React from 'react';
function BookAttributes({ title }) {
console.log(title)
return (
<>
<h1>{title}</h1>
</>
)
}
export default BookAttributes;
You should be using dynamic routes here if you want to work with data-fetching methods like getStaticProps or getServerSideProps.
You can create a page like pages/book/[id].js. But to generate the page you have to decide what data-fetching method you want to run. If the data for the page doesn't change very often you can choose to use static-site-generation using getStaticProps which will generate the pages at build time. If the data will be changing a lot you can either do server-side-rendering using getServerSideProps or fetch the data client-side.
Here is an example for your use-case that you can use for server-side-rendering using getServerSideProps, keep in mind the API call inside getServerSideProps might fail so you should have appropriate error handling.
In pages/book/[id].js
import axios from 'axios';
import BookAttributes from '../components/Book/BookAttributes';
export const getServerSideProps = async (ctx) => {
const bookId = ctx.params?.id
const url = 'http://localhost:3000/api/book';
const response = await axios.get(url, { params: { _id: bookId} })
return {
props: {
book: response.data
}
}
}
function Book({ book }) {
return (
<>
<h1>Book Page</h1>
<BookAttributes {...book} />
</>
)
}
export default Book;
Using static-site-generation
Because the page is dynamic you have to provide a list of paths for which nextjs will generate the pages. You can do that by exporting an async function called getStaticPaths.
in pages/book/[id].js
import axios from 'axios';
import BookAttributes from '../components/Book/BookAttributes';
export const getStaticPaths = async () => {
// here you have two options if you know all the ids of the books
// you can fetch that data from the api and use all the ids to generate
// a list of paths or show a fallback version of page if you don't know all
// ids and still want the page to be static
// Pseudo code might look like this
const res = await axios.get('api-endpoint-to-fetch-all-the-books')
const paths = res.data.map(book => ({ params: { id: book.id }}))
return {
paths,
fallback: false
}
}
export const getStaticProps = async (ctx) => {
const bookId = ctx.params?.id
const url = 'http://localhost:3000/api/book';
const response = await axios.get(url, { params: { _id: bookId} })
return {
props: {
book: response.data
}
}
}
function Book({ book }) {
return (
<>
<h1>Book Page</h1>
<BookAttributes {...book} />
</>
)
}
export default Book;
The fallback property in the returned value of getStaticPaths is somewhat important to understand. If you know all the necessary id for the pages you can set the fallback to false. In this case nextjs will simply show a 404 error page for all the paths that were not returned from the function getStaticPaths.
If fallback is set to true nextjs will show a fallback version of page instead of a 404 page for the paths that were not returned from the getStaticPaths function. Now where should you set fallback to true? Let's suppose in your case new books are added to the database frequently, but the data for the books doesn't change very often so you want the pages to be static. In this case, you can set fallback to true and generate a list of paths based on avaliable book ids. For the new books nextjs will first show the fallback version of the page than fetch the data based on the id provided in the request and will send the data as JSON which will be used to render the page in the client.
Problem: if I refresh a page, getInitialProps trigger but it gets me the error describe on the bottom of the page which points out an issue going all the way to my redux wrapper on /_app.js, which I believe to be the issue. This behavior only happens on refresh page. If I go from a page that has no getInitialProps and then navigate client side to the page with getInitialProps it works fine. If I then do a manual refresh of the page it triggers the error.
This is my setup for redux and I've tried it with the Class based version with same results so that's not the issue.
/_app.js
import { Provider } from "react-redux";
import withRedux from "next-redux-wrapper";
import { initStore } from "../store";
import "./styles.scss";
const MyApp = props => {
const { Component, pageProps, store } = props;
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
);
};
MyApp.getInitialProps = async ({ Component, ctx }) => {
const pageProps = Component.getInitialProps
? await Component.getInitialProps(ctx)
: {};
return { pageProps };
};
export default withRedux(initStore)(MyApp);
this is the getInitialProps on the index.js page
/pages/index.js
OpenApplications.getInitialProps = async ({ store }) => {
const stateDB = await getApplications();
// populate state with all applications from DB
await store.dispatch(populateApplicationsState(stateDB));
return store;
};
this is the internal API setup with Auth
/api/applications.js
import auth0 from "../../../../lib/auth/auth0";
// auth0.requireAuthentication ensures that only authenticated users access this internal API path
export default auth0.requireAuthentication(async (req, res) => {
// get user profile from Auth0 on the server side
const { user } = await auth0.getSession(req);
process the rest of the stuff...
})
this is the code behind the Auth0 init config
.../auth/auth0.js
import { initAuth0 } from "#auth0/nextjs-auth0";
import config from "./config";
export default initAuth0({
clientId: config.AUTH0_CLIENT_ID,
clientSecret: config.AUTH0_CLIENT_SECRET,
scope: config.AUTH0_SCOPE,
domain: config.AUTH0_DOMAIN,
redirectUri: config.REDIRECT_URI,
postLogoutRedirectUri: config.POST_LOGOUT_REDIRECT_URI,
session: {
cookieSecret: config.SESSION_COOKIE_SECRET,
cookieLifetime: config.SESSION_COOKIE_LIFETIME,
cookieSameSite: "lax",
storeIdToken: true,
storeRefreshToken: true,
storeAccessToken: true
}
});
This is the Application tab on Chrome Dev Tools and it shows the a0state and a0session. When I refresh, the state disappears but the cookie stays. On an unprotected page it then comes back after I assume making a call to check the session is valid but on a protected page it goes into the error above.
error if requireAuthtentication is taken OFF
error if requireAuthentication is ON
So my suspicion is that whenever a manual page refresh is onset, auth0 loses the state and it doesn't pass it in the req to the API so that it fails the auth check and also it fails to get the session from the user. If I remove the auth0.requireAuthentication, it still fails because I'm dependent on the user which comes from auth0.getSession(req); which fails nonetheless and that triggers an error anyway because user comes undefined. What I think it's happening is that the session is not being passed on correctly due to the Redux wrapper setup that doesn't pass the necessary info or triggers the necessary Auth for that info to be there. At this point I'm a bit lost as all these tools are new to me and I'm not sure where it's going wrong and where to fix it and since it's 3 different libraries interacting it gets even harder. Help figure this super annoying bug :)
I checked everywhere and couldn't find a solution or a similar setup to get a comparison and find out a potential mistake I've done on my setup. Everything is pretty much like the examples on the docs for the different tools next-wrapper-redux/nextjs-auth0/next
I have an image lazy load component that just renders the image src when on the server, and renders a loader when on client pending when the actual image is lazyloaded.
The problem.
After the initial render and client side takes over, the image src mismatch. This is because the server set the actual src, but the client is setting the loader (placeholder).
Question.
Is there a way to detect that this initial render was server rendered? just before the client renders/mounts
You can find out if it is currently executed on the server by checking for req attribute inside getInitialProps
Example page code
function Page({ isServer }) {
return <div>Is this page on the server? - {isServer ? 'YES' : 'NO'}</div>;
}
Page.getInitialProps = async ({ req }) => {
return { isServer: !!req };
};
export default Page;
Some info on official repo about isServercheck
You can check that typeof Window === 'undefined'
The fix is to use useEffect as described in the Next.js docs at https://nextjs.org/docs/messages/react-hydration-error
do this:
import { useState, useEffect } from 'react'
function AnyComponent() {
const [isSSR, setIsSsr] = useState(true)
console.log(`isSSR: `, isSSR);
useEffect(() => setIsSsr(false))
return (
<span>Is SSR? {isSSR ? 'Yes!' : 'No!'}</span>
)
}
Obviously, this trivial example will update so quickly that you would only ever see "Is SSR? No!" but the first render will be yes. Check the console.
Why? useEffect is only executed in the browser.
I try to migrate my project to Next.js framework for having SSR (Server side rendering) feature. Here is my simple page:
class Example extends React.Component {
static getInitialProps() {
// api getting data. Need authentication data under local storage
}
render() {
return <div>{this.props.data}</div>;
}
}
The problem I met is: I want my data is from getInitialProps first (that is the purpose of SSR). But when sending, I need a information about user for backend API. But at this function, rendering is happened on server so I cannot get data from local storage. How can you solve this problem.
Thanks
You can put the call to localStorage into a react component lifecycle method such as componentDidMount and then setState() with the result.
It's not great, but will work.
Note that this would obviously not use SSR.
This works for me:
const USER = 'user'
const URL = 'http://www.example.com/user'
// here you can also use isServer
if (req) {
const {data: user} = await Axios.get(URL)
localStorage.setItem(USER, JSON.stringify(user))
} else {
const user = await JSON.parse(localStorage.getItem(USER))
}