website-logomedCode

How to Build a Portfolio Site with Sanity and Next.js

Create stunning portfolio sites with Sanity and Next.js. Learn step-by-step tutorials, tips, and tricks for seamless integration and beautiful design....
MOHAMMED DAKIR| November 16, 2023
next.js
How to Build a Portfolio Site with Sanity and Next.js

#dev #sanity #nextjs #javascript #blog

What is Sanity?

Sanity is a headless CMS framework for managing content. It provides tools to leverage APIs to connect to your web app providing instantaneous, rich and automated infrastructure for managing content on the cloud.

With Sanity, you can hook up pages or content that require regular updating to the studio and manage them from the content lake without having to touch code frequently. This makes the content creation and management process accessible to more people regardless of their technical background.

Step 1: Install Next.js

Open a terminal and run this command to install the latest version of Next.js:

npx create-next-app@latest

Select all your preferred install options. Except for the project name, I'll go with the default options.

What is your project named? ... sanity-nextjs-site
√ Would you like to use TypeScript with this project? ... YesWould you like to use ESLint with this project? ... YesWould you like to use Tailwind CSS with this project? ... YesWould you like to use `src/` directory with this project? ... NoWould you like to use App Router? (recommended) ... YesWould you like to customize the default import alias? ... No

This should install all the required dependencies, including Tailwind CSS into the project folder. To see it live run the command below:

cd sanity-nextjs-site

npm run dev

Visit http://localhost:3000 to see the site.

Step 2: Setup Sanity Studio

Sanity studio is Sanity's open source single-page app for managing your data and operations. This is the interface from which you can create, delete, and update your data within Sanity.

Install Sanity Studio

Open up a new terminal outside of your Next.js application and type the commands below:

$ Project name: Sanity Next.js Site
$ Use the default dataset configuration?: Yes
$ Project output path: C:\Users\USER\Desktop\sanity-studio
$ Select project template: Clean project with no predefined schemas
$ Do you want to use TypeScript? Yes
$ Package manager to use for installing dependencies?: npm

Once completed, this should install Sanity studio locally. To see the studio, run npm run dev and visit localhost:3333, log into your account using the same method used in creating your account, and you should see the studio running locally.

Step 3: Mount Sanity Studio into Next.js

You can choose to host your studio separately, but in this tutorial you'll be mounting it together with your Next.js application using the next-sanity toolkit.

End the server running your Next app and run this command:

npm install sanity next-sanity

And then on the sanity-studio directory running the studio locally, copy the schema folder and sanity.config.ts file and paste into the root of your Next.js app.

The folder structure should look like this:

├── .next
├── app/
├── node_modules/
├── public/
├── schemas/
│   └── index.ts
├── .eslintrc.json
├── .gitignore
├── next-env.d.ts
├── next.config.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── README.md
├── sanity.config.ts
├── tailwind.config.js
└── tsconfig.json

Next, inside the sanity.config.ts file, add a basePath key and give it a value of /studio or any valid URL path where you would like your studio to live.

// sanity.config.ts

import { defineConfig } from "sanity";
import { deskTool } from "sanity/desk";
import { schemaTypes } from "./schemas";

export default defineConfig({
  name: "sanity-nextjs-site",
  title: "Sanity Next.js Site",
  projectId: "ga8lllhf",
  dataset: "production",
  basePath: "/studio",
  plugins: [deskTool()],
  schema: { types: schemaTypes },
});

Here's a breakdown of each property:

  • name: Used to differentiate workspaces. Not compulsory for single workspace setup.
  • title: Title of your project. This will show up on the Studio.
  • projectId: This is a unique ID that points to the Sanity project you're working with.
  • dataset: The name of the dataset to use for your studio. Common names are production and development.
  • basePath: This is the URL path where your studio will be mounted.
  • schema: The object where your schema files will be defined.

Create the Studio Component

This is where the studio page will be mounted within your Next app. You can name this file whatever you prefer, but it must match with the basePath key specified inside the sanity.config.ts file. In my case, the file name will be studio.

To create the studio route, we'll utilize Next.js dynamic segments. Inside the app directory, create a studio/[[...index]]/page.tsx file.

app/
└── studio/
    └── [[...index]]/
         └── page.tsx

With this, when you visit any route that matches with /studio, the studio component page.tsx will be rendered.

To complete this setup, paste this code inside the component:

// app/studio/[[...index]]/page.tsx

"use client";

import { NextStudio } from "next-sanity/studio";
import config from "@/sanity.config";

export default function Studio() {
  return <NextStudio config={config} />;
}

First, NextStudio is imported from the next-sanity library and the configuration file is imported from the sanity.config.ts file you created earlier.

Now run npm run dev and visit localhost:3000/studio. You will get a prompt to add localhost:3000 as a CORS origin to your Sanity project. Just click continue to add the URL.

Once added, log into your Sanity account using the same method you used in creating your account and you should see the Studio mounted into your Next.js application

Step 4: Create Content Schemas

Schemas are essentially a way of organizing datasets in a database depending on what type of content you need.

Since we're building a portfolio site, we'll create schemas to handle projects, profile, and so on. To be more specific, you'll create three schemas files for this portfolio project:

  • profile: Schema file for defining your personal information like name, about, skills, and so on.
  • project: Schema file for your projects.
  • work: Schema file for defining your work experience.

Let's start with the profile schema.

Profile Schema

Inside the schemas directory, create a profile.ts file.

touch schemas/profile.ts

Let's start by defining the basic properties of a schema file.

// schemas/profile.ts

import { defineField } from "sanity";
import { BiUser } from "react-icons/bi";

const profile = {
  name: "profile",
  title: "Profile",
  type: "document",
  icon: BiUser,
  fields: [],
};

export default profile;

Each schema file must contain a name, title, and type property. Here's a brief breakdown of the function of each property:

  • The name key is the property that is used to reference a schema in the query language. The value must be a unique value to avoid conflating schemas.
  • title defines what the schema type is called in the Studio UI.
  • type defines what schema type you're working with. The document value will tell the studio that it should make it possible to make new documents.
  • The icon is an optional property you can add alongside the title. To use the icon, install the react-icons library with the command npm install -D react-icons
  • The fields array, is where the individual input fields will be defined. Here are the fields for the profile schema:
fields: [
    defineField({
      name: "fullName",
      title: "Full Name",
      type: "string",
      validation: (rule) => rule.required(),
    }),
    defineField({
      name: "headline",
      title: "Headline",
      type: "string",
      description: "In one short sentence, what do you do?",
      validation: (Rule) => Rule.required().min(40).max(50),
    }),
    {
      name: "profileImage",
      title: "Profile Image",
      type: "image",
      description: "Upload a profile picture",
      options: { hotspot: true },
      fields: [
        {
          name: "alt",
          title: "Alt",
          type: "string",
        },
      ],
    },
    {
      name: "shortBio",
      title: "Short Bio",
      type: "text",
      rows: 4,
    },
    {
      name: "email",
      title: "Email Address",
      type: "string",
    },
    {
      name: "location",
      title: "Location",
      type: "string",
    },
    {
      name: "fullBio",
      title: "Full Bio",
      type: "array",
      of: [{ type: "block" }],
    },
    {
      name: "resumeURL",
      title: "Upload Resume",
      type: "file",
    },
    {
      name: "socialLinks",
      title: "Social Links",
      type: "object",
      description: "Add your social media links:",
      fields: [
        {
          name: "github",
          title: "Github URL",
          type: "url",
          initialValue: "https://github.com/",
        },
        {
          name: "linkedin",
          title: "Linkedin URL",
          type: "url",
          initialValue: "https://linkedin.com/in/",
        },
        {
          name: "twitter",
          title: "Twitter URL",
          type: "url",
          initialValue: "https://twitter.com/",
        },
        {
          name: "twitch",
          title: "Twitch URL",
          type: "url",
          initialValue: "https://twitch.com/",
        },
      ],
      options: {
        collapsed: false,
        collapsible: true,
        columns: 2,
      },
    },
    {
      name: "skills",
      title: "Skills",
      type: "array",
      description: "Add a list of skills",
      of: [{ type: "string" }],
    },
 ],


To understand how fields work, visualize each field object as a HTML <input> that will be available in the studio. The value in each input will be exported to a JSON object you can use to inject your data. You can add as many fields, but each must contain a nametitle, and type property.

The defineField() helper function helps enable auto-completion of field types in your schema file.

Sanity comes with its own built-in schema types: number, datetimeimage, array, object, string, url, and more. You can check out the full list of schema types here.

To expose this newly created schema file to the Studio, you need to import it into the schemas array inside the schemas/index.ts file:

// schemas/index.ts

import profile from "./profile";

export const schemaTypes = [profile];

Now you can start working with it from within the studio. Visit your studio at localhost:3000/studio or whatever path you used to mount it. Then click on the Profile tab and select the edit button on the top corner to start editing the fields.

Step 5: Query Data using GROQ

GROQ (Graph-Relational Object Queries) is Sanity's query language designed to query collections of largely schema-less JSON documents. The idea behind the query language is to be able to describe exactly what information you need from your schema, or filter certain data, and return only specific elements from your data

To start using GROQ, first create a sanity/sanity.client.ts file in your project root directory.

mkdir sanity && touch sanity/sanity.client.ts

Paste the code into this file:

// sanity/sanity.client.ts

import { createClient, type ClientConfig } from "@sanity/client";

const config: ClientConfig = {
  projectId: "ga8lllhf",
  dataset: "production",
  apiVersion: "2023-07-16",
  useCdn: false,
};

const client = createClient(config);

export default client;
  • apiVersion: The version of the Sanity API you're using. For the latest API version, use your current date in this format YYYY-MM-DD.
  • useCdn is used to disable edge cases

What this file does is provide a few configurations that will be defined in each query so this is just to avoid repeating it every time. Now for the main query, create a sanity/sanity.query.ts file.

touch sanity/sanity.query.ts

Note: There is not clear-cut way to arrange or name these files so feel free to change it up as needed.

Here's the basic query for the profile schema:

// sanity/sanity.query.ts

import { groq } from "next-sanity";
import client from "./sanity.client";

export async function getProfile() {
  return client.fetch(
    groq`*[_type == "profile"]{
      _id,
      fullName,
      headline,
      profileImage {alt, "image": asset->url},
      shortBio,
      location,
      fullBio,
      email,
      "resumeURL": resumeURL.asset->url,
      socialLinks,
      skills
    }`
  );
}

If you read this far, thank the author to show them you care: write comment below

This website uses cookies to enhance your browsing experience, analyze site traffic, and serve better user experiences. By continuing to use this site, you consent to our use of cookies. Learn more in our cookie policy

Explore the latest insights, articles,free components, and expert advice on programming and software development

© Copyright 2024 MED DAKIR. All rights reserved./privacy policy