How do I do dynamic routing with Next.js based on slug from GraphQL API?
Asked Answered
O

1

6

I'm having a tough time getting my head around dynamic routing based on variables. I'm able to get a list of items in a collection but not an individual item and its fields for an individual page with a dynamic route with Next.js.

Background

I have a KeystoneJS headless CMS with a GraphQL API. I'm trying to create a simple blog with a list of posts and an individual post page. I have been able to query and return a list of posts, but I need to get an individual post based on the slug field so it can be accessed at /posts/[slug].js.

What I've tried

I've been using Apollo Client to handle the queries. I have an apolloClient.js file that connects to the API:

// apolloClient.js
import { ApolloClient, InMemoryCache } from "@apollo/client";

export default new ApolloClient({
  uri: "http://localhost:3000/admin/api",
  cache: new InMemoryCache(),
});

I have a post.service.js file to query the API:

// post.service.js
import { gql } from "@apollo/client";
import apolloClient from "../_utils/apolloClient";

export async function getAll() {
  return apolloClient
    .query({
      query: gql`
        query {
          allPosts {
            id
            term
            slug
          }
        }
      `,
    })
    .then((result) => result.data.allPosts);
}

export async function getBySlug(slug) {
  return apolloClient
    .query({
      query: gql`
        query {
          Post(slug: $slug) {
            id
            title
            lead
            body
          }
        }
      `,
    })
    .then((result) => {
      return result.data.Post;
    });
}

And finally, in posts/[slug].js I am trying to return the data like so:

//[slug].js
import Head from "next/head";
import { ApolloClient, InMemoryCache, gql } from "@apollo/client";
import { getAll, getBySlug } from "../../_services/post.service";

export default function Post({ post }) {
  return (
    <div>
      <Head>
        <title>Launchpad</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main>
        <h1>{post.term}</h1>
      </main>
      <footer>
        <p>{post.lead}</p>
      </footer>
    </div>
  );
}

export async function getStaticPaths() {
  const posts = await getAll();

  const paths = posts.map((post) => ({
    params: { id: post.slug },
  }));

  return { paths, fallback: false };
}

export async function getStaticProps({ params }) {
  const post = await getBySlug(params.id);

  return { props: { post } };
}

Obviously, this doesn't work. I must not be passing the variable (which I'm assuming is the slug) into the query properly and having read several tutorials I still can't get my head around it. Does anyone see what I'm doing wrong?

Ouse answered 20/1, 2021 at 23:17 Comment(2)
Couple of things: 1) in getStaticPaths you're saving await getAll() to lingo but then mapping the posts variable, is this a typo? 2) Doing the same in getStaticProps for the getBySlug call, using the term variable but then returning post.Flatto
Yes, copied code from the wrong file, thanks. I've updated the code in my original question. I have the same problem. Nothing is coming back.Ouse
F
1

In getStaticPaths the keys returned in the params objects need to match the dynamic route naming. In your case, since you're using posts/[slug].js for the route, you'd need to return params with the format { slug: post.slug }.

export async function getStaticPaths() {
  const posts = await getAll();

  const paths = posts.map((post) => ({
    params: { slug: post.slug }, // Rename to `slug`
  }));

  return { paths, fallback: false };
}

export async function getStaticProps({ params }) {
  const post = await getBySlug(params.slug); // Rename to `params.slug`

  return { props: { post } };
}

Edit: Regarding the request issue, the following change to getBySlug should make it work as expected.

export async function getBySlug(slug) {
  return apolloClient
    .query({
      query: gql`
        query Post($slug: String){
          post(slug: $slug) {
            id
            title
            lead
            body
          }
        }
      `,
      variables: {
        slug
      }
    })
    .then((result) => {
      return result.data.Post;
    });
}
Flatto answered 21/1, 2021 at 13:2 Comment(5)
Thanks. I've updated my code but I get the error: Error: Response not successful: Received status code 400. This suggests my query is wrong in post.service.js? Is my argument wrong?Ouse
Yes, that means something's wrong with the request.Flatto
Okay, thanks! I'll ask a question separately for how to query the API by slug as I have no idea how the query would know what the slug is.Ouse
Does this help: https://mcmap.net/q/468592/-apollo-query-with-variable? Shouldn't you pass an extra variables object to apolloClient.query() call? Note that my GraphQL knowledge is limited.Flatto
Thanks. Yes, it helped me write the query better. I still get an Apollo Error with my query so I've reached out for help here.Ouse

© 2022 - 2024 — McMap. All rights reserved.