#How can I use Clerk with Convex on NextJS13 app routes?

59 messages · Page 1 of 1 (latest)

silent mist
#

Any one can help?

I saw the tutorial to use Clerk + Convex on Next13, but is using pages router, I tried, but not works on app router, says router is not ready.

pseudo estuary
#

@silent mist Where does it say that router is not ready?

silent mist
#

On the browser, let me post here all the code I used, one second!

pseudo estuary
#

Clerk's integration with App router is a little different, I could understand these being confusing to combine.

silent mist
#

here:

'use client';
import { ReactNode } from 'react';
import { ConvexProviderWithClerk } from 'convex/react-clerk';
import { ConvexReactClient } from 'convex/react';
import { ClerkProvider, useAuth } from '@clerk/nextjs';

const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);

export default function ConvexClientProvider({ children }: { children: ReactNode }) {
    return (
        <ClerkProvider>
            <ConvexProviderWithClerk client={convex} useAuth={useAuth}>
                {children}
            </ConvexProviderWithClerk>
        </ClerkProvider>
    );
}
silent mist
#
import './globals.css';
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import ConvexClientProvider from '../context/ConvexClientProvider';

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

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

export default function RootLayout({ children }: { children: React.ReactNode }) {
    return (
        <html lang='en'>
            <body className={inter.className}>
                <ConvexClientProvider>{children}</ConvexClientProvider>
            </body>
        </html>
    );
}
silent mist
#

Error

#
- error Error: NextRouter was not mounted. https://nextjs.org/docs/messages/next-router-not-mounted
null```
#

But! If instead use clerk nextjs, use clerk react, works...

#
import { ReactNode } from 'react';
import { ConvexProviderWithClerk } from 'convex/react-clerk';
import { ConvexReactClient } from 'convex/react';
import { ClerkProvider, useAuth } from '@clerk/clerk-react';

const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);

export default function ConvexClientProvider({ children }: { children: ReactNode }) {
    return (
        <ClerkProvider publishableKey={process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY!}>
            <ConvexProviderWithClerk client={convex} useAuth={useAuth}>
                {children}
            </ConvexProviderWithClerk>
        </ClerkProvider>
    );
}
#

This works fine...

pseudo estuary
#

💯 thanks for the repro!

#

Using clerk-react is the approach we've tested

silent mist
#

BTW, I LOVED CONVEX, for now on, its my favorite serveless backend 🙂

silent mist
pseudo estuary
#

but wanting to use clerk-nextjs makes sense, I'll look into this

silent mist
#

Yeah, because I don't know what feature we will gonna miss

#

but, another question, now, with this implementation, what change on the flow?

pseudo estuary
silent mist
#

So, just tell me if I made the correct implementation here (its another thing)

#

Like, I put a funciton to every new user create on clerk, create a profile on convex

#

So, the profile quere/mutations:

import { v } from 'convex/values';

export const getAll = query({
    args: {},
    handler: async (ctx) => {
        return await ctx.db.query('profile').collect();
    }
});

export const getByUserId = query({
    args: {
        userId: v.string()
    },
    handler: async (ctx, { userId }) => {
        return await ctx.db
            .query('profile')
            .withIndex('byUserId', (q) => q.eq('userId', userId))
            .first();
    }
});

export const create = mutation({
    args: {
        userId: v.string(),
        name: v.string(),
        email: v.string(),
        avatarUrl: v.optional(v.string())
    },
    handler: async (ctx, { userId, name, email, avatarUrl }) => {
        return await ctx.db.insert('profile', {
            userId,
            name,
            email,
            avatarUrl
        });
    }
});

export const update = mutation({
    args: {
        userId: v.string(),
        name: v.string(),
        email: v.string(),
        avatarUrl: v.optional(v.string())
    },
    handler: async (ctx, { userId, name, email, avatarUrl }) => {
        const profile = await ctx.db
            .query('profile')
            .withIndex('byUserId', (q) => q.eq('userId', userId))
            .first();

    if (!profile) {
            return await ctx.db.insert('profile', {
                userId,
                name,
                email,
                avatarUrl
            });
        }

        return await ctx.db.patch(profile._id, {
            userId,
            name,
            email,
            avatarUrl
        });
    }
});

export const remove = mutation({
  args: {
    userId: v.string(),
  },
  handler: async (ctx, { userId }) => {
    const profile = await ctx.db
      .query('profile')
      .withIndex('byUserId', (q) => q.eq('userId', userId))
      .first();

    if (!profile) {
      return
    }

    return await ctx.db.delete(profile._id);
  }
});
pseudo estuary
#

But I agree this deserves specifically mentioning, Next.js is common enough that we can have a guide specifically for Next.js + Clerk someday

silent mist
#

and this is the api/route who will receive the webhooks events:

import type { WebhookEvent } from '@clerk/clerk-sdk-node';
import { ConvexHttpClient } from 'convex/browser';
import { api } from '../../../../../convex/_generated/api';

const client = new ConvexHttpClient(process.env.CONVEX_URL!);

export async function POST(req: Request) {
    const body = (await req.json()) as WebhookEvent;
    console.log(body);
    try {
        if (body.type === 'user.created') {
            const name =
                (body.data.first_name ? body.data.first_name : 'No') +
                ' ' +
                (body.data.last_name ? body.data.last_name : 'Name');

            const user = await client.query(api.profile.getByUserId, { userId: body.data.id });
            if (!user) {
                await client.mutation(api.profile.create, {
                    userId: body.data.id,
                    name,
                    email: body.data.email_addresses[0].email_address,
                    avatarUrl: body.data.profile_image_url
                });

                console.log('Profile created');
            }
        }

        if (body.type === 'user.updated') {
            const name =
                (body.data.first_name ? body.data.first_name : 'No') +
                ' ' +
                (body.data.last_name ? body.data.last_name : 'Name');

            console.log(name);

            await client.mutation(api.profile.update, {
                userId: body.data.id,
                name,
                email: body.data.email_addresses[0].email_address,
                avatarUrl: body.data.profile_image_url
            });

            console.log('Profile updated');
        }

        if (body.type === 'user.deleted') {
            const user = await client.query(api.profile.getByUserId, { userId: body.data.id as string });
            if (user) {
                await client.mutation(api.profile.remove, { userId: body.data.id as string });
            } else {
                console.log('Profile deleted not found profile');
            }
        }

        return NextResponse.json({ status: 'ok' });
    } catch (e) {
        console.log(e);
        return NextResponse.json({ status: 'error' });
    }
}
#

Every time the user is created, updated or delete, I will create the profile on the convex,

pseudo estuary
#

@silent mist have you seen the example for this?

silent mist
#

or on delete, I will remove

pseudo estuary
silent mist
pseudo estuary
#

so one difference is that you're doing this in a Next.js API route, while the example does it with a Convex HTTP endpoint

silent mist
#

nice, its the same logic, but instead a profile, is the entire user

#

yeah

#

i used ngrok

#

for my localhost receive the apis

pseudo estuary
#

This looks reasonable though! You're not validating the webhook request yet

silent mist
#

Yeah, its one of the things I need to implement

pseudo estuary
#

and you should add some authentication to the user profile update mutation, so that it's protected from public use

silent mist
#

good insight, thanks!

pseudo estuary
#

I think your approach looks good? although using a Convex HTTP endpoint might be more convenient

silent mist
#

I will be open a lot more support,

silent mist
#

My project will get a lot query relationship

#

So, I will need help with this soon

#

for now, I'm playing with convex, I will try migrate my sanity.io project to here...

pseudo estuary
#

Sounds good, let us know how it goes!

silent mist
#

🙂

near yarrow
silent mist
#

Just to update this topic, the implementation with react clerk instead clerk nextjs will be problematic with app route, because app route relies on clerk nextjs helpers functions

#

I already got some errors

near yarrow
#

As long as you stick to client-side and client components, you should be ok. So basically don't use Next-specific Clerk integration. Would that work?

silent mist
#

I think will, I just need first understand what the integration with clerk makes, and then take the decision

silent mist
#

So, after reading the documentation, the use case is to has a helper context to know if the user are or not logged in and block some query/mut?

near yarrow
#

Yup!