Joint session with Vercel: How to build intelligent storefronts (May 15th)

useLoaderData undefined error with Sanity query in Remix loader

16 repliesLast updated: Dec 2, 2025

Not sure what's going on here, because the same code is working on another page but I am currently getting this error:

TypeError: Cannot destructure property 'posts' of '(0 , import_react27.useLoaderData)(...)' as it is undefined.
For this code:


import { useLoaderData } from "@remix-run/react";
import { Link } from "@remix-run/react";
import moment from "moment";
import { getClient } from "~/lib/sanity/getClient";
import { urlFor } from "~/lib/sanity/sanity";

// loader() must be async!
export async function loader() {
  const posts = await getClient().fetch(
    `*[_type == "post"]{ 
      _id, 
      title, 
      slug, 
      author,
      "authorName": author->name,
      "authorImage": author->image,
      mainImage, 
      publishedAt, 
      categories,
      excerpt, 
      myTags, 
      featuredPost,
      "categoryName": *[_type == 'category' && _id in ^.categories[]._ref]{title, slug},
    } | order(publishedAt desc) `
  );

  return { posts };
}

export default function LatestBlogPosts() {
  let { posts } = useLoaderData();

return (
    <section className="">
      <div className="relative pt-16 pb-20 px-4 sm:px-6 lg:pt-24 lg:pb-28 lg:px-8">
        <div className="absolute inset-0">
          <div className="h-1/3 sm:h-2/3" />
        </div>
        <div className="relative mx-auto">
          <div className="text-left">
            <h2 className="text-7xl tracking-tight font-black text-richBlack">
              ALL POSTS
            </h2>
          </div>
          <div className="mt-12 grid gap-5 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 lg:max-w-none">
            {posts?.length > 1
              ? posts.slice(0, 12).map((post: any) => (
Any help?
AI Update

This is a classic Remix loader issue where useLoaderData() returns undefined. Since you mentioned the same code works on another page, here are the most likely causes:

1. Loader Not Being Called (Most Common)

The most common reason is that your route file isn't properly exporting the loader or the file naming doesn't match Remix's routing conventions. Check:

export async function loader() {
  console.log("LOADER EXECUTING"); // Check server logs
  const posts = await getClient().fetch(/* ... */);
  console.log("Posts count:", posts?.length);
  return { posts };
}

If you don't see "LOADER EXECUTING" in your server console when you visit the page, the loader isn't running at all.

2. Client-Side Navigation Issue

If you're navigating to this page from another page, Remix might be trying to call the loader but getting an error that's swallowed. Add error handling:

export async function loader() {
  try {
    const posts = await getClient().fetch(
      `*[_type == "post"]{ 
        _id, 
        title, 
        slug, 
        author,
        "authorName": author->name,
        "authorImage": author->image,
        mainImage, 
        publishedAt, 
        categories,
        excerpt, 
        myTags, 
        featuredPost,
        "categoryName": *[_type == 'category' && _id in ^.categories[]._ref]{title, slug},
      } | order(publishedAt desc)`
    );
    
    return { posts: posts || [] };
  } catch (error) {
    console.error("Sanity fetch error:", error);
    throw new Response("Failed to load posts", { status: 500 });
  }
}

3. Sanity Client Configuration

Since it works elsewhere, this is less likely, but verify your getClient() function returns a properly configured client:

import { createClient } from "@sanity/client";

export function getClient() {
  return createClient({
    projectId: process.env.SANITY_PROJECT_ID, // or your hardcoded ID
    dataset: process.env.SANITY_DATASET || "production",
    apiVersion: "2024-01-01",
    useCdn: false, // Important for loaders
  });
}

4. Defensive Component Code

As a quick fix while debugging, make your component more defensive:

export default function LatestBlogPosts() {
  const data = useLoaderData();
  const posts = data?.posts || [];

  if (!posts || posts.length === 0) {
    return <div>No posts found</div>;
  }

  return (
    <section className="">
      {/* your existing JSX */}
    </section>
  );
}

5. Check for TypeScript Errors

If you're using TypeScript, make sure there are no type errors preventing the loader from compiling. Run npm run typecheck to verify.

Debugging Steps

The fact that identical code works on another page strongly suggests either a route configuration issue or the loader isn't being executed at all on this specific route. The console.log approach will quickly tell you which it is.

Show original thread
16 replies

Was this answer helpful?

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.

Related contributions