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

Viewed 3358

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?

1 Answers

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;
    });
}
Related