Error: getStaticPaths is required for dynamic SSG pages and is missing for "xxx". NextJS - reactjs

I am getting this error "Error: getStaticPaths is required for dynamic SSG pages and is missing for 'xxx'" when I try to create my page in NextJS.
I don't want to generate any static page on build time. So why do I need to create a 'getStaticPaths' function?

If you are creating a dynamic page eg: product/[slug].tsx then even if you don't want to create any page on build time you need to create a getStaticPaths method to set the fallback property and let NextJS know what to do when the page you are trying to get doesn't exist.
export const getStaticPaths: GetStaticPaths<{ slug: string }> = async () => {
return {
paths: [], //indicates that no page needs be created at build time
fallback: 'blocking' //indicates the type of fallback
}
}
getStaticPaths does mainly two things:
Indicate which paths should be created on build time (returning a paths array)
Indicate what to do when a certain page eg: "product/myProduct123" doesn't exist in the NextJS Cache (returning a fallback type)

Dynamic Routing Next Js
pages/users/[id].js
import React from 'react'
const User = ({ user }) => {
return (
<div className="row">
<div className="col-md-6 offset-md-3">
<div className="card">
<div className="card-body text-center">
<h3>{user.name}</h3>
<p>Email: {user.email} </p>
</div>
</div>
</div>
</div>
)
}
export async function getStaticPaths() {
const res = await fetch('https://jsonplaceholder.typicode.com/users')
const users = await res.json()
const paths = users.map((user) => ({
params: { id: user.id.toString() },
}))
return { paths, fallback: false }
}
export async function getStaticProps({ params }) {
const res = await fetch(`https://jsonplaceholder.typicode.com/users/${params.id}`)
const user = await res.json()
return { props: { user } }
}
export default User

For rendering dynamic route use getServerSideProps() instead of getStaticProps()
For Example:
export async function getServerSideProps({
locale,
}: GetServerSidePropsContext): Promise<GetServerSidePropsResult<Record<string, unknown>>> {
return {
props: {
...(await serverSideTranslations(locale || 'de', ['common', 'employees'], nextI18nextConfig)),
},
}
}
You can check here as well

if you are using getStaticPaths, you are telling next.js that you want to pregenerate that page. However since you used it inside a dynamic page, next.js does not know in advance how many pages it has to create.
with getStaticPaths, we fetch the database. If we are rendering blogs, we fetch the database to decide how many blogs we have, what would be idOfBlogPost and then based on this information, getStaticPath will pre-generate pages.
also, getStaticProps does not run only during the build time. If you add revalidate:numberOfSeconds, next.js will recreate new page with fresh data after "numberOfSeconds" time.

You're rendering a dynamic route so use getServerSideProps() instead of getStaticProps()

Related

GetServerSideProps not getting called for nested page in Next with Typescript

I have a page set up at /article/[id] that is supposed to fetch an article for the id using getServerSideProps. It seems that getServerSideProps is not getting called at all since I don't see any of the console logs.
When I navigate to the page, it says "TypeError: Cannot read properties of undefined (reading 'title')" since article is undefined. When I replace the page content with simple static text, it shows up fine with the right url.
The file is currently called [id].tsx, but I tried different names with the same result.
import { GetServerSideProps, NextPage } from 'next'
import Link from 'next/link'
interface Article {
userId: number;
id: number;
title: string;
body: string;
}
interface ArticleProps {
article: Article
}
const ArticleDetail: NextPage<ArticleProps> = ({ article }) => {
return (
// <p>Hello</p> // Shows up on the page
<div>
<h1>{article.title}</h1>
<p>{article.body}</p>
<br />
<Link href="/">Go Back</Link>
</div>
)
}
export default ArticleDetail
export const getServersideProps: GetServerSideProps = async (context) => {
console.log("hello"); // this is never logged
const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${context.params?.id}`)
const article = await res.json() as Article
console.log(article); // this is never logged
return {
props: {
article
}
}
}
Move your getServerSideProps (beware of the case sensitivity) to your page component in the /pages folder and then pass it down thru props. You can not use it in your nested React components
EDIT after your response: the issue is that your getServerSideProps never gets called because of your typo "getServersideProps"

fetching data in React with DynamicRoutes, nextJS and useEffect

I have a blog link. Link is dynamic route with blog id. It's the Link wrapper from Next.
//link
<h3 className="blogTitle">
<Link href="[blog]" as={props.item.postingId}>{props.item.title}</Link>
</h3>
Now I want to pass "blog id" to the component and to present data in a new page.
//page where link leads to
const ad = () => {
const router = useRouter()
const {
query: {blog},
} = router
const [data, setData] = useState(false);
const [loading, setLoading] = useState(false);
console.log('....outside useEffect log', blog)
useEffect(() => {
console.log('useEffect consolelog', blog);
axios.get('httpwww.blogapiadress.com/'+ ad)
.then(response => setData(response.data))
.then(setLoading(false))
}, [])
return(
<Container fluid className="padding0">
/// data should be here.
</Container>
);
}
export default ad;
Problem: in useEffect console.log('blog', blog) returns undefined, so router does not return value from query. However, outside of useEffect it does. How to solve that issue, I want to fetch data related to the router query?
Since axios is getting undefined instead of blog id, I am getting 404.
You can use getStaticProps() to fetch the blog data at build time.
Example:
// posts will be populated at build time by getStaticProps()
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li>{post.title}</li>
))}
</ul>
)
}
// This function gets called at build time on server-side.
// It won't be called on client-side, so you can even do
// direct database queries. See the "Technical details" section.
export async function getStaticProps() {
// Call an external API endpoint to get posts.
// Access route params:
const blog = context.query.blog // or context.params.blog for parametrized routes
return {
const res = await fetch('https://...')
const posts = await res.json()
// By returning { props: { posts } }, the Blog component
// will receive `posts` as a prop at build time
return {
props: {
posts,
},
}
}
export default Blog
More info on NextJS docs.
I don't think you need to use the global window object to access dynamic data related to your route -- you should be able to use the Next router. I think the way you are defining href -- the only required prop for Link is causing issues. Looking at docs and your current exampel you probably want to use something like:
<Link
href={{
pathname: '/[blog]',
query: { blog: props.item.postingId },
}}
>
<a>{props.item.title}</a>
</Link>
// or
<Link href={`/${encodeURIComponent(props.item.postingId)}`}>
<a>{props.item.title}</a>
</Link>
Then you should be able to properly access [blog] (i.e., your postingId) using Router. For example, if your route was defined dynamically by /[blog].js, you could use the following:
import { useRouter } from 'next/router'
const ad = () => {
const router = useRouter()
const { blog } = router.query
const [data, setData] = useState(false);
const [loading, setLoading] = useState(false);
useEffect(()=>{
axios.get('httpwww.blogapiadress.com/'+ blog)
.then(response => {
setData(response.data)
setLoading(false)
})
}, [])
if (loading || !data) return <div> Loading... </div>
return(
<Container fluid className="padding0">
/// Render data
</Container>
);
}
export default ad;
Looking for answer for a few hours, and when I posted question on stack.. I figured out the answer.
So problem was that query is empty with next static generation at build time https://nextjs.org/docs/api-reference/next/router#router-object
I havent found best solution, but i found working one.
I got blog id from the windows.location href
useEffect(()=>{
const last = window.location.href.split('/').pop();
console.log('last', last)
axios.get('https://blogpostings.com/'+last)
.then(response => setData(response.data))
}, [])
I am not sure if its proper or good way, but it works.
I hope someone will find this helpful.
If you want get the query parameter from the link, you need to insert at the end of the url: https://sample-link.com?blog=123
Then same as your code above:
import { useRouter } from 'next/router'
const router = useRouter()
const {
query: {blog}
} = router;
console.log("blog: ", blog)
Result:
blog: 123

where to use getInitialProps when auto-direct from one page to another

In my application I am auto-directing from '/' to '/PageOne' like this:
const Home = () => {
const router = useRouter();
useEffect(() => {
router.push('/pageone', undefined, { shallow: true });
}, []);
return <PageOne />;
};
and in my PageOne, I want to use getInitialProps like:
const pageOne = (data) => {
return (
<Layout>
...
</Layout>
);
};
pageOne.getInitialProps = async (
ctx: NextPageContext
): Promise<{ data }> => {
const response = await someAPICall()
return {
data: response.data
};
};
export default pageOne;
This will cause an error in my Home page because I referenced to PageOne using and it is missing the param "data", but I'm not able to pass the data to because the data are not there when rendering Home page.
Shall I call the API to get data in Home page instead of PageOne? If I do so, will refreshing PageOne leads to another API call to get most recent data or the API will be called only when refreshing Home page?
Do not use shallow routing because that is meant to just change the url - a good use case is adding a query string or indicating to the application that something has changed when its bookmarked, e.g: ?chat=true (not your usecase)
Shallow routing allows you to change the URL without running data fetching methods again, that includes getServerSideProps, getStaticProps, and getInitialProps.
It's one of the caveats called out in this page => https://nextjs.org/docs/routing/shallow-routing#caveats
If not already, you would benefit from starting to use global state in your application
https://github.com/vercel/next.js/tree/canary/examples/with-redux
or you can use in-built features:
https://www.basefactor.com/global-state-with-react

Fetching Data from API using NextJS and Material UI React

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.

next.js getStaticPaths list every path or only those in the immediate vicinity?

Using Next.js to export static pages, I get that in a dynamic route like pages/[id].js any path I put in the getStaticPaths section will be created. Cool.
Is it better to list every page:
getStaticPaths(){
return (
// some function to spit out a list of every possible page
)
}
or
getStaticPaths(){
return (
// some function to return the next and previous page
)
}
or does it matter?
For dynamic routes example posts/[id].js getStaticPaths needs to define a list of paths so that Next.js pre-render all the specified paths at build time.
The function getStaticPaths needs to return an object with paths property which is an array with the route params and the property fallback which will be true or false. If fallback is set to false for any path that is not returned from the function getStaticPaths will not be pre-rendered hence resulting in a 404 page.
If you know all the paths that you need to render ahead of time you can set fallback to false.Here is an example..
// getStaticPaths for /category/[slug] where slug can only be -
// either 'category-slug-1', 'category-slug-2' or 'category-slug-3'
export const getStaticPaths = async () => {
return {
paths: [
{ params: { slug: 'category-slug-1'} },
{ params: { slug: 'category-slug-2'} },
{ params: { slug: 'category-slug-3'} }
],
fallback: false // fallback is set to false because we already know the slugs ahead of time
}
}
Let's says you have a route /posts/[id].js and ids coming from a database, and new posts are created every day. In this case, you can return the paths which already exist to pre-render some pages. and set fallback to true and on request, Next.js will serve a fallback version of the page instead of showing 404 page for the paths that are not returned from the function getStaticPaths, then in the background, nextjs will call getStaticProps for the requested path and serve the data as JSON which will be used to render the page in the browser.
Here is an example,
export const getStaticPaths = async () => {
const posts = await // your database query or fetch to remote API
// generate the paths
const paths = posts.map(post => ({
params: { id: post.id } // keep in mind if post.id is a number you need to stringify post.id
})
);
return {
paths,
fallback: true
}
}
P.S. - When using fallback set to true you need to render some sort of fallback component in your NextPage component otherwise when you try to access the data from props, it will throw an error like cannot read property ...x of undefined
You can render a fallback like this,
// in your page component
import {useRouter} from 'next/router';
const router = useRouter();
if (router.isFallback) {
return <div>loading...</div>
}
const paths = posts.map(post => ({
params: { id: post.id.toString() }
})

Resources