CoreMVP's open-source Next.js application foundation.
Embedded Docs and Blog use local MDX and Fumadocs. The landing page links the quickstart, source repository, and account creation. See src/content/docs and src/content/blogs to customize the included content.
Build an individual-account application with email/password authentication, a protected dashboard, and one recurring Stripe subscription. Checkout, verified webhooks, persisted subscription state, and server access checks are connected so you can build your product on top of them.
Hikari is MIT licensed and independently maintained. CoreMVP provides the commercial startup application foundation for company-level capabilities.
Install Bun 1.3.14, Node.js 20.19 or later, and a running Docker-compatible daemon. Start in a fresh clone:
git clone https://github.com/coremvp/hikari.git
cd hikari
bun install
bunx supabase start
./coremvp env sync
bun run devOpen localhost:3000 and create an account. Signup signs you in immediately and opens Dashboard. Email confirmation is disabled. Password recovery emails arrive in the local Supabase mailbox at 127.0.0.1:55424.
env sync writes the local Supabase connection settings to ignored .env.local and preserves existing Stripe settings. ./coremvp env list reports whether each required variable is set, without displaying values. Authentication works before you configure Stripe; subscription features require the billing settings below.
The local Supabase project is hikari-oss, using ports 55420–55424. Its services are separate from other projects on your machine. Stop your development server and run bunx supabase stop when finished. Use bunx supabase db reset only to reset disposable local data.
Use a Stripe test account for development. Create one active, fixed-amount recurring price in the Stripe Dashboard. In that account's Customer Portal settings, enable payment-method updates, invoice history, and cancellation at the end of the billing period. Leave plan and quantity changes disabled for the included one-price setup. The Subscriptions guide shows the controls and complete setup.
Set these server-only values in ignored .env.local:
| Variable | Value source |
|---|---|
STRIPE_SECRET_KEY |
Test secret key from the selected Stripe account |
STRIPE_PRICE_ID |
The one approved recurring price, beginning with price_ |
STRIPE_WEBHOOK_SECRET |
Signing secret from the local listener below |
Forward subscription events to the application:
stripe login
stripe listen --events customer.subscription.created,customer.subscription.updated,customer.subscription.deleted,customer.subscription.paused,customer.subscription.resumed --forward-to localhost:3000/api/webhooks/stripeCopy the listener's signing secret into .env.local and restart bun run dev. Keep the listener running while testing Checkout. Sign in, open Account, and select Start subscription. Complete payment using Stripe's 4242 4242 4242 4242 test card, a future expiry, and any three-digit CVC. Refresh the subscription display after returning from Stripe.
Subscription access requires persisted active or trialing state on your configured price. Scheduled cancellation keeps access until Stripe changes that status. Other statuses and other prices deny access. Returning from Checkout does not grant access.
Existing subscriptions that still need management open Customer Portal instead of another Checkout. If only canceled or incomplete_expired subscriptions remain, you can start a new Checkout. Open sessions are reused during repeated Checkout requests. Portal price changes must remain on the approved price if you want them to keep application access.
The webhook accepts the five subscription lifecycle events listed above. It verifies the original request body, retrieves the current subscription from Stripe, and upserts its state. Updates for each subscription are serialized with a Postgres transaction lock. Failed retrieval or persistence returns an error so Stripe can retry; inspect failed deliveries in Stripe Workbench. Events for customers outside this application's customer mapping are acknowledged without creating access.
bun run lint
bun run typecheck
bun run test
bun run build
bunx playwright install chromium
./coremvp e2e auth
bun run test:integration
./coremvp e2e billing:subscriptionThe Auth journey uses the running local application, Supabase Auth, and recovery emails in the local mailbox. It checks immediate signup, the protected dashboard/account and API, signin, logout, and password recovery.
Unit tests cover subscription access and controlled provider-state convergence. Database integration uses the real local database and application service/repository/API with signed Stripe fixtures. It also checks that anonymous and authenticated Supabase clients cannot read or write billing tables. No Stripe API is called by those tests.
The subscription E2E needs a configured Stripe test account, the running listener, the application, and local Supabase. It creates a real test subscription through Checkout, waits for webhook-backed access, opens Customer Portal, and cancels its test subscription during cleanup. Missing settings fail the journey instead of silently skipping it. Do not use a live Stripe key.
Use a fresh Supabase project and one Vercel Next.js project. Hosted authentication and provider-backed Stripe test verification must be completed for your configuration before you release your application.
-
Create a Supabase project. Link this clone to that exact project and apply the migrations:
bunx supabase login bunx supabase link --project-ref <your-project-ref> bunx supabase db push
-
In Supabase Auth, set Site URL to your final HTTPS application origin. Add these Redirect URLs, replacing
<your-app>with your application's hostname:https://<your-app>/auth/callback https://<your-app>/auth/callback?next=/reset-password https://<your-app>/auth/confirmIn the Email provider settings, turn Confirm email off so signup signs users in immediately, as it does locally. For an initial test with Supabase's default recovery email template, open the recovery link in the same browser and device where you started the flow. Hikari's callback exchanges the PKCE code for a session. When your Supabase configuration permits custom templates, use
supabase/templates/recovery.html; its link usesSiteURLandTokenHash.The default hosted email service sends recovery emails only to your Supabase organization's members and has a low rate limit. Free projects using that service may reject template edits. Configure Auth SMTP delivery in Supabase and validate password recovery delivery to your intended users before releasing your application. Supabase Auth continues to own the email flow.
-
Create or link the Vercel project from the Hikari root:
bunx vercel login bunx vercel whoami bunx vercel teams ls bunx vercel project ls bunx vercel link --project <your-project-name>
Confirm the intended account and project; stop if they are wrong. For a team project, append
--scope <team-slug>to bothproject lsandlink; for a personal project, omit--scopeand select your personal account in the link prompts. Select the listed project or create a fresh project with your chosen name. Use the Next.js preset. The checked-invercel.jsonusesbun install --frozen-lockfileandbun run build; Next.js runs under Node.js. -
Add these variables to Production in the selected Vercel project's Dashboard, or use interactive
bunx vercel env add <name> productionprompts. Add--sensitiveforDATABASE_URL,STRIPE_SECRET_KEY, andSTRIPE_WEBHOOK_SECRET. Enter values only in the prompts or Dashboard fields.Variable Configuration APP_URLFinal HTTPS origin, with no path or query NEXT_PUBLIC_SUPABASE_URLThis Supabase project's API URL NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEYThis project's publishable key DATABASE_URLSupabase transaction-pooler URL, with TLS enabled STRIPE_SECRET_KEYSecret key for the selected Stripe test account STRIPE_PRICE_IDYour one approved recurring test price STRIPE_WEBHOOK_SECRETSigning secret for the hosted endpoint Only the two
NEXT_PUBLIC_SUPABASE_*values are public. Never put database credentials or Stripe secrets in a public variable. Follow Supabase's connection guide for the pooler and TLS settings. Drizzle usesprepare: falsefor transaction pooling.Verify all seven names target Production with
bunx vercel env ls productionbefore deploying. The deployment guide includes the complete prompt sequence. -
Follow the hosted destination steps: select Your account, snapshot events, and the five lifecycle events listed above. Match the event API version to your installed Stripe SDK using the guide's credential-free command. Register
https://<your-app>/api/webhooks/stripeand save this endpoint's signing secret as ProductionSTRIPE_WEBHOOK_SECRET. Configure Customer Portal in that account, then deploy to load the credentials. -
Deploy:
bunx vercel deploy --prod ./coremvp prod e2e smoke https://<your-app>
Smoke checks the hosted page, application liveness, and anonymous account rejection. It does not prove hosted authentication or billing. On the deployed application, create a fresh account and verify immediate Dashboard access, sign in, recover its password, complete Stripe test Checkout, verify active access in Dashboard, and open Customer Portal. Check successful signed delivery and the durable subscription row in your selected providers.
The schema transition refuses to discard nonempty legacy Hikari tables. Existing deployments need a backup and a separately planned data migration. Do not reset a hosted project to bypass that guard. The relaunch quickstart and deployment path target fresh projects.
flowchart LR
UI[React / React Query] --> API[Same-origin /api routes in Hono]
API --> Service[Services]
Service --> Data[Drizzle repositories]
Service --> Provider[Supabase Auth / Stripe providers]
Place pages in src/app, HTTP parsing and validation in src/api, business rules in src/services, persistence in src/repositories, and external calls in src/providers. The Next.js Auth callback routes handle provider protocols; application APIs use Hono.
Use requireUser() for authenticated operations and billing.requireAccess(user.id) for subscriber operations. Both run on the server. Never authorize from browser-editable metadata, cookie presence, a client cache, or Checkout query parameters.
customers stores the account-to-Stripe mapping. subscriptions stores subscription ID, customer, status, recurring price, cancellation flag, and period end. Supabase browser roles have no table privileges or client policies. Add your application's tables through migrations and update the Drizzle schema alongside them. App tables that remain server-owned should keep that same boundary.
Hikari includes an individual account, one subscription path, and embedded Docs/Blog. Organizations, memberships/roles, Projects, lifetime payments, guest checkout, admin, AI/RAG, monitoring, and analytics belong to the commercial CoreMVP product. Hikari does not include a newsletter or an app-level email provider.
MIT. Preserve the copyright and permission notice when redistributing the source. The CoreMVP name does not change the rights granted by the MIT license.