DEV Community

Julian Bustos
Julian Bustos

Posted on • Edited on

How to Create a Custom Deploy (Vercel/Netlify) Button for your Sanity Project

Deploying a Sanity project to Vercel or Netlify has never been easier, thanks to their deploy hooks. But what if you want to take it a step further and create a custom deploy button that suits your unique project setup? In this guide, we’ll walk you through the process of creating a custom deploy button tailored specifically for your Sanity project, ensuring a seamless deployment experience for you and your team.

What you will need

Assuming you have a Next.js site with Sanity CMS v3 integrated, you’ll need a few essentials to create a custom deploy button. First, obtain a deploy hook URL from your deployment provider, which in this case is Vercel. This URL will be used to trigger the redeployment of your project whenever the button is clicked. Additionally, ensure you have a running Sanity CMS project, specifically version 3, as this guide will focus on building the custom button for this version of Sanity. With these components in place, you’ll be able to set up a streamlined deployment workflow tailored to your project.

We will also need to add the @sanity/ui library

npm install @sanity/ui

Adding a Custom Deploy Button to the Sanity Dashboard

The goal is to have a deploy button conveniently located on the top right corner of your Sanity dashboard. To achieve this, we’ll need to customize the Sanity navbar by creating a custom navigation component. Sanity allows us to extend the default UI, so we’ll make use of that flexibility to insert our deploy button.

Let’s start by creating a custom navigation component. This component will be responsible for adding the button to the navbar, allowing users to trigger a deployment directly from within the dashboard. Here's how to get started with the custom navbar component.

We'll first just render the custom sanity navbar as we just want to add to it.

I like to keep my sanity code in one place and I have a directory called components. Inside of it we can create our file CustomNavbar.tsx

// sanity/components/CustomNavbar.tsx
import { NavbarProps } from "sanity";

export function CustomNavbar(props: NavbarProps) {
  const { renderDefault } = props;

  return (
    <>
      {renderDefault(props)}
    </>
  )
}
Enter fullscreen mode Exit fullscreen mode

It won't work without implementing it, so let's import it in our sanity config. We can add to the studio dashboard custom components.

// sanityConfig.ts
import { CustomNavbar } from "./components/NewCustomNavbar";

export const sanityAdminConfig = {
  projectId: process.env.SANITY_PROJECT_ID || "",
  dataset,
  useCdn: process.env.NODE_ENV === "production",
  title: "Your Site Title",
  apiVersion: "2021-10-21",
  basePath: "/studio", // hosted studio path
  plugins: [
  ],
  schema: {
    // your schemas
  },
};

export const studioConfig = defineConfig({
  ...sanityAdminConfig,
  studio: {
    components: {
      navbar: CustomNavbar,
    },
  },
});

Enter fullscreen mode Exit fullscreen mode

Awesome! Now let's add a button to our nav, and let's add some sanity UI elements to our nav to make it look better.

import { Box, Button, Flex } from "@sanity/ui";
import { NavbarProps } from "sanity";

export function CustomNavbar(props: NavbarProps) {
  const { renderDefault } = props;

  return (
    <Flex align="center">
      <Box flex={1}>
        {renderDefault(props)}
      </Box>
      <Button
        tone="positive"
        mode="ghost"
        style={{
          height: "100%",
          borderRadius: 0,
        }}
      >
        Deploy
      </Button>
    </Flex>
  )
}
Enter fullscreen mode Exit fullscreen mode

Great! Now let's prepare a function to trigger our deploy hook, and we can add sanity's toasts to give appropriate messages to the the user.

Here we'll need to add our deploy hook, I stored it in my env as NEXT_PUBLIC_DEPLOY_HOOK.

Since we are using the toast to give messages we will need to declare this function inside of our custom nav component.

// sanity/components/CustomNavbar.tsx
import { Box, Button, Flex, useToast } from "@sanity/ui";

export function CustomNavbar(props: NavbarProps) {
  // ... rest of your code
  const toast = useToast();
  const deployHookUrl = process.env.NEXT_PUBLIC_DEPLOY_HOOK;

  const deploySite = async () => {

    if (!deployHookUrl) {
      toast.push({
        status: 'error',
        title: 'Deployment Failed',
        description: 'No Vercel deploy hook found. Please check your environment variables.',
      });
      return;
    }

    try {
      const response = await fetch(deployHookUrl, {
        method: 'POST',
      });

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      toast.push({
        status: 'success',
        title: 'Deployment Triggered',
        description: 'Deployment has been triggered successfully. It may take a few minutes to complete.',
      });
    } catch (error) {
      console.error('Deployment failed:', error);
      toast.push({
        status: 'error',
        title: 'Deployment Failed',
        description: error instanceof Error ? error.message : 'An unknown error occurred',
      });
    }
  };
  return (
    <Flex align="center">
      <Box flex={1}>
        {renderDefault(props)}
      </Box>
      <Button
        tone="positive"
        mode="ghost"
        style={{
          height: "100%",
          borderRadius: 0,
        }}
      >
        Deploy
      </Button>
    </Flex>
  )
}

Enter fullscreen mode Exit fullscreen mode

Great now lets prepare a handleDeploy function that we can call in our button. We will do this so we can trigger a timeout in the end and give our users some messages and enable and disable the button. We will also add a state to keep track of wether we are deploying or not.

export function CustomNavbar(props: NavbarProps) {
  // ... rest of your code
    const [isDeploying, setIsDeploying] = useState(false);

      const handleDeploy = async () => {
    setIsDeploying(true);
    await deploySite();

    // Re-enable the button after 1 minute
    setTimeout(() => {
      setIsDeploying(false);
    }, 60000); // 60000 ms = 1 minute
  };
  return (
    <Flex align="center">
      <Box flex={1}>
        {renderDefault(props)}
      </Box>
      <Button
        onClick={handleDeploy}
        disabled={isDeploying}

        tone="positive"
        mode="ghost"
        style={{
          height: "100%",
          borderRadius: 0,
        }}
      >
        {isDeploying ? 'Deploying...' : 'Deploy'}

      </Button>
    </Flex>
  )
}
Enter fullscreen mode Exit fullscreen mode

The reason we are using setTimeout is that, unfortunately, there isn't a built-in way to listen for the completion of the build process directly. This workaround ensures that the deployment is triggered effectively from within your Sanity CMS project without relying on third-party applications.

If you have any suggestions or methods for monitoring the build state changes more accurately, I would love to hear your thoughts or read about it.

Thank you for reading, and I hope you found this guide useful for enhancing your deployment workflow.

Top comments (0)