Next.js Discord

Discord Forum

Need help: Returning a 404 if slug does not exist in Sanity CMS

Answered
Masai Lion posted this in #help-forum
Open in Discord
Masai LionOP
import { client, urlFor } from '@/lib/sanity'
import { Article } from '../../../../../interface'
import { notFound } from 'next/navigation'
import Container from '@/components/container'
import Image from 'next/image'
import { SanityDocument, groq } from 'next-sanity'
export const articlesPathsQuery = groq*[_type == "articles"][]{ "params": { "slug": slug.current } }
// Prepare Next.js to know which routes already exist
export async function generateStaticParams() {
const articles = await client.fetch(articlesPathsQuery)
console.log('articles', articles)
return articles.map((a: any) => a.params)
}
async function getArticles(category: string) {
const query = *[_type == 'articles' && category->title == "${category}"]{ _id, title, "category": category->title, "teaserImage": teaserImage.asset->url, "slug": slug.current, }
const data = await client.fetch(query)
return data
}
export default async function CategoryPage({ params }: { params: any }) {
const data: Article[] = await getArticles(params.category)
// console.log(data)
return (
<Container>
<h1 className='text-5xl first-letter:uppercase py-8'>
{params.category}
</h1>
{data.length ? (
<div className=''>
<div className='grid grid-cols-3'>
{data.map((article, i) => (
<div
key={i}
className='px-6 py-8 border col-span-1'
>
<Image
src={urlFor(article.teaserImage).url()}
width={500}
height={500}
alt='change me'
className='aspect-square object-cover'
/>
<h1>{article.title}</h1>
</div>
))}
</div>
</div>
) : (
<div className='pt-8'>
<p>No articles found in this category</p>
</div>
)}
</Container>
)
}
Answered by not-milo.tsx
Ok so, your current GROQ queries could be simplified a bit.

The first one can return just the slug like this:
const articlesPathsQuery = groq`
  *[_type == "articles"].slug.current
`


And in the second one instead of inlining the category with the ${} syntax you can leverage GROQ parameters and transform it in this:
const articlesQuery = groq`
  *[_type == 'articles' && category->title == $category]{
    ...
  }
`


Then when you execute the query you can pass the category parameter like so:
const data = await client.fetch(articlesQuery, { category })
View full answer

26 Replies

Masai LionOP
Now I'm receiving slugs back from the generateStaticParams: they return: articles [
{ params: { slug: 'best-burger-restaurants-in-boston' } },
{
params: { slug: 'where-to-watch-boston-christmas-tree-lightings-2023' }
}
]
Now the articles are displaying correctly relative to the category. However, I need some help with the logic to return a 404 if the category doesn't exist. For instance when a user types: /blog/categories/jknasd. I would like to display a 404.
@not-milo.tsx 👋
generateStaticParams should return an array of objects containing the parameter you declared in your route.

So if your page is inside ./app/articles/[slug]/page.tsx the objects returned should look like this:
{
  slug: "where-to-watch-boston-christmas-tree-lightings-2023",
}
Masai LionOP
I thought so. That was my attempt here:
export async function generateStaticParams() {
const articles = await client.fetch(articlesPathsQuery)
console.log('articles', articles)
return articles.map((a: any) => a.params)
}
You're doing a lot of extra work in your GROQ query... I'll give you a detailed example as soon as I get back to my desk (15min~)
Masai LionOP
Have to destructure but brain melted after a while of trying lol
Okay thanks so much, no idea how much I appreciate you right now
Ok so, your current GROQ queries could be simplified a bit.

The first one can return just the slug like this:
const articlesPathsQuery = groq`
  *[_type == "articles"].slug.current
`


And in the second one instead of inlining the category with the ${} syntax you can leverage GROQ parameters and transform it in this:
const articlesQuery = groq`
  *[_type == 'articles' && category->title == $category]{
    ...
  }
`


Then when you execute the query you can pass the category parameter like so:
const data = await client.fetch(articlesQuery, { category })
Answer
The other thing is: if you're trying to build a category page you shouldn't fetch the article slugs but the category titles instead and return those from getStaticParams.

So if you have a page under ./app/category/[title]/page.tsx then your getStaticParams should look somethinglike this:
const categoryTitlesQuery = groq`
  *[_type == "category"].title
`

export const getStaticParams = () => {
  const titles = (await client.fetch(categoryTitlesQuery)) as string[]

  return titles.map((title) => ({ params: { title } }))
}
Masai LionOP
Right the simplyfying is much cleaner. Fetching the category title makes a lot of sense. I was trying to cross check the article title with the category title. Is it okay to send you my github repo so you can check out the category page. I'm so close to doing this!
Oh yeah, go ahead 👍🏻
Masai LionOP
the sanity id is hardcoded as well so you may be able to see the data as well. Let me know if you run into any issues.
Yeah, you're almost there... You just need to change those two things I pointed out and it should work.
Masai LionOP
Thanks so much. I'm gonna try again tomorrow. Good night buddy!
Take your time! 😉
Masai LionOP
Haha, I went back 😅. I have it working here:
import { client, urlFor } from '@/lib/sanity'
import { Article } from '../../../../../interface'
import { notFound } from 'next/navigation'
import Container from '@/components/container'
import Image from 'next/image'
import {groq } from 'next-sanity'

const categoryTitlesQuery = groq`*[_type == "categories"].title`
export async function generateStaticParams() {
  const titles = (await client.fetch(categoryTitlesQuery)) as string[]
  return titles
}
async function getArticles(category: string) {
  const query = `*[_type == 'articles' && category->title == "${category}"]{
    _id,
    title,
    "category": category->title,
      "teaserImage": teaserImage.asset->url,
      "slug": slug.current,
  }
`
  const data = await client.fetch(query)
  return data
}

export default async function CategoryPage({ params }: { params: any }) {
  const data: Article[] = await getArticles(params.category)

  const titles = await client.fetch(categoryTitlesQuery)

  return (
    <Container>
      <h1 className='text-5xl first-letter:uppercase py-8'>
        {params.category}
      </h1>
      {params.category && titles.includes(params.category) ? (
        data.length > 0 ? (
          <div className='grid grid-cols-3'>
            {data.map((article, i) => (
              <div
                key={i}
                className='px-6 py-8 border col-span-1'
              >
                <Image
                  src={urlFor(article.teaserImage).url()}
                  width={500}
                  height={500}
                  alt='change me'
                  className='aspect-square object-cover'
                />
                <h1>{article.title}</h1>
              </div>
            ))}
          </div>
        ) : (
          <div className='pt-8'>
            <p>No articles found in this category</p>
          </div>
        )
      ) : (
        notFound()
      )}
    </Container>
  )
}
@Masai Lion import { client, urlFor } from '@/lib/sanity' import { Article } from '../../../../../interface' import { notFound } from 'next/navigation' import Container from '@/components/container' import Image from 'next/image' import {groq } from 'next-sanity' const categoryTitlesQuery = groq`*[_type == "categories"].title` export async function generateStaticParams() { const titles = (await client.fetch(categoryTitlesQuery)) as string[] return titles } async function getArticles(category: string) { const query = `*[_type == 'articles' && category->title == "${category}"]{ _id, title, "category": category->title, "teaserImage": teaserImage.asset->url, "slug": slug.current, } ` const data = await client.fetch(query) return data } export default async function CategoryPage({ params }: { params: any }) { const data: Article[] = await getArticles(params.category) const titles = await client.fetch(categoryTitlesQuery) return ( <Container> <h1 className='text-5xl first-letter:uppercase py-8'> {params.category} </h1> {params.category && titles.includes(params.category) ? ( data.length > 0 ? ( <div className='grid grid-cols-3'> {data.map((article, i) => ( <div key={i} className='px-6 py-8 border col-span-1' > <Image src={urlFor(article.teaserImage).url()} width={500} height={500} alt='change me' className='aspect-square object-cover' /> <h1>{article.title}</h1> </div> ))} </div> ) : ( <div className='pt-8'> <p>No articles found in this category</p> </div> ) ) : ( notFound() )} </Container> ) }
It's not quite right tho... You shouldn't directly return your category titles, but instead map over them like this:
return titles.map(title => ({ category: title }))
Once you do this you can remove the notFound check and replace it with the export of dynamicParams.
export const dynamicParams = false
Masai LionOP
export const dynamicParams = false
export default async function CategoryPage({ params }: { params: any }) {
  const data: Article[] = await getArticles(params.category)
  return (
    <Container>
      <h1 className='text-5xl first-letter:uppercase py-8'>
        {params.category}
      </h1>

      {data.length > 0 ? (
        <div className='grid grid-cols-3'>
          {data.map((article, i) => (
            <div
              key={i}
              className='px-6 py-8 border col-span-1'
            >
              <Image
                src={urlFor(article.teaserImage).url()}
                width={500}
                height={500}
                alt='change me'
                className='aspect-square object-cover'
              />
              <h1>{article.title}</h1>
            </div>
          ))}
        </div>
      ) : (
        <div className='pt-8'>
          <p>No articles found in this category</p>
        </div>
      )}
    </Container>
  )
}
This is cleaner. Works correctly
Thanks so much. How long have you been working with Sanity cms?
@Masai Lion Thanks so much. How long have you been working with Sanity cms?
It's been almost 4 years now ✌🏻