#How can I use Clerk with Convex on NextJS13 app routes?
59 messages · Page 1 of 1 (latest)
@silent mist Where does it say that router is not ready?
On the browser, let me post here all the code I used, one second!
Here's our guide to using the App router, is this what you're using? https://docs.convex.dev/quickstart/nextjs
Clerk's integration with App router is a little different, I could understand these being confusing to combine.
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>
);
}
Context
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>
);
}
Layout
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...
BTW, I LOVED CONVEX, for now on, its my favorite serveless backend 🙂
Nice, so, will be good to but this on documentation 🙂
but wanting to use clerk-nextjs makes sense, I'll look into this
Yeah, because I don't know what feature we will gonna miss
but, another question, now, with this implementation, what change on the flow?
We installing that library in step 6 of https://docs.convex.dev/auth/clerk but we can call it out specifically there
owww, I saw now
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);
}
});
But I agree this deserves specifically mentioning, Next.js is common enough that we can have a guide specifically for Next.js + Clerk someday
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,
@silent mist have you seen the example for this?
or on delete, I will remove
No, I made using the webhook clerk documentation, I don't know
Nice, well we can compare https://github.com/thomasballinger/convex-clerk-users-table/blob/main/convex/http.ts#L17
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
nice, its the same logic, but instead a profile, is the entire user
yeah
i used ngrok
for my localhost receive the apis
This looks reasonable though! You're not validating the webhook request yet
Yeah, its one of the things I need to implement
and you should add some authentication to the user profile update mutation, so that it's protected from public use
good insight, thanks!
I think your approach looks good? although using a Convex HTTP endpoint might be more convenient
I will be open a lot more support,
prob, I din't had this repo before, now I will look again
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...
Sounds good, let us know how it goes!
🙂
@silent mist there are helpful posts and code examples for dealing with document relationships on Stack:
https://stack.convex.dev/relationship-structures-let-s-talk-about-schemas
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
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?
I think will, I just need first understand what the integration with clerk makes, and then take the decision
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?
Yup!