5

I am trying to create dynamic pages that show individual book details (.i.e. title/author) on a separate page based on a query string of the "id" for each book. In a previous question I asked, answers from users were very helpful and I have a much better understanding of how to use getStaticPaths and getStaticProps correctly. However, I am not quite there in my code for how to do this.

Here is the basic setup and context.

  1. I am running NextJS 9.4 and would like to use a API endpoint instead of querying the database directly.
  2. The book data is being pulled from a MongoDB Atlas Database and uses Mongoose
  3. Documents in the MongoDB have a "_id" as a unique ID.
  4. I have tried to incorporate and learn from existing Github examples and NextJS documentation but I still get the following error.

Error: A required parameter (id) was not provided as a string in getStaticPaths for /book/[id]

Here is the code I have so far. I have tried to keep the code as clean as possible for now.

export default function Book({ book }) {
    return (
        <article>
            <h1>Book Details Page</h1>
            <p>{book.title}</p>
            <p>{book.author}</p>
        </article>
    )
}

export async function getStaticPaths() {
    
    const url = `${baseUrl}/api/books/books`

    const response = await axios.get(url);

    const books = response.data

    const paths = books.map((book) => ({
        params: { id: book.id },
    }))
    
    return { paths, fallback: false }
}

export async function getStaticProps({ params }) {
    const url = `${baseUrl}/api/books/books/${params.id}`
    const res = await axios.get(url)
    const book = await res.json()

    return { props: { book }}
}

The API endpoint looks like this:

import Book from '../../../models/Book';
import dbConnect from '../../../utils/dbConnect';

// conects to the database
dbConnect();

// This gets all the book from the database
export default async (req, res) => {
    const books = await Book.find()
    res.status(200).json(books)
}

Any support or feedback would be greatly appreciated. Once I get this working, I can hopefully be able to understand and help assist others in creating dynamic routes with NextJs. Thank you.

Gregory Wiley
  • 63
  • 2
  • 6
  • Is this a typo "`${baseUrl}/api/books/books`" in the function `getStaticPaths` or the endpoint the correct? From the error it looks like the route param `id` was set incorrectly. Can you log the value of `books` in console and verify that you are getting the correct data? – subashMahapatra Jun 28 '20 at 14:17
  • 1
    As you state, Mongo documents contain an `_id` field, but when you're mapping over the `books` response you're using `books.id`. That could be your problem. Use `books._id` instead. Also, always catch your promises. Since you're using async/await, use a try/catch block. – Matt Carlotta Jun 28 '20 at 14:45

2 Answers2

5

You can't make calls to Next.js API routes inside getStaticProps or getStaticPaths. These functions are executed at build time, so there is no server is running to handle requests. You need to make request to DB directly.

If you want to keep it clean you could create a helper module like allBooksIds() and keep DB query in a separate file.

See the same issue - API call in NextJS getStaticProps

Nikolai Kiselev
  • 6,201
  • 2
  • 27
  • 37
  • 1
    Thank you Nikolai for this information! I now understand that I should not fetch an API route from getStaticProps and will instead learn how to write the server-side code directly in getStaticProps. I have found that NextJS Github has as great mongo-mongoose example. Thanks for your help. – Gregory Wiley Jun 29 '20 at 13:20
  • @Nikolai: I'm having a similar issue and came upon your answer. I don't know, if I'm understanding something incorrectly, but in the Next.js-documentation there are examples, where API calls are made inside getStaticPaths (https://nextjs.org/docs/basic-features/data-fetching#getstaticpaths-static-generation). Could you tell me where is the difference? – Ewax_Du Feb 17 '21 at 09:53
  • Ok, I think the difference is that in the example they make an EXTERNAL api call. However, maybe you could add a small example how to solve the problem? Would be very helpful! – Ewax_Du Feb 17 '21 at 10:11
  • @NikolaiKiselev Please look a tthis thread. I am facing some weird issue here https://stackoverflow.com/questions/67624322/nextjs-dynamic-routes-with-next-i18next-build-error – Ehsan Nissar May 21 '21 at 08:05
0

Simply add toString() method in getStaticPaths because the book id is of type ObjectID("ID") if you do params: { id: book._id.toString() } it will convert ObjectID("ID") to type string which is accepted by getStaticPaths().The complete code for the nextjs part is below also update your API route as follows :-

The upper one is the API route the bellow one is Nextjs Page

import Book from '../../../models/Book';
import dbConnect from '../../../utils/dbConnect';

// conects to the database
dbConnect();

// This gets all the book from the database
export default async (req, res) => {
    const books = await Book.find({})
    res.status(200).json(books)
}

export default function Book({ book }) {
    return (
        <article>
            <h1>Book Details Page</h1>
            <p>{book.title}</p>
            <p>{book.author}</p>
        </article>
    )
}

export async function getStaticPaths() {
    
    const url = `${baseUrl}/api/books/books`

    const response = await axios.get(url);

    const books = response.data

    const paths = books.map((book) => ({
        params: { id: book._id.toString() },
    }))
    
    return { paths, fallback: false }
}

export async function getStaticProps({ params }) {
    const url = `${baseUrl}/api/books/books/${params.id}`
    const res = await axios.get(url)
    const book = await res.json()

    return { props: { book }}
}

Hope this is helpful

KUSHAD
  • 127
  • 1
  • 2
  • 8