Documentation Index
Fetch the complete documentation index at: https://mintlify.com/get-convex/convex-backend/llms.txt
Use this file to discover all available pages before exploring further.
The Next.js module provides helpers for integrating Convex with Next.js features including Server Components, Server Actions, and Route Handlers.
Installation
npm install convex next react
Setup
All exported functions use the NEXT_PUBLIC_CONVEX_URL environment variable by default. The npx convex dev command automatically sets this during local development.
NEXT_PUBLIC_CONVEX_URL=https://small-mouse-123.convex.cloud
Server Components
fetchQuery
Execute a query from a Server Component:
async function fetchQuery<Query extends FunctionReference<"query">>(
query: Query,
args?: Query["_args"],
options?: NextjsOptions
): Promise<FunctionReturnType<Query>>
Usage:
import { fetchQuery } from "convex/nextjs";
import { api } from "@/convex/_generated/api";
export default async function TasksPage() {
const tasks = await fetchQuery(api.tasks.list, { completed: false });
return (
<div>
{tasks.map((task) => (
<div key={task._id}>{task.text}</div>
))}
</div>
);
}
fetchMutation
Execute a mutation from a Server Component or Server Action:
async function fetchMutation<Mutation extends FunctionReference<"mutation">>(
mutation: Mutation,
args?: Mutation["_args"],
options?: NextjsOptions
): Promise<FunctionReturnType<Mutation>>
Usage in a Server Action:
import { fetchMutation } from "convex/nextjs";
import { api } from "@/convex/_generated/api";
export async function createTask(formData: FormData) {
"use server";
const text = formData.get("text") as string;
await fetchMutation(api.tasks.create, { text });
}
fetchAction
Execute an action from a Server Component or Server Action:
async function fetchAction<Action extends FunctionReference<"action">>(
action: Action,
args?: Action["_args"],
options?: NextjsOptions
): Promise<FunctionReturnType<Action>>
Usage:
import { fetchAction } from "convex/nextjs";
import { api } from "@/convex/_generated/api";
export default async function SummaryPage() {
const summary = await fetchAction(api.ai.generateSummary, {
text: "Some long text..."
});
return <div>{summary}</div>;
}
Preloading for Client Components
preloadQuery
Preload query data in a Server Component to pass to a Client Component:
async function preloadQuery<Query extends FunctionReference<"query">>(
query: Query,
args?: Query["_args"],
options?: NextjsOptions
): Promise<Preloaded<Query>>
Server Component:
import { preloadQuery } from "convex/nextjs";
import { api } from "@/convex/_generated/api";
import TaskList from "./TaskList";
export default async function TasksPage() {
const preloadedTasks = await preloadQuery(api.tasks.list, {
completed: false
});
return <TaskList preloadedTasks={preloadedTasks} />;
}
Client Component:
"use client";
import { Preloaded, usePreloadedQuery } from "convex/react";
import { api } from "@/convex/_generated/api";
export default function TaskList({
preloadedTasks
}: {
preloadedTasks: Preloaded<typeof api.tasks.list>;
}) {
const tasks = usePreloadedQuery(preloadedTasks);
return (
<div>
{tasks.map((task) => (
<div key={task._id}>{task.text}</div>
))}
</div>
);
}
usePreloadedQuery
Use preloaded data in a Client Component and subscribe to updates:
import { usePreloadedQuery, Preloaded } from "convex/react";
import { api } from "@/convex/_generated/api";
function TaskList({ preloaded }: {
preloaded: Preloaded<typeof api.tasks.list>
}) {
const tasks = usePreloadedQuery(preloaded);
// tasks will update reactively after initial render
return (
<div>
{tasks.map((task) => <div key={task._id}>{task.text}</div>)}
</div>
);
}
Options
All functions accept a NextjsOptions object:
type NextjsOptions = {
token?: string;
url?: string;
skipConvexDeploymentUrlCheck?: boolean;
};
Parameters:
token - JWT authentication token
url - Convex deployment URL (defaults to process.env.NEXT_PUBLIC_CONVEX_URL)
skipConvexDeploymentUrlCheck - Skip URL validation for self-hosted backends
Example with authentication:
import { auth } from "@/auth";
import { fetchQuery } from "convex/nextjs";
import { api } from "@/convex/_generated/api";
export default async function ProfilePage() {
const session = await auth();
const profile = await fetchQuery(
api.users.profile,
{},
{ token: session?.token }
);
return <div>{profile.name}</div>;
}
Route Handlers
Use Convex functions in Next.js API routes:
import { fetchQuery } from "convex/nextjs";
import { api } from "@/convex/_generated/api";
import { NextResponse } from "next/server";
export async function GET() {
const tasks = await fetchQuery(api.tasks.list, {});
return NextResponse.json(tasks);
}
export async function POST(request: Request) {
const body = await request.json();
const taskId = await fetchMutation(api.tasks.create, body);
return NextResponse.json({ id: taskId });
}
Client-side usage
For client-side reactivity, use the standard React hooks with ConvexProvider:
Layout or root component:
"use client";
import { ConvexProvider, ConvexReactClient } from "convex/react";
const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);
export function ConvexClientProvider({
children
}: {
children: React.ReactNode;
}) {
return <ConvexProvider client={convex}>{children}</ConvexProvider>;
}
Client Component:
"use client";
import { useQuery, useMutation } from "convex/react";
import { api } from "@/convex/_generated/api";
export default function Tasks() {
const tasks = useQuery(api.tasks.list, {});
const createTask = useMutation(api.tasks.create);
return (
<div>
{tasks?.map((task) => <div key={task._id}>{task.text}</div>)}
<button onClick={() => createTask({ text: "New task" })}>
Add Task
</button>
</div>
);
}
Patterns
Server Component with Client Component
Combine server-side data fetching with client-side reactivity:
// app/page.tsx (Server Component)
import { preloadQuery } from "convex/nextjs";
import { api } from "@/convex/_generated/api";
import TaskList from "./TaskList";
export default async function Home() {
const preloadedTasks = await preloadQuery(api.tasks.list, {});
return <TaskList preloaded={preloadedTasks} />;
}
// TaskList.tsx (Client Component)
"use client";
import { usePreloadedQuery, useMutation, Preloaded } from "convex/react";
import { api } from "@/convex/_generated/api";
export default function TaskList({
preloaded
}: {
preloaded: Preloaded<typeof api.tasks.list>;
}) {
const tasks = usePreloadedQuery(preloaded);
const createTask = useMutation(api.tasks.create);
return (
<div>
{tasks.map((task) => <div key={task._id}>{task.text}</div>)}
<button onClick={() => createTask({ text: "New" })}>
Add
</button>
</div>
);
}
Server Actions
"use client";
import { createTask } from "./actions";
export default function CreateTaskForm() {
return (
<form action={createTask}>
<input name="text" />
<button type="submit">Create</button>
</form>
);
}
// actions.ts
"use server";
import { fetchMutation } from "convex/nextjs";
import { api } from "@/convex/_generated/api";
export async function createTask(formData: FormData) {
const text = formData.get("text") as string;
await fetchMutation(api.tasks.create, { text });
}
Authentication
Pass auth tokens from your auth provider:
import { auth } from "@clerk/nextjs";
import { fetchQuery } from "convex/nextjs";
import { api } from "@/convex/_generated/api";
export default async function ProtectedPage() {
const { getToken } = auth();
const token = await getToken({ template: "convex" });
const data = await fetchQuery(
api.users.getCurrentUser,
{},
{ token }
);
return <div>{data.name}</div>;
}
Best practices
- Use
preloadQuery to hydrate Client Components with initial data
- Execute mutations in Server Actions for progressive enhancement
- Use
fetchQuery for server-only pages that don’t need reactivity
- Combine server and client rendering for optimal performance
- Pass authentication tokens via the
token option
- Use environment variables for the Convex URL
- Leverage Next.js caching strategies with
fetchQuery
Type safety
All functions are fully type-safe with generated API types:
import { api } from "@/convex/_generated/api";
// Fully type-checked
const tasks = await fetchQuery(api.tasks.list, {
completed: false // Type-checked
});
// tasks has type Task[]
const preloaded = await preloadQuery(api.tasks.list, {});
// preloaded has type Preloaded<typeof api.tasks.list>
Caching
Next.js caches fetchQuery results by default. Control caching with Next.js options:
import { fetchQuery } from "convex/nextjs";
import { api } from "@/convex/_generated/api";
// Force dynamic (no caching)
export const dynamic = 'force-dynamic';
// Or use Next.js revalidation
export const revalidate = 60; // Revalidate every 60 seconds
export default async function Page() {
const tasks = await fetchQuery(api.tasks.list, {});
return <div>{tasks.length} tasks</div>;
}