Check if Sanity post exists in React Router v5/v6 before rendering

7 replies
Last updated: Mar 10, 2021
How can we check inside React Router V5 or V6 if a Sanity.io [type="post"] Exists? Currently, the NotFound component will not work it always returns a empty page.

const App = () => {

return (

<BrowserRouter>

<Routes>

<Route element={<AllPosts />} path="/" exact />

<Route element={<BlogSinglePost />} path="/blog/:slug" />

<Route element={<NotFound />} path="*" />

</Routes>

</BrowserRouter>

);

};
AI Update

The issue you're experiencing is that your BlogSinglePost component needs to check if the document exists and handle the case when it doesn't. React Router will always match the /blog/:slug route before the * wildcard route, so you need to handle the 404 logic inside your BlogSinglePost component itself.

Here's how to fix this:

In your BlogSinglePost component:

import { useParams, Navigate } from 'react-router-dom';
import { useEffect, useState } from 'react';
import { client } from './sanityClient'; // your Sanity client

const BlogSinglePost = () => {
  const { slug } = useParams();
  const [post, setPost] = useState(null);
  const [loading, setLoading] = useState(true);
  const [notFound, setNotFound] = useState(false);

  useEffect(() => {
    const query = `*[_type == "post" && slug.current == $slug][0]`;
    
    client.fetch(query, { slug })
      .then((data) => {
        if (!data) {
          setNotFound(true);
        } else {
          setPost(data);
        }
        setLoading(false);
      })
      .catch((error) => {
        console.error('Error fetching post:', error);
        setNotFound(true);
        setLoading(false);
      });
  }, [slug]);

  if (loading) return <div>Loading...</div>;
  if (notFound) return <Navigate to="/404" replace />;

  return (
    <div>
      {/* Your post content */}
      <h1>{post.title}</h1>
      {/* ... */}
    </div>
  );
};

Update your routes:

const App = () => {
  return (
    <BrowserRouter>
      <Routes>
        <Route element={<AllPosts />} path="/" exact />
        <Route element={<BlogSinglePost />} path="/blog/:slug" />
        <Route element={<NotFound />} path="/404" />
        <Route element={<NotFound />} path="*" />
      </Routes>
    </BrowserRouter>
  );
};

Key points:

  1. The GROQ query with parameters *[_type == "post" && slug.current == $slug][0] filters documents by type and slug, using parameters for safe value passing
  2. The [0] at the end returns only the first match (or null if no match exists)
  3. When client.fetch returns null, you know the document doesn't exist
  4. Use React Router's <Navigate> component to redirect to your NotFound route when the post doesn't exist
  5. The wildcard * route will catch any other unmatched routes

This approach ensures that the document existence check happens at the data-fetching level, which is where it needs to be since React Router doesn't know anything about your Sanity content.

Show original thread
7 replies
Not an answer to your question. But couldn't you do this in the BlogSinglePost page instead, by checking if the data returned from the fetch command is an empty array?
import { useEffect, useState } from "react";

import { useParams } from "react-router-dom";


import SanityClient from "sanity.client";

import { getImage, TheContent } from "sanity.helpers";


import Loader from "components/Loader";


const BlogSinglePost = (props) => {

const [singlePost, setSinglePost] = useState(null);

const [loading, setLoading] = useState(true);

const { slug } = useParams();

const { projectId, dataset } = SanityClient.clientConfig;


useEffect(() => {
`const singlePostQuery = ``

*[slug.current == $slug] {

_id,

title,

slug,

mainImage {

alt,

asset -> {

_id,

url

}

},

body,

"name": author->name,

"authorImage": author->image,

"authorImageAlt": author->image

}
``;`


setTimeout(() => {

setLoading(false);

}, 500);


SanityClient.fetch(singlePostQuery, { slug })

.then((data) => (setSinglePost(data.length === 0) ? {} : data))

.catch(console.error);

}, [slug]);


// if (!singlePost || loading) return <Loader />;


if (!singlePost) return <div>Loading...</div>;

if (Object.keys(singlePost).length === 0)

return <div>Article not found...</div>;


return (

<>

{singlePost &&

singlePost.map((post) => (

<div key={post._id}>

<h2>{post.title}</h2>

<img src={getImage(post.authorImage).width(100).url()} alt="" />

<h4>{post.name}</h4>

{post.mainImage && (

<img

src={getImage(post.mainImage).width(860).url()}

alt={post.mainImage.alt}

/>

)}

<TheContent

blocks={post.body}

projectId={projectId}

dataset={dataset}

/>

</div>

))}

</>

);

};


export default BlogSinglePost;
Hi!
I have tried this. But for some reason it keeps loading.
.then((data) => (setSinglePost(data.length === 0) ? {} : data))

This looks like the parentheses aren’t ordered correctly. Would you mind trying:


.then((data) => (setSinglePost((data.length === 0) ? {} : data)))
user A
Hi there!
I have tried the code which you have shared. It works for articles that are not found. But for some reason, if it does exist it stops at loading..
Solved!!! Any idea how I can redirect to the React Router 404 page component?
user A
In this example it looks like they’re passing the 404 component via children rather than the
element
prop.

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?