🔮 Sanity Create is here. Writing is reinvented. Try now, no developer setup

Discussion about creating a catch-all route within the app directory in Next.js and Sanity.io.

14 replies
Last updated: Mar 17, 2023
Has anyone made a catch all route within the app dir?
Mar 15, 2023, 8:39 PM
Yes, I made one yesterday, I'll try to provide a base example!
Mar 16, 2023, 7:05 AM
This example assumes you have two document types:
page
and
post
. My post slugs all start with
/post
:
/post/the_post_title

app/[[..slug]]/page.tsx
:
import { groq } from 'next-sanity'
import getQueryFromSlug from '@helpers/getQueryFromSlug'
import { sanityClient } from '@lib/sanity.server'
import SinglePage from './single-page'

export async function generateStaticParams() {
  const paths = await sanityClient.fetch(groq`*[_type in ["page", "post"] && defined(slug.current)][].slug.current`)

  return paths.map((slug: string) => ({
    slug: slug.split('/').filter((p) => p),
  }))
}

/**
 * Helper function to return the correct version of the document
 * If we're in "preview mode" and have multiple documents, return the draft
 */
function filterDataToSingleItem(data: object | any, preview = false) {
  if (!Array.isArray(data)) {
    return data
  }

  if (data.length === 1) {
    return data[0]
  }

  if (preview) {
    return data.find((item) => item._id.startsWith(`drafts.`)) || data[0]
  }

  return data[0]
}

export default async function Page({ params }: { params: { slug: string[] } }) {
  const { slug } = params

  const { query, queryParams, docType } = getQueryFromSlug(slug)

  const pageData = await sanityClient.fetch(query, queryParams)

  const data = filterDataToSingleItem(pageData, false)

  return (
    <>
      {docType === 'page' && <SinglePage data={pageData} />}
    </>
  )
}

helpers/getQueryFromSlug
:
import { groq } from 'next-sanity'

const getQueryFromSlug = (slugArray = []) => {
  const docQuery = {
    post: groq`*[_type == "post" && slug.current == $slug][0] {
      title,
      _type,
      "slug": slug.current,
    }`,
    page: groq`*[_type == "page" && slug.current == $slug][0] {
      title,
      _type,
      "slug": slug.current,
    }`,
  }

  let docType = ''

  const [slugStart] = slugArray

  // We now have to re-combine the slug array to match our slug in Sanity.
  let queryParams = { slug: `/${slugArray.join('/')}` }

  if (slugStart === 'post' && slugArray.length === 2) {
    docType = `post`
  } else {
    docType = `page`
  }

  return {
    docType,
    queryParams,
    query: docQuery[docType],
  }
}

export default getQueryFromSlug

single-page.tsx
:
'use client'

import React from 'react'

interface SinglePageProps {
  data: object | any
}

const SinglePage = ({ data }: SinglePageProps) => {
  console.log(data)
  return (
    <>
      Page
    </>
  )
}

export default SinglePage
This might be a lot to take in, please let me know if you get stuck on a certain step somewhere.

Before moving to the app dir, I followed this article for the base setup:
https://www.simeongriggs.dev/nextjs-sanity-slug-patterns
Mar 16, 2023, 7:19 AM
Takk Henrik! Ill take a look riiight now .
Mar 16, 2023, 8:34 AM
Do you still have a page.tsx in the root folder, or is the [[..slug]]/page.tsx used to catch everything including the index page? I have a page builder where everything is modular and my current setup is like this image here, I am trying to wrap my head around the best structure for it, the nesting is not going further than
/post/post1
on this particular project, but I might as well learn the “correct” way to do it to begin with. I will only have /page and /stories/story as slugs. Ive added a snap from my slug route as well, doesn’t seem like i need tooo much altering of my code to make it work? Ignore the : any`s, im learning ts and next at the same time. I will let you know when and if I figure this out! Thank you so much for the example!
Mar 16, 2023, 11:14 AM
Aaa sorry, I catch my index route there as well, should've added that to the example I provided
Mar 16, 2023, 11:41 AM
My
/app
folder looks like this
Mar 16, 2023, 11:42 AM
You seem very close to make it work, let me know if I can assist you more.
I'm not myself sure that my approach is the "correct" way to do it but I've enjoyed working like that so far, at least for smaller, less complex projects
🙂
Mar 16, 2023, 11:44 AM
Yeah ive been loving the appdir, really nice to do everything inside the component 🙂
Mar 16, 2023, 11:48 AM
struggling a bit to get the dynamic fetch to work, i got it to work doing it like in the picture, but this is perhaps considered bad form? feels a bit hacky.
Mar 16, 2023, 8:26 PM
Thanks Henrik, made it work!
Mar 16, 2023, 8:58 PM
Awesome, glad to hear that! 🤟 👏
Mar 16, 2023, 9:00 PM
If anyone from the future needs some ideas, ill supply some of my code for a modular setup in the appdir, now on to the next problem, the preview! 😆
Mar 16, 2023, 10:24 PM
I recently ran into the same issue where I wanted the latest draft returned, and not the original when doing previews. I used
order(_updatedAt desc)
to get the draft first instead of the original: https://sanity-io-land.slack.com/archives/C9Z7RC3V1/p1678866113734179?thread_ts=1675954847.939669&amp;cid=C9Z7RC3V1 . No need for any parsing in code afterwards 🙂
Mar 17, 2023, 9:29 AM
Nice Jon Espen! Ill take a look, thanks! 🙂
Mar 17, 2023, 9:31 AM

Sanity– build remarkable experiences at scale

Sanity is a modern headless CMS that treats content as data to power your digital business. Free to get started, and pay-as-you-go on all plans.

Was this answer helpful?