DEV Community

Julian Bustos
Julian Bustos

Posted on

How to add user auth with Clerk to Nextjs (App Directory) and store it in Embedded Sanity CMS

How to add user auth with Clerk to Nextjs (App Directory) and store it in Sanity CMS

First we are going to install a new Nextjs project but this should work with any Nextjs App Directory project, then we will install Clerk and get it working, once it’s working we will install and embed a Sanity studio and get it ready to receive the information provided by Clerk. We will finish by creating an API route, where we will send the user data to Sanity, and verify that the user was created.

  • Install Nextjs in an empty folder.
npx create-next-app@latest .
Enter fullscreen mode Exit fullscreen mode
  • Proceed with you favourite settings I will choose Tailwind, Typescript, and the App Directory.

For the purpose of this tutorial I want to start with an empty home page in app/page.tsx.

// app/page.tsx
const Home = () => {
  return (
    <main className="grid place-content-center min-h-screen">
      Hello World
    </main>
  )
}

export default Home;
Enter fullscreen mode Exit fullscreen mode
  • Run the project and verify everything is working
npm run dev
Enter fullscreen mode Exit fullscreen mode
  • Install Clerk and dependencies.
npm install @clerk/nextjs
Enter fullscreen mode Exit fullscreen mode
  • Go to www.clerk.com and sign-up for a free account.
  • Once inside click on Add Application and give it a name
  • Select your favorite sign-in providers. I will choose email and Google.

Image description

  • Copy the API Keys and paste them into your .env.local file.
  • Mount ClerkProvider Update your root layout to include the wrapper. The component wraps your Next.js application to provide active session and user context to Clerk's hooks and other components. It is recommended that the wraps the to enable the context to be accessible anywhere within the app.
  • Your app/layout.tsx should look something like this:
// app/layout.tsx
import './globals.css'
import { Inter } from 'next/font/google'
import { ClerkProvider } from '@clerk/nextjs'

const inter = Inter({ subsets: ['latin'] })

export const metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <ClerkProvider>
      <html lang="en">
        <body className={inter.className}>{children}</body>
      </html>
    </ClerkProvider>
  )

Enter fullscreen mode Exit fullscreen mode
  • Protect your Application

    Now that Clerk is installed and mounted in your application, it’s time to decide which pages are public and which need to hide behind authentication. We do this by creating a middleware.ts file at the root folder (or inside src/ if that is how you set up your app).

    In my case I will create the middleware.ts file inside the root folder of my project. I will also add a Public Routes array, so any route you would like to be public you will need to add to this array.

    We will add the studio route, and let sanity handle the auth for the CMS studio.

// middlewate.ts
import { authMiddleware } from "@clerk/nextjs";

// This example protects all routes including api/trpc routes
// Please edit this to allow other routes to be public as needed.
// See https://clerk.com/docs/nextjs/middleware for more information about configuring your middleware

const publicRoutes = ["/", "/studio"];

export default authMiddleware({
  publicRoutes,
});

export const config = {
  matcher: ["/((?!.*\\..*|_next).*)", "/", "/(api|trpc)(.*)"],
};
Enter fullscreen mode Exit fullscreen mode
  • Create the sign-up page.

    In the app directory create a sign-up folder and then another catch-all [[…sign-up]] folder with a page inside of it. Your route should look like this: app/sign-up/[[…sign-up]]/page.tsx

    Inside the page.tsx file we will add the provided SignUp component from Clerk.

    // app/sign-up/[[…sign-up]]/page.tsx
    
    import { SignUp } from "@clerk/nextjs";
    
    export default function Page() {
      return (
        <main className="grid place-content-center min-h-screen">
          <SignUp/>
        </main>
      );
    }
    
  • Do the same for the Sign in page. But we will also add the redirectUrl parameter provided by nextjs so that the user can be redirected back to the page they were visiting before or home if there isn’t any.

// app/sign-up/[[…sign-in]]/page.tsx

import { SignIn } from "@clerk/nextjs";

export default function Page({searchParams}: {searchParams: {redirectUrl: string | undefined}}) {
  const {redirectUrl} = searchParams;
  return (
    <main className="grid place-content-center min-h-screen">
      <SignIn redirectUrl={redirectUrl || "/"}/>
    </main>
  );
}
Enter fullscreen mode Exit fullscreen mode
  • Next we will add some more environment variables to control the behavior of clerk and also to tell it what route the sign-up and sign-in pages are in, we do this in the .env.local file in the root right after our API keys.
// .env.local

NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/
Enter fullscreen mode Exit fullscreen mode
  • Create a link to the sign up page inside the homepage. In app/page.tsx lets add the link
  • Run the app, navigate to http://localhost:3000 using the browser and click on sign-up, sign-up for your app.

    // app/page.tsx
    import Link from "next/link";
    
    const Home = () => {
      return (
        <main className="grid place-content-center min-h-screen">
          <Link href="/sign-up">Sign Up</Link>
        </main>
      );
    };
    
    export default Home;
    

Image description

  • Go to dashboard.clerk.com select your project and verify your new user.
  • In the homepage let’s add some user data to the home page and some conditional rendering to see the data if the user is logged in.
// app/page.tsx

import { UserButton, currentUser } from "@clerk/nextjs";
import Link from "next/link";
export default async function Home() {
  const user = await currentUser();

  if (user) {
    return (
      <main className="grid place-content-center gap-2 min-h-screen">
        <div className="flex items-center gap-2">
          <UserButton afterSignOutUrl="/" />
          <h2>Welcome back {user.firstName}!</h2>
        </div>
      </main>
    );
  }
  return (
    <main className="grid place-content-center min-h-screen">
      <Link href="/sign-up">Sign Up</Link>
    </main>
  );
}
Enter fullscreen mode Exit fullscreen mode
  • Let’s visit our homepage:

Image description

  • If we click on the user image we have access to some options, for now I will try to sign out to see the other view.

Image description

  • You should see your sign-up button now:

Image description

  • We can also add a signout button provided by clerk to make it easier to sign out and clearer that its a sign up / sign in page as well:
//// app/page.tsx
import { currentUser, UserButton, SignOutButton } from "@clerk/nextjs";
import Link from "next/link";
export default async function Home() {
  const user = await currentUser();

  if (user) {
    return (
      <main className="grid place-content-center gap-2 min-h-screen">
        <div className="flex items-center gap-2">
          <UserButton afterSignOutUrl="/" />
          <h2>Welcome back {user.firstName}!</h2>
          <SignOutButton />
        </div>
      </main>
    );
  }
  return (
    <main className="grid place-content-center min-h-screen">
      <Link href="/sign-up">Sign Up / Sign In</Link>
    </main>
  );
}
Enter fullscreen mode Exit fullscreen mode
  • Now that we have sign up and sign out flows setup let’s create and embed a new sanity CMS in our Nextjs project, this process will still work even if the CMS isn’t embedded however I will embed a new project for the purpose of this tutorial.
  • Install the necessary packages for Sanity to work as well as the client packages to make the requests to the CMS.
npm install sanity next-sanity next-sanity-image @portabletext/react @sanity/client @sanity/image-url
Enter fullscreen mode Exit fullscreen mode
  • Go to sanity.io sign up for an account. Sanity gives instructions on how to create the studio however since we are embedding the studio on a Nextjs project we can just ignore them and navigate to https://www.[sanity.io/manage](http://sanity.io/manage). If Sanity created a project for you click on it and copy the project ID, if they didn’t you can click on Create a new project on the top and then copy the project ID provided.
  • Go to your .env.local and add the project id under the Clerk variables like this:
/// .env.local
SANITY_PROJECT_ID=(your sanity project id)
Enter fullscreen mode Exit fullscreen mode
  • Create a sanity folder on the root of your project, and a config.ts file with the following content:
// sanity/config.ts
export const sanityConfig = {
  projectId: process.env.SANITY_PROJECT_ID || "",
  dataset: 'production',
  useCdn: process.env.NODE_ENV === 'production',
  apiVersion: '2021-03-25',
}
Enter fullscreen mode Exit fullscreen mode
  • Create your studioConfig.ts file inside the sanity folder in it for now we will define the config and the basePath for the sanity studio.
// sanity/studioConfig.ts
import { sanityConfig } from './config';
import { defineConfig } from "sanity";

export const sanityAdminConfig = {
  ...sanityConfig,
  title: "Clerk Users and Sanity Test",
  basePath: "/admin",
};

export const studioConfig = defineConfig(sanityAdminConfig);
Enter fullscreen mode Exit fullscreen mode
  • Inside App directory create a page studio route and an index catch all filder, where we will mount the studio. app/studio/[[…index]]/page.tsx We will import the studio config we just created and give it as a prop to the NextStudio component provided by Sanity, this is Client component so we will have to add the use client directive at the top of the file.
// app/studio/[[…index]]/page.tsx
'use client'

import { studioConfig } from '@/sanity/studioConfig'
import {NextStudio} from 'next-sanity/studio'

export default function StudioPage() {
  //  Supports the same props as `import {Studio} from 'sanity'`, `config` is required
  return <NextStudio config={studioConfig} />
}
Enter fullscreen mode Exit fullscreen mode
  • Lets restart our project by running npm run dev and navigate to http://localhost:3000/studio
  • We should get this error:

Image description

This is because we need to allow the route in the Sanity studio for it to be able to read and write to sanity’s database. We can add CORS origins in the Sanity Management Dashboard for the project.

  • Go to https://www.sanity.io/manage open the project we created previously and navigate to the API tab and look for the + Add CORS origin button

Image description

  • Add: http://localhost:3000, and click Allow Credentials since we are embedding the studio in our NextJS project we need to allow it to have credentials. When you deploy your site you will need to add the root address of your site ie: [http://www.yoursite.com](http://www.yoursite.com) to the list of allowed sites.

Image description

  • Click Save.
  • We can also delete localhost:3333 since this is the location of the default sanity studio which we are not using.

Image description

Image description

Now we have an empty studio embedded in the Nextjs site! Let’s add a users document list to our sanity.

  • Create the schema for the users, for our tutorial we will just need first name, last name, and email.
  • Create a schemas folder inside the sanity folder and create user.ts sanity/schemas/user.ts add the document for users, we will use an icon from sanity so that the users’ list gets a nice icon.
// sanity/schemas/user.ts

import { defineType, defineField } from "sanity";
import { UserIcon } from "@sanity/icons";

export const userSchema = defineType({
  name: "user",
  title: "Users",
  type: "document",
  icon: UserIcon,
  fields: [
    defineField({
      name: "firstName",
      title: "First Name",
      type: "string",
    }),
    defineField({
      name: "lastName",
      title: "Last Name",
      type: "string",
    }),
    defineField({
      name: "email",
      title: "Email",
      type: "string",
    })
  ],
});
Enter fullscreen mode Exit fullscreen mode

Now lets add the schemas to our studioConfig. I like to keep my schemas organized in a separate file so that it’s easy to add or remove schemas from my project.

  • Create in the sanity folder a schemas.ts file. sanity/schema.ts and add the following:
// sanity/schema.ts

import {userSchema} from "./schemas/user";
export const schemaTypes = [
  userSchema
]
Enter fullscreen mode Exit fullscreen mode
  • Import the schemas into the sanity/studioConfig.ts file and add it to the config.
// sanity/studioConfig.ts

import { sanityConfig } from './config';
import { defineConfig } from "sanity";
import { schemaTypes } from "./schemas";

export const sanityAdminConfig = {
  ...sanityConfig,
  title: "Clerk Users and Sanity Test",
  basePath: "/studio",
  schema: {
    types: schemaTypes,
  },
};

export const studioConfig = defineConfig(sanityAdminConfig);
Enter fullscreen mode Exit fullscreen mode

We are still getting the same screen, that’s because I forgot to add the desktool to the config, let’s do that.

// sanity/studioConfig.ts 

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

export const sanityAdminConfig = {
  ...sanityConfig,
  title: "Clerk Users and Sanity Test",
  basePath: "/studio",
  plugins: [
    deskTool(),
  ],
  schema: {
    types: schemaTypes,
  },
};

export const studioConfig = defineConfig(sanityAdminConfig);
Enter fullscreen mode Exit fullscreen mode
  • Refresh, and we should be able to see the users list and we can add users to our list,

Image description

However, we want them to be added when the user signs in with our site. To do that we will use Clerks environment variables to redirect the user after sign-up to an API route, where we can get the user’s information add save it in our sanity and redirect the user again either to where they where coming from or to the homepage.

Let’s start by creating the API route first.

  • Create a folder create-sanity-user (you can name this something relevant to your app) inside the app folder and create a file route.tsx. This is a special Nextjs file that let’s us create api end points. app/create-sanity-user/route.ts, in it we will use clerk’s currentUser server function to access the user information. We also want to prevent anyone from accessing the route, so if the user doesn’t exist then we can redirect them to the sign-in page, however since we are using the middleware.ts file we won’t need to add that to our api route. Let’s try to return the user’s data first.
// app/create-sanity-user/route.ts

import { currentUser } from "@clerk/nextjs";
import { NextResponse } from "next/server";

export const GET = async () => {
  const user = await currentUser();
  return NextResponse.json({ user });
};
Enter fullscreen mode Exit fullscreen mode
  • Navigate to http://localhost:3000/create-sanity-user we should now see the data that clerk gives you access too, we can save any of it in our sanity.
  • Navigate to [http://localhost:3000/](http://localhost:3000/) and log out and navigate back to http://localhost:3000/create-sanity-user` and we should now get redirected to the sign-in page! Great!
  • Let’s log back in and we should get redirected back to create-sanity-user end point.
  • Let’s now add the user data to sanity, we need to create a sanityClient file which will allow us to have access to sanity’s functionality, and be able to read and write data from our nextjs app.
  • We will also need an API token from sanity to be able to write data onto it. Go back to https://www.[sanity.io/manage](http://sanity.io/manage) and in the API tab click on + Add API token

Image description

  • Give it a name and select the Editor option to have access to read and write token options. Copy your token, and add it to your .env.local file.

jsx
SANITY_API_TOKEN=(your api token)

  • Create in the sanity folder a sanityClient.ts file and import the createClient function from next-sanity, and your own config, and just add the token from the environment variables, or empty if it doesn’t exist.

`jsx
// sanity/sanityClient.ts

import { createClient } from "next-sanity";
import { sanityConfig } from "./config";
export default createClient({
...sanityConfig,
token: process.env.SANITY_API_TOKEN || "",
});
`

  • Go to the app/create-sanity-user/route.ts file and import the sanityClient we just created, we will use the createIfNotExists (which prevents errors when a user already exists) function that the next-sanity package provides to create a document in our sanity.
  • We are also going to get the base path URL to redirect to from the request, this will work for both development and deployed, since the NextResponse.redirect function expects a full url. And redirect the user to this base URL.
  • Even though the middleware is taking care of authentication, we still want to prevent typescript errors in case the user doesn’t exist.

`jsx
// app/create-sanity-user/route.ts

import { NextApiRequest } from "next";
import sanityClient from "@/sanity/sanityClient";
import { currentUser } from "@clerk/nextjs";
import { NextResponse } from "next/server";

export const GET = async (req: NextApiRequest) => {
const user = await currentUser();
if (!user) {
return NextResponse.redirect("/sign-in");
}

const {id, firstName, lastName, emailAddresses} = user;
Enter fullscreen mode Exit fullscreen mode

await sanityClient.createIfNotExists({
_type: "user",
_id: id,
firstName,
lastName,
email: emailAddresses[0].emailAddress
})

const url = req.url?.split("/create-sanity-user")[0] || "/"
return NextResponse.redirect(url);
};
`

  • Restart your server and navigate to http://localhost:3000/create-sanity-user then navigate to your studio at `http://localhost:3000/studio` and see the new user has been added to your studio
  • The last step will be to update the NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL environment variable and point it to /create-sanity-user route to our environment variables so Clerk can have the right behavior after signing-up.

jsx
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/create-sanity-user

  • Restart your server since we updated the environment variables and try to signup with a different user, then check your studio and verify the new user was added.

That’s it, now you can add your own specific details to your users, or relate it to other documents specific for your use case.

Top comments (6)

Collapse
 
pak11273 profile image
Isaac Pak

Thanks for a great tutorial. Just wanted to mention that you can sign in with a google account without having to sign up and this user doesn't get put into sanity.

Collapse
 
kennedynvsf profile image
Kennedy NvsF

For some reason i am only storing the client in sanity by manually entering the api path into the search bar and pressing Enter.
I have changed my *NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL * variable to the path i set.

Note: I am using the api folder structure instead of the one you used in the example, i wonder if maybe that is causing the error.

do you have any pointers regarding this?
Great article by the way.

Collapse
 
julimancan profile image
Julian Bustos

For some reason dev.to stopped parsing correctly code blocks towards the end of the article I hope this doesn't stop you from getting the whole concept. You can read the original article here with correct markdown formatting: julian-bustos.notion.site/How-to-a...

Collapse
 
hnrq profile image
Henrique Ramos • Edited

Hey, Julian, welcome to the Dev.to community! About formatting, maybe this is related to an extra backtick somewhere in your text. I would guess it's the backtick on doesn't, at

Create in the sanity folder a sanityClient.ts file and import the createClient function from next-sanity, and your own config, and just add the token from the environment variables, or empty if it doesn’t exist.
Enter fullscreen mode Exit fullscreen mode

Or it could also be a missing backtick at the

SANITY_API_TOKEN=(your api token)
Enter fullscreen mode Exit fullscreen mode

code block.

Nice article, by the way!

Collapse
 
julimancan profile image
Julian Bustos

Hey Henrique thanks for appreciating it, unless ’ count as it shouldn't affect it, I did check several times around thatSANITY_API_TOKEN` and it has the 3 ticks before and 3 after, but it seems to register the 2 first ticks as an inblock code section and then the rest of it as a separate in block section rather than recognizing it as a code block section with triple ticks... It works all the way to that SANITY_API_TOKEN just fine.... do you think it's too long and I broke dev.to? :D

Thread Thread
 
hnrq profile image
Henrique Ramos

Hmm, that's odd... 🤔
I believe not, there are some pretty long posts around. Maybe there's something related w/ the backticks and we're overlooking it 😅