Bali business directory website built with Next.js, Clerk authentication, and Convex backend.
- Framework: Next.js 16 (App Router)
- Authentication: Clerk
- Backend / Database: Convex
- Styling: Tailwind CSS 4 + Radix UI
- Payments: Stripe
- Font: Sora (Google Fonts)
npm installCopy .env.example to .env.local and fill in the required values:
cp .env.example .env.localClerk handles user authentication (sign-in, sign-up, session management).
- Create an account at dashboard.clerk.com
- Create a new application
- Copy the keys into
.env.local:NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_xxxxx CLERK_SECRET_KEY=sk_test_xxxxx - Configure sign-in/sign-up redirect URLs in the Clerk dashboard to match:
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
Architecture — Custom Flows (non-prebuilt UI):
This project uses Clerk Custom Flows instead of Clerk's prebuilt <SignIn/> / <SignUp/> components. This approach gives full control over the UI to match the Figma design exactly, while still using Clerk's authentication backend.
Prebuilt (<SignIn/>) |
Custom Flows (useSignIn()) — our choice |
|
|---|---|---|
| UI control | Limited (colors/fonts only) | 100% custom |
| Clerk branding | "Secured by Clerk" watermark | None |
| Social login | Auto-rendered | Manual via authenticateWithRedirect |
| Email verification | Auto-handled | Manual (verification code step) |
Key hooks used:
useSignIn()— custom login flow (email + password)useSignUp()— custom registration flow (email + password + email verification code)useUser()— access current user datauseClerk()— Clerk instance (signOut, etc.)
File structure:
ClerkProviderwraps the app insrc/app/layout.tsx- Custom auth modal:
src/components/AuthModal.tsx— main login/register UI (matches Figma design, uses Custom Flows) - Fallback sign-in page:
src/app/sign-in/[[...sign-in]]/page.tsx(prebuilt, for direct URL access) - Fallback sign-up page:
src/app/sign-up/[[...sign-up]]/page.tsx(prebuilt, for direct URL access) - User sync hook:
src/hooks/useStoreUserEffect.ts(syncs Clerk user to Convex on login)
Auth flow:
- User clicks login/register in the Header → opens
AuthModal - Login:
signIn.create({ identifier, password })→setActive({ session })→ redirect to dashboard - Register:
signUp.create({ emailAddress, password })→ email verification code →attemptEmailAddressVerification→setActive({ session })→useStoreUserEffectsyncs user to Convex → redirect to dashboard
Convex is used as the real-time backend database.
- Create an account at dashboard.convex.dev
- Create a new project
- Copy the deployment URL into
.env.local:NEXT_PUBLIC_CONVEX_URL=https://your-project.convex.cloud - Run the Convex dev server alongside Next.js:
npx convex dev
Architecture:
- Convex functions are in the
convex/directory ConvexClientProviderinsrc/components/ConvexClientProvider.tsxwraps the app- User data is synced from Clerk to Convex via
useStoreUserEffecthook - Schema and mutations/queries are defined in
convex/(e.g.,convex/users.ts)
npm run devOpen http://localhost:3000 to view the app.
The app can be deployed on Vercel. Make sure to set all environment variables in the Vercel dashboard.
See Next.js deployment docs for details.