| 2024-01-26

Next.js 14 App with Clerk and Supabase

    This ariticle describes the process of building a Next.js 14 application with Clerk and Supabase. This app handles user authentication with Clerk and manages data with Supabase.

    Demo:

    My Example app, Depoloyed at Vercel.

    My GitHub Repo


    Official Documentation:

    Clerk and Next.js Quickstart. Clerk Docs

    Clerk and Supabase. Supabase Docs

    Deploy to production. Clerk Docs


    Step 1: Initializing the Project

    1. Create Next.js 14 App:
      npx create-next-app@latest my-app
      cd my-app
      
      sh
    2. Install Dependencies:
      npm install @clerk/nextjs @supabase/supabase-js
      
      sh

    Step 2: Environment Setup

    Set up the environment for your Next.js project by integrating Clerk and Supabase. This involves obtaining the necessary API keys from both Clerk and Supabase and configuring them in your project.

    1. Obtaining Clerk API Keys:

      • Go to the Clerk Dashboard.
      • Sign up or log in to your Clerk account.
      • Create a new Application or select an existing one.
      • Navigate to the 'API Keys' section in your Clerk dashboard.
    2. Obtaining Supabase API Keys:

      • Visit the Supabase Dashboard.

      • Sign in or create a new Supabase account.

      • Create a new project or select an existing one.

      • Once your project dashboard is open, go to the 'Settings' > 'API' section.

      • You will find two keys here: the anon public key and the service_role secret key. For most client-side operations, the anon key is what you'll use.

      • You will also need Supabase's jwt secret key. To obtain this key, go to the 'Settings' > 'API'. You will find the jwt secret key here under the Config section.

      In your Clerk application, use the lefthand menu to navigate to the JWT Templates page. Click on the button to create a new template based on Supabase. Name your template (I've named mine supabase, as in the example shown here in Supabase's Clerk Integration Docs) and click on the Create Template button. Enter your Supabase jwt secret key in the Signing Key input and apply changes. Once you have done this, you should be able to generate a token for your Supabase client.

      For more information check out the Supabase's Clerk Integration Docs.

    3. .env.local Configuration: After obtaining your API keys, the next step is to securely add them to your Next.js application using an environment file.

      • Create a file named .env.local in the root of your Next.js project (if it doesn't already exist).

    add api keys to .env.local as follows:

    NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=your-clerk-publishable-key
    CLERK_SECRET_KEY=your-clerk-secret-key
    
    NEXT_PUBLIC_SUPABASE_URL=your-supabase-url
    NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anonkey
    
    # add these values for the Clerk URLs:
    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=/
    
    text

    This environment setup ensures that your application can communicate securely with Clerk and Supabase services. Remember, these keys should be kept secure and not exposed in client-side code beyond what is necessary for the functionalities provided by Clerk and Supabase.

    Step 3: Clerk Integration

    Integrating Clerk into your Next.js application involves setting up the ClerkProvider in your main layout, creating a navigation bar with authentication controls, and setting up dedicated pages for sign-in and sign-up. Here's how to go about it:

    1. Clerk Setup in layout.tsx: The ClerkProvider component is used to wrap your application's root layout. This ensures that Clerk's authentication functionalities are accessible throughout your app.

      • Open or create a layout.tsx file in your project.

      • Import ClerkProvider from @clerk/nextjs.

      • You'll also need to import the useRouter hook from next/router.

      • Wrap your layout component's content with ClerkProvider, passing in the Clerk Frontend API key from your environment variables.

      • Example implementation:

        import type { Metadata } from "next";
        import { Inter } from "next/font/google";
        import "./globals.css";
        import { ClerkProvider } from "@clerk/nextjs";
        import NavBar from "@/components/nav/nav-bar";
        const inter = Inter({ subsets: ["latin"] });
        export const metadata: Metadata = {
          title: "Create Next App",
          description: "Generated by create next app",
        };
        export default function RootLayout({
          children,
        }: Readonly<{
          children: React.ReactNode;
        }>) {
          return (
            <ClerkProvider>
              <html lang="en">
                <body className={inter.className}>
                  <div className="flex flex-col gap-8 px-20 py-12">
                    <NavBar />
                    {children}{" "}
                  </div>
                </body>
              </html>
            </ClerkProvider>
          );
        }
        
        tsx
    2. NavBar Component:

      The NavBar component serves as the main navigation header for your application, providing links and authentication controls available globally.

      • I recommend using Clerk's UserButton component to handle authentication controls like sign-out and account management. Here I have added the UserButton to an AuthButton component that also handles the case where the user is not authenticated and offers a link to the sign-in page. If the user is logged in the UserButton will be displayed.

        import React from "react";
        import { UserButton } from "@clerk/nextjs";
        import Link from "next/link";
        import { auth } from "@clerk/nextjs/server";
        
        function AuthButton() {
          const { userId } = auth();
          // console.log("user id:", userId);
          return (
            <div>
              {userId ? (
                <div className="border-2 rounded-full w-[35px] h-[35px]">
                  <UserButton afterSignOutUrl="/" />
                </div>
              ) : (
                <Link
                  href="/sign-in"
                  className="border-2 rounded-lg px-4 py-2 font-bold"
                >
                  Log In
                </Link>
              )}
            </div>
          );
        }
        
        export default AuthButton;
        
        tsx
    3. Creating Authentication Pages:

      Dedicated pages for sign-in and sign-up provide a focused area for users to manage their authentication state.

      • Create two new pages in your Next.js application, typically named sign-in.tsx and sign-up.tsx.

      • Use Clerk's provided components such as SignIn and SignUp to render the respective authentication forms.

      • Example for sign-in.tsx:

        import { SignIn } from "@clerk/nextjs";
        
        const SignInPage = () => {
          return (
            <div>
              <SignIn />
              {/* Additional content or styling as needed */}
            </div>
          );
        };
        
        export default SignInPage;
        
        tsx
      • Similarly, set up the sign-up.tsx page with the SignUp component.

    By following these steps, you'll have integrated Clerk into your Next.js application, providing a seamless authentication experience for your users. Remember to style and customize your NavBar and authentication pages to match the look and feel of your application.

    Step 4: Supabase Integration

    Integrating Supabase into your Next.js application involves initializing the Supabase client and configuring row-level security to manage data access. Here’s a guide on how to set this up:

    1. Initializing Supabase Client: To interact with Supabase, you need to initialize the Supabase client. This is done by creating a standard client for general operations and a Clerk-authenticated client for operations that require user authentication.

    Standard Supabase Client:

    • Create a file named lib/supabase.ts.

    • Import the createClient method from @supabase/supabase-js.

    • Initialize the Supabase client using the Supabase URL and ANON key from your environment variables.

    • Example:

      import { createClient } from "@supabase/supabase-js";
      
      const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
      const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
      
      export const supabase = createClient(supabaseUrl, supabaseAnonKey);
      
      typescript

    Clerk-Authenticated Supabase Client:

    Server-Side Supabase Client Setup

    Server-side setup is crucial for operations that should be securely executed on the server, like direct database manipulations.

    1. Import Clerk's Server-Side Auth Module:

      import { auth } from "@clerk/nextjs/server";
      
      javascript
    2. Retrieve the Authentication Token:

      const { getToken } = auth();
      const token = await getToken({ template: "supabase" });
      
      javascript
    3. Create the Supabase Client:

      import { createClient } from "@supabase/supabase-js";
      
      const supabaseServerClient = createClient(
        process.env.NEXT_PUBLIC_SUPABASE_URL,
        process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
        {
          global: {
            headers: {
              Authorization: `Bearer ${token}`,
            },
          },
        }
      );
      
      javascript
    4. Usage:

      • Use supabaseServerClient for server-side database operations.

    Client-Side Supabase Client Setup

    The client-side setup is used for operations that require real-time user interaction, like form submissions.

    1. Import Clerk's Client-Side Auth Hook:

      import { useAuth } from "@clerk/clerk-react";
      
      javascript
    2. Initialize the Supabase Client Inside a React Component:

      import { useState, useEffect } from "react";
      import { createClient } from "@supabase/supabase-js";
      
      function MyComponent() {
        const [supabaseClient, setSupabaseClient] = useState(null);
        const { getToken } = useAuth();
      
        useEffect(() => {
          const initializeSupabase = async () => {
            const token = await getToken({ template: "supabase" });
            const client = createClient(
              process.env.NEXT_PUBLIC_SUPABASE_URL,
              process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
              {
                global: {
                  headers: {
                    Authorization: `Bearer ${token}`,
                  },
                },
              }
            );
            setSupabaseClient(client);
          };
      
          initializeSupabase();
        }, []);
      
        // ... rest of your component
      }
      
      javascript
    3. Usage:

      • Use supabaseClient within your React components for client-side interactions.
    4. Row-Level Security in Supabase: Row-level security (RLS) is a feature that allows you to control access to rows in a database table based on user attributes or other access policies. This is crucial for protecting sensitive data and ensuring that users can only access data they are permitted to see.

      • Setting Up RLS:

        • In your Supabase dashboard, go to the table you want to apply RLS to.
        • Enable Row-Level Security for the table.
        • Create policies that define the conditions under which rows in this table can be accessed, updated, or deleted. For example, you can create a policy that allows users to only read rows where the user ID column matches their own user ID.
        • Test these policies thoroughly to ensure they work as expected and do not inadvertently expose sensitive data.
      • Integrating RLS with Clerk:

        • When using Clerk for authentication, ensure that the user's unique ID from Clerk is stored in your Supabase tables where needed (e.g., as a column in each row).
        • Your RLS policies can then reference this ID to control access based on the authenticated user's ID.

    By following these steps, you will successfully integrate Supabase into your Next.js application, complete with a standard client for general database operations and a Clerk-authenticated client for user-specific data access. Additionally, setting up row-level security ensures that your application's data access is secure and compliant with your access policies.


    Thanks for reading. If you enjoyed this post, I invite you to explore more of my site. I write about web development, programming, and other fun stuff.