Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings
/better-convexPublic template

A modern Convex + Next.js template with best practices

License

NotificationsYou must be signed in to change notification settings

udecode/better-convex

Repository files navigation

A modern Next.js starter template featuringConvex backend withBetter Auth integration, showcasing type-safe backend patterns and custom authentication helpers. Includes React Compiler for automatic optimizations.

Key Features

  • 🔐 Better Auth Integration: Complete authentication with GitHub and Google OAuth, session management, and organization support
  • 👥 Multi-Organization Support: Personal and team organizations with role-based access (owner/member)
  • 💳 Subscription Ready: Polar payment integration with Premium subscriptions and monthly credits (coming soon)
  • 📊 Full-Stack Type Safety: End-to-end TypeScript with Convex Ents for relationships and custom function wrappers
  • ⚡ Rate Limiting: Built-in protection with tier-based limits (free/premium)
  • 🎯 Starter Features: Todo management, projects, tags, and comments with soft delete
  • 🔍 Search & Pagination: Full-text search indexes and efficient paginated queries
  • 🚀 Developer Experience: Pre-configured hooks, RSC helpers, auth guards, and skeleton loading

Tech Stack

  • Framework: Next.js 16 with App Router & React 19.2 (React Compiler enabled)
  • Backend: Convex with Ents (entity relationships)
  • Authentication: Better Auth with better-auth-convex package & organization plugin
  • Payments: Polar integration (subscriptions & credits)
  • Styling: Tailwind CSS v4 with CSS-first configuration
  • State: Jotai-x for client state, React Query for server state
  • Forms: React Hook Form + Zod validation
  • UI: shadcn/ui components with Radix UI primitives
  • Code Quality: Ultracite (Biome preset) for linting/formatting, Lefthook for git hooks

Getting Started

Prerequisites

  • Node.js 18 or later
  • pnpm package manager
  • GitHub and/or Google OAuth app credentials

Setup Instructions

  1. Clone and install dependencies:
git clone<your-repo-url>cd better-convexpnpm install
  1. Set up environment variables:

Create.env.local for Next.js:

cp .env.example .env.local

Createconvex/.env for Convex:

cp convex/.env.example convex/.env

Add credentials toconvex/.env:

# Required environment variablesGITHUB_CLIENT_ID=your_github_client_idGITHUB_CLIENT_SECRET=your_github_client_secretGOOGLE_CLIENT_ID=your_google_client_idGOOGLE_CLIENT_SECRET=your_google_client_secretRESEND_API_KEY=your_resend_api_key
  1. Start development servers:
# This will start both Next.js and Convexpnpm dev
  1. Initialize Convex environment (first time only):

In a new terminal:

pnpm sync
  1. Open the app:

Navigate tohttp://localhost:3005

Database Management

pnpm init# Populate with sample datapnpm reset# Reset all tablespnpm studio# Open Convex dashboard

Custom Convex Functions

Instead of using raw Convexquery/mutation/action, this template provides custom wrappers with built-in auth, rate limiting, and type safety:

Backend Functions (convex/functions.ts)

// Public query - auth optionalexportconstexample=createPublicQuery()({args:{id:zid("items")},// Always use zid() for IDsreturns:z.object({name:z.string()}).nullable(),handler:async(ctx,args)=>{returnawaitctx.table("items").get(args.id);},});// Protected mutation with rate limitingexportconstcreateItem=createAuthMutation({rateLimit:"item/create",// Auto tier limitsrole:"admin",// Optional role check (lowercase)})({args:{name:z.string().min(1).max(100)},returns:zid("items"),handler:async(ctx,args)=>{// ctx.user is pre-loaded SessionUser with ent methodsreturnawaitctx.table("items").insert({name:args.name,userId:ctx.user._id,});},});

Available function types:

  • createPublicQuery() - No auth required
  • createAuthQuery() - Requires authentication
  • createPublicMutation() - Auth optional
  • createAuthMutation() - Requires auth
  • createPublicPaginatedQuery() - With pagination
  • createAuthPaginatedQuery() - Auth + pagination
  • createInternalQuery/Mutation/Action() - Convex-only

Client-Side Helpers

React Hooks (src/lib/convex/hooks)

// Never use useQuery directly - use these wrappersconst{ data, isPending}=usePublicQuery(api.items.list,{});// ALWAYS pass {} for no argsconst{ data}=useAuthQuery(api.user.getProfile,{});// Skips if not auth// Mutations with toast integrationconstupdateSettings=useAuthMutation(api.user.updateSettings);toast.promise(updateSettings.mutateAsync({name:"New"}),{loading:"Updating...",success:"Updated!",error:(e)=>e.data?.message??"Failed",});// Paginated queriesconst{ data, hasNextPage, fetchNextPage}=usePublicPaginatedQuery(api.messages.list,{author:"alice"},{initialNumItems:10});

Server Components (src/lib/convex/server.ts)

// Auth helpers for RSCconsttoken=awaitgetSessionToken();// Returns string | nullconstuser=awaitgetSessionUser();// Returns SessionUser & { token } | nullconstisAuthenticated=awaitisAuth();// Fetch with authconstdata=awaitfetchAuthQuery(api.user.getData,{id:userId});constdata=awaitfetchAuthQueryOrThrow(api.user.getData,{id:userId});

Schema & Database

The template includes two schemas working together:

Core Schema (Convex Ents)

// convex/schema.ts - Application data with relationshipsconstschema=defineEntSchema({user:defineEnt({name:v.optional(v.string()),bio:v.optional(v.string()),personalOrganizationId:v.string(),// Every user has a personal org}).field("email",v.string(),{unique:true}).edges("subscriptions",{ref:"userId"})// Polar subscriptions.edges("todos",{ref:true}).edges("ownedProjects",{to:"projects",ref:"ownerId"}),todos:defineEnt({title:v.string(),description:v.optional(v.string()),}).field("completed",v.boolean(),{index:true}).deletion("soft")// Soft delete support.edge("user").edge("project",{optional:true}).edges("tags")// Many-to-many.searchIndex("search_title_description",{searchField:"title",filterFields:["userId","completed"],}),});

Better Auth Schema

// convex/authSchema.ts - Auto-generated auth tables// Generated via: cd convex && npx @better-auth/cli generate -y --output authSchema.ts// Includes: user, session, account, organization, member, invitation

Key Patterns from.cursor/rules/convex.mdc

Authentication Context

In authenticated functions,ctx.user is a pre-loadedSessionUser with full entity methods:

handler:async(ctx,args)=>{// ❌ Don't refetch the userconstuser=awaitctx.table("user").get(ctx.user._id);// ✅ Use pre-loaded userawaitctx.user.patch({credits:ctx.user.credits-1});};

Rate Limiting

Define limits inconvex/helpers/rateLimiter.ts:

exportconstrateLimiter=newRateLimiter(components.rateLimiter,{'comment/create:free':{kind:'fixed window',period:MINUTE,rate:10},'comment/create:premium':{kind:'fixed window',period:MINUTE,rate:30},});// Auto-selects tier based on user plan (free/premium)createAuthMutation({rateLimit:'comment/create'})({...});

Validators

Two different validator systems are used:

  • Schema files (convex/schema.ts): Usev. validators ONLY
  • Function files (convex/*.ts): Usez. validators ONLY
// Schema (v.) - in convex/schema.ts.field('email',v.string(),{unique:true})// Functions (z.) - in convex/*.tsargs:{email:z.string().email(),id:zid('user')// Always use zid() for IDs}

Development Commands

pnpm dev# Start dev serverspnpm typecheck# Run TypeScript checkspnpm lint# Check code with Ultracite/Biomepnpm lint:fix# Fix linting and formatting issuespnpm check# Run all checks (lint, ESLint, TypeScript)pnpm seed# Seed databasepnpm reset# Reset databasepnpm studio# Open Convex dashboard

Best Practices

  1. Never use rawquery/mutation - Always use custom wrappers
  2. Usezid() for IDs in functions,v.id() in schemas
  3. Pass{} for no args in queries, notundefined
  4. Usectx.table() instead ofctx.db (banned, except for streams first param)
  5. Leverage pre-loadedctx.user in auth contexts
  6. Use.optional() not.nullable() for optional args
  7. Never create indexes for edge-generated fields

File Structure

convex/├── functions.ts      # Custom function wrappers├── schema.ts         # Database schema├── auth.ts          # Better Auth setup├── todos.ts         # Example CRUD operations└── helpers/    └── rateLimiter.tssrc/lib/convex/├── hooks/           # React hooks├── server.ts        # RSC helpers├── auth-client.ts   # Client auth setup└── components/      # Auth components

Claude Agents & Cursor Rules

This template includes specialized AI agents and coding rules to enhance your development experience:

Cursor Rules (.cursor/rules/)

Core Convex Rules

  • convex.mdc ⭐ -CRITICAL: Complete Convex patterns guide (MUST READ for backend work)
  • convex-client.mdc - Client-side Convex integration patterns
  • convex-ents.mdc - Entity relationships and edge patterns
  • convex-aggregate.mdc - Efficient counting with O(log n) performance
  • convex-optimize.mdc - Performance optimization patterns
  • convex-search.mdc - Full-text search implementation
  • convex-streams.mdc - Advanced filtering with consistent pagination
  • convex-trigger.mdc - Database triggers and reactive patterns
  • convex-scheduling.mdc - Cron jobs and scheduled functions
  • convex-http.mdc - HTTP endpoints and webhooks
  • convex-examples.mdc - Reference implementations

Frontend Rules

  • react.mdc - React component patterns
  • nextjs.mdc - Next.js routing and RSC patterns
  • jotai-x.mdc - State management patterns
  • toast.mdc - Notification patterns
  • ultracite.mdc - Code quality standards and formatting rules

Start from Scratch

To remove all starter code and keep only auth/user functionality:

Backend Files to Delete (convex/)

# Function filesrm convex/todos.tsrm convex/todoInternal.tsrm convex/todoComments.tsrm convex/projects.tsrm convex/tags.tsrm convex/seed.ts

Authentication Implementation

Admin Setup

Admin users are configured via environment variables and automatically assigned admin role on first login:

# convex/.envADMIN="admin@example.com,another@example.com"

Initialization (convex/init.ts)

Auto-runs on dev server startup (--run init):

  • Creates admin users fromADMIN env variable
  • Assignsrole: 'admin' to pre-configured emails
  • Runs seed data in development environment

Seeding (convex/seed.ts)

Development data population:

  • seedUsers: Creates Alice, Bob, Carol, Dave test users
  • generateSamples: Creates sample projects with todos (auth-protected action)
  • Preserves admin user and existing sessions during cleanup

Reset (convex/reset.ts)

Database cleanup utilities (dev only)

Frontend Files to Delete (src/)

# Page routesrm -rf src/app/projects/rm -rf src/app/tags/# Componentsrm -rf src/components/todos/rm -rf src/components/projects/# Breadcrumb navigation (optional - uses todo examples)rm src/components/breadcrumb-nav.tsx

Schema Updates (convex/schema.ts)

Remove these tables and their edges from the schema:

  • todos table
  • projects table
  • tags table
  • todoComments table
  • projectMembers table (join table)
  • todoTags table (join table)
  • commentReplies table (join table)

Update theusers table to remove edges:

user:defineEnt({// Keep profile fieldsname:v.optional(v.string()),bio:v.optional(v.string()),image:v.optional(v.string()),role:v.optional(v.string()),deletedAt:v.optional(v.number()),}).field("emailVerified",v.boolean(),{default:false}).field("email",v.string(),{unique:true});// Remove all todo/project related edges

Aggregates Updates (convex/aggregates.ts)

Keep only:

  • aggregateUsers

Remove:

  • aggregateTodosByUser
  • aggregateTodosByProject
  • aggregateTodosByStatus
  • aggregateTagUsage
  • aggregateProjectMembers
  • aggregateCommentsByTodo

Config Updates (convex/convex.config.ts)

Remove aggregate registrations:

// Keep only:app.use(aggregate,{name:"aggregateUsers"});// Remove all todo/project/tag related aggregates

Triggers Updates (convex/triggers.ts)

Remove all todo/project/tag related triggers if any exist.

Home Page Update (src/app/page.tsx)

Replace with a simple authenticated landing page:

exportdefaultasyncfunctionHomePage(){return(<divclassName="container mx-auto px-4 py-6"><h1className="mb-4 text-3xl font-bold">Welcome</h1><p>Your authenticated app starts here.</p></div>);}

Clean Generated Files

After making these changes:

# Regenerate Convex typespnpm dev

This will give you a clean authentication-only starter with:

  • ✅ Better Auth integration
  • ✅ User management
  • ✅ Rate limiting
  • ❌ No todo/project/tag starter code

Resources

About

A modern Convex + Next.js template with best practices

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors2

  •  
  •  

Languages


[8]ページ先頭

©2009-2025 Movatter.jp