How to add user auth with Clerk to Nextjs (App Directory) and store it in Embedded Sanity CMS
Published: 2023-08-14 18:32:34

## 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.

```jsx
npx create-next-app@latest .
```

- 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`. 

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

export default Home;
```

- Run the project and verify everything is working

```jsx
npm run dev
```

- Install Clerk and dependencies.

```jsx
npm install @clerk/nextjs
```

- Go to [www.clerk.com](http://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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/39qpou663gw449p2ertl.png)

- Copy the API Keys and paste them into your .env.local file.
- Mount ClerkProvider
Update your root layout to include the <ClerkProvider> wrapper. The <ClerkProvider> 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 <ClerkProvider> wraps the <body> to enable the context to be accessible anywhere within the app.
- Your app/layout.tsx should look something like this:

```jsx
// 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>
  )

```

- 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)(.*)"],
};
```

- 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.
    
    ```jsx
    // 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.

```jsx
// 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>
  );
}
```

- 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.

```jsx
// .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=/
```

- 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.
    
    ```jsx
    // 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ecxy1ai1agqgezqjj05m.png)

- Go to [dashboard.clerk.com](http://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.

```jsx
// 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>
  );
}
```

- Let’s visit our homepage:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/taykpof8cq4jhp9ac9cz.png)

- 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qn9hwp09z7zasy5205vx.png)

- You should see your sign-up button now:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f6dtuqabv0oq96k8imr9.png)

- 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:
```jsx
//// 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>
  );
}
```

- 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.

```jsx
npm install sanity next-sanity next-sanity-image @portabletext/react @sanity/client @sanity/image-url
```

- Go to [sanity.io](http://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:

```jsx
/// .env.local
SANITY_PROJECT_ID=(your sanity project id)
```

- Create a sanity folder on the root of your project, and a config.ts file with the following content:

```jsx
// sanity/config.ts
export const sanityConfig = {
  projectId: process.env.SANITY_PROJECT_ID || "",
  dataset: 'production',
  useCdn: process.env.NODE_ENV === 'production',
  apiVersion: '2021-03-25',
}
```

- 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.

```jsx
// 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);
```

- 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.

```jsx
// 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} />
}
```

- Lets restart our project by running `npm run dev` and navigate to `http://localhost:3000/studio`
- We should get this error:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pq9utmlggteommodre8v.png)

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](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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/isp8hciszewyv51achop.png)

- 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/am93gx0lux0qfnskvcyp.png)

- Click Save.
- We can also delete [localhost:3333](http://localhost:3333) since this is the location of the default sanity studio which we are not using.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gwbzvvwtznf058fkalhj.png)

- Go back to [`http://localhost:3000](http://localhost:3000/)/studio` and sign in with your user.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y3li4xafbos4e0khq7j7.png)

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.

```jsx
// 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",
    })
  ],
});
```

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:

```jsx
// sanity/schema.ts

import {userSchema} from "./schemas/user";
export const schemaTypes = [
  userSchema
]
```

- Import the schemas into the `sanity/studioConfig.ts` file and add it to the config.

```jsx
// 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);
```

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

```jsx
// 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);
```

- Refresh, and we should be able to see the users list and we can add users to our list,

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/23w45n3eg12g5btgkkti.png)

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.


```jsx
// 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 });
};
```
- Navigate to [`http://localhost:3000/create-sanity-user`](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`](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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/it9ecqjtdjrs3vxn48vg.png)

- 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;
  
  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`](http://localhost:3000/create-sanity-user) then navigate to your studio at [`http://localhost:3000](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.