Trouble deploying Next JS & Vercel blogs page, resolved using Sanity Client.

20 replies
Last updated: Jul 22, 2021
Hey guys, I'm kind of having some trouble with deploying my blogs page using
Next JS & Vercel
. The
blogs page & [Slug] page
runs absolutely fine on 
localhost:3000
 but throws an error on a deployed website 
500 | Internal Server Error
, You can have a look here In case If you want to 
<https://www.mahijendra.dev/>
. I'm not sure know what I'm doing wrong here.

This is my pages/blog.js code
 
import React, { useState, useEffect } from "react";
import imageUrlBuilder from "@sanity/image-url";
import { sanityClient } from "../lib/sanityClient";
import PostCard from "../components/PostCard"
import MainNav from "../components/MainNav";
import BlogCard from "../components/BlogCard";

const Blog = ({ posts }) => {
    const [header] = useState({
        mainHeader: "BLOGS",
        subHeading: "My Blogs",
        text:
            "Blah Blah"
    });

    const [mappedPosts, setMappedPosts] = useState([]);

    useEffect(() => {
        if (posts.length) {
            const imageBuilder = imageUrlBuilder(sanityClient);

            setMappedPosts(
                posts.map((post) => {
                    return {
                        ...post,
                        mainImage: imageBuilder
                            .image(post.mainImage)
                            .width(450)
                            .height(500),
                    };
                })
            );
        } else {
            setMappedPosts([]);
        }
    }, [posts]);

    return (
        <>
           <MainNav />

            <div className="common">
                <h3 className="font-semibold text-customGreen py-3 pt-12 tracking-wide font-nunito">{header.mainHeader}</h3>
                <h1 className="font-bold lg:text-3xl text-customGreen tracking-wide font-nunito">{header.subHeading}</h1>
                <p className="font-normal lg:text-md text-customGreen tracking-wide font-nunito">{header.text}</p>
                <div className="commonBorder"></div>
            </div>

            <div className="pt-16 pb-6 px-4 sm:px-6 lg:pt-8 xl:mx-48" >
                <div className="">
                    {mappedPosts &&
                    mappedPosts.length &&
                    mappedPosts.map((post, index) => (
                        <BlogCard data={post} key={index} />
                    ))}
                </div>
            </div>
        </>
    );
};

export const getServerSideProps = async (context) => {
    const query = encodeURIComponent(`*[ _type == "post" ]`);
    const url = `${process.env.SANITY_URL}query=${query}`;

    const data = await fetch(url).then((res) => res.json());
    const posts = data.result;

    if (!posts || !posts.length === 0) {
        return {
            props: {
                posts: [],
            },
        };
    } else {
        return {
            props: {
                posts,
            },
        };
    }
};

export default Blog
And the SANITY_URL stands for this which is located in my
.env.local

SANITY_URL = <https://projectID.api.sanity.io/v2021-06-07/data/query/production>?

And the error that I keep seeing on
vercel realtime logs
is

[GET] /blog

2021-07-22T11:08:58.538Z	3f8b63af-f76f-47d6-b88f-dbeb20a4cf52	ERROR	TypeError: Only absolute URLs are supported
    at getNodeRequestOptions (/var/task/node_modules/node-fetch/lib/index.js:1305:9)
    at /var/task/node_modules/node-fetch/lib/index.js:1410:19
    at new Promise (<anonymous>)
    at fetch (/var/task/node_modules/node-fetch/lib/index.js:1407:9)
    at getServerSideProps (/var/task/.next/server/pages/blogNew.js:250:22)
    at renderToHTML (/var/task/node_modules/next/dist/next-server/server/render.js:39:221)
    at processTicksAndRejections (internal/process/task_queues.js:95:5)
    at async /var/task/node_modules/next/dist/next-server/server/next-server.js:112:97
    at async /var/task/node_modules/next/dist/next-server/server/next-server.js:105:142
    at async Server.renderToHTMLWithComponents (/var/task/node_modules/next/dist/next-server/server/next-server.js:137:387)
RequestId: 3f8b63af-f76f-47d6-b88f-dbeb20a4cf52 Error: Runtime exited with error: exit status 1
Runtime.ExitError

[GET] /robots.txt
robots.txt
2021-07-22T10:51:22.797Z	9fb2b9e1-9155-49d1-af6a-82b43494503c	ERROR	TypeError: Only absolute URLs are supported
    at getNodeRequestOptions (/var/task/node_modules/node-fetch/lib/index.js:1305:9)
    at /var/task/node_modules/node-fetch/lib/index.js:1410:19
    at new Promise (<anonymous>)
    at fetch (/var/task/node_modules/node-fetch/lib/index.js:1407:9)
    at getServerSideProps (/var/task/.next/server/pages/[slug].js:416:22)
    at renderToHTML (/var/task/node_modules/next/dist/next-server/server/render.js:39:221)
    at processTicksAndRejections (internal/process/task_queues.js:95:5)
    at async /var/task/node_modules/next/dist/next-server/server/next-server.js:112:97
    at async /var/task/node_modules/next/dist/next-server/server/next-server.js:105:142
    at async Server.renderToHTMLWithComponents (/var/task/node_modules/next/dist/next-server/server/next-server.js:137:387)
RequestId: 9fb2b9e1-9155-49d1-af6a-82b43494503c Error: Runtime exited with error: exit status 1
Runtime.ExitError
AI Update

The error message TypeError: Only absolute URLs are supported is the key here - it's telling you exactly what's wrong. Your code works locally because your .env.local file is available, but environment variables aren't automatically transferred to Vercel when you deploy.

The issue is that process.env.SANITY_URL is likely undefined in your Vercel deployment, which means you're trying to fetch from an incomplete URL (just query=... without the base URL part), and node-fetch requires absolute URLs.

Here's how to fix it:

  1. Add your environment variable to Vercel:

    • Go to your Vercel project dashboard
    • Navigate to Settings → Environment Variables
    • Add SANITY_URL with the value: https://projectID.api.sanity.io/v2021-06-07/data/query/production? (replace projectID with your actual project ID)
    • Make sure it's available for Production, Preview, and Development environments
    • Redeploy your site (Vercel usually auto-redeploys when you add env vars, but you can trigger it manually)
  2. Better yet, use the official Sanity client instead of manual fetch:

Your current approach works, but using the official @sanity/client is more robust and handles a lot of edge cases for you:

import { createClient } from '@sanity/client'

export const sanityClient = createClient({
  projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,
  dataset: 'production',
  apiVersion: '2021-06-07',
  useCdn: true,
})

// Then in getServerSideProps:
export const getServerSideProps = async (context) => {
  const posts = await sanityClient.fetch(`*[_type == "post"]`);
  
  return {
    props: {
      posts: posts || [],
    },
  };
};

This way you'd add NEXT_PUBLIC_SANITY_PROJECT_ID to your Vercel environment variables instead.

  1. Add a fallback check (good practice):
export const getServerSideProps = async (context) => {
  if (!process.env.SANITY_URL) {
    console.error('SANITY_URL is not defined');
    return { props: { posts: [] } };
  }
  
  const query = encodeURIComponent(`*[ _type == "post" ]`);
  const url = `${process.env.SANITY_URL}query=${query}`;
  // ... rest of your code
};

The most common mistake here is forgetting to add environment variables to Vercel - they don't get transferred from your local .env.local file automatically for security reasons. Once you add them in the Vercel dashboard, your deployment should work just like it does locally!

It's much easier to write code with Sanity Client. I'd recommend using that instead of 'fetching' a URL

https://www.sanity.io/docs/js-client
Create the instance of the client and use it's fetch method to execute GROQ queries.
More about GROQ Queries here:
https://www.sanity.io/docs/query-cheat-sheet
I think the issue with your URL is that it does not have a '?' symbol before parameters. Creating a URL can be error prone, and therefore it's better to go for the Sanity Client
Alright sure I'll give it a try using Sanity client. Thank you!
Yeah I did deploy
user T
How's things going? did you try Sanity Client?
Previously until yesterday I was using
ServerSideProps
to fetch the data. Now I Switched to
sanityClient
as User suggested.
ServerSideProps is for server-side rendering. If you want server side rendering, you have to fetch using sanity client, inside ServerSideProps
Yeah okay. It keeps throwing me this error
ReferenceError: slug is not defined
How do I declare this using Next JS?

import React, {useState, useEffect} from "react";
import imageUrlBuilder from "@sanity/image-url";
import MainNav from "../components/MainNav";
import Link from "next/Link"
import BlogCard from "../components/BlogCard";
import sanityClient from "../lib/client"

const Blog = ({posts, props}) => {
    const [header] = useState({
        mainHeader: "BLOGS",
        subHeading: "My Blogs",
        text:
            "I am a developer, primarily active in the Hashnode community as a blogger. I like writing about web development on my blog."
    });
    const [mappedPosts, setMappedPosts] = useState([]);

    useEffect(() => {
        sanityClient
            .fetch(`*[_type == "post"]{
            title,
            slug,
            mainImage{
            asset-> {
                _id,
                url
              },
             alt
            }
        }`)
            .then((data) => setMappedPosts(data))
            .catch(console.error);
    }, [])

  
    return (
        <>
            <MainNav/>
            <div className="common">
                <h3 className="font-semibold text-customGreen py-3 pt-12 tracking-wide font-nunito">{header.mainHeader}</h3>
                <h1 className="font-bold lg:text-3xl text-customGreen tracking-wide font-nunito">{header.subHeading}</h1>
                <p className="font-normal lg:text-md text-customGreen tracking-wide font-nunito">{header.text}</p>
                <div className="commonBorder"></div>
            </div>


            <div className="pt-16 pb-6 px-4 sm:px-6 lg:pt-8 xl:mx-48">
                <div className="">
                    {mappedPosts &&
                    mappedPosts.length &&
                    mappedPosts.map((post, index) => (
                        <Link href={"/"}  to={"/post/" + post.slug.current} key={post.slug.current} passHref>
                            <div key={index}>
                                <div className="pt-4 pb-4 px-4 sm:px-6 lg:pt-4 lg:pb-4">
                                    <div className="relative max-w-2xl mx-auto divide-y-2 divide-gray-200 lg:max-w-3xl flex justifyCenter">
                                        <div
                                            className="grid gap-16 lg:gap-x-5 lg:gap-y-8 px-12 py-8 shadow-xl overflow-hidden md:max-w-2xl mt-5 transform hover:scale-105 duration-500 ease-in-out">
                                            <div key={post.title}>
                                                <a href={slug} className="mt-2 block">
                                                    <p className="text-2xl font-semibold font-quicksand text-customGreen">{post.title}</p>
                                                    <p className="mt-3 text-sm font-medium tracking-wide  font-quicksand text-lightGreen ">{post.content}</p>
                                                    <p className="mt-3 lg:mt-6 text-sm text-lightGreen font-quicksand tracking-wide italic">{post.publishedAt} | {post.description}</p>
                                                </a>
                                            </div>
                                        </div>
                                    </div>
                                </div>

                            </div>
                        </Link>
                    ))}
                </div>
            </div>
        </>
    );
};

export default Blog

Yeah alright. I catch your drift
Is this the right way to define your
slug
in the blogs page?
    const router = useRouter()
    const { slug } = router.query
router.pathname will give you the slug
How do I get the pathname here?
router.pathnamethats enough
oh you're trying to get the slug for [slug].js?
yeah exactly, to the blogs page
When I'm using
router.query
, It's redirecting me to the main page I mean to
localhost:3000
on click on the blog or an article
Please refer to the link I shared
Yeah I actually read this one. And also the Migrating from react router to Next Js one
I'm still not able to fetch the data, I mean
slug
. Not sure what I'm doing wrong

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?