nextanative
The ticketing, collectibles and loyalty platform behind an Indonesian idol group: four apps sharing one typed Postgres schema.
Scale / counted from the repository
- deployable apps on one shared type package
- 4
- SQL migrations
- 84
- API route modules
- 56
- admin screens
- 40+
- achievement badges across 5 categories
- 40+
- feature flags
- 10
- locales: en, id, ja
- 3
- sweep interval for expired holds
- 60s
- Role
- Solo full-stack engineer
- Period
- Ongoing
- Type
- Fan platform · Live commerce
- Stack
- TypeScript, React 19, Next.js 15, Hono, Cloudflare Workers, Supabase Postgres
01
Overview
nextanative is a pop-punk idol group in Jakarta. Their core product is the cheki, a polaroid photo session with one member of the band, and it used to be sold through WhatsApp, Google Forms and spreadsheets. This is the platform that replaced that: a public site, a members app, a staff console and one API, built as a Turborepo where the business rules live in Postgres rather than in the clients. One fan identity carries every order, card, badge, stamp and voucher, which is what makes a loyalty economy possible at all.
One fan identity, carried through every surface
- 01Fan identity
- 02Order
- 03Card claim
- 04Badge earned
- 05VOLT wallet
- 06Marketplace
- 07Public profile
02
The challenge
Cheki demand is a queue of people trying to buy the same few slots at once. Two fans claim the last slot in the same second and the quota is sold twice. Someone holds a slot and never pays, so inventory stays frozen while real buyers are turned away. Member presales are a form link that leaks in minutes. The split between idol, management and photographer is reconciled by hand after every event. None of that is a screen problem. The hard part is keeping a slot sold exactly once, an early access window closed to the wrong tier, and a payout reproducible months later.
03
My contribution
- Designed the schema across 84 migrations: events, quotas, orders, collectible cards, badges, ledgers, vouchers and support tickets.
- Wrote the API as 56 Hono route modules on Cloudflare Workers, with JWKS token verification and role middleware.
- Built all three front ends: the Next.js public site, the Vite fan app and the Vite admin console.
- Wrote the Postgres functions that hold the risky rules: booking expiry, card claims, stamp redemption and badge eligibility.
- Set up deployment on Workers and Pages with staging and production environments, secrets and scheduled jobs.
04
Product surfaces
Surface
Who uses it
Built with
Runs on
Public site
@nexta/public
Who uses itEveryone, including search engines
Built withNext.js 15 App Router, server rendered
Runs onCloudflare Workers via OpenNext
O2SN fan app
@nexta/fan
Who uses itRegistered fans
Built withVite, React 19
Runs onCloudflare Pages
Admin console
@nexta/admin
Who uses itStaff, management, photographers, door crew
Built withVite, React 19
Runs onCloudflare Pages
API
@nexta/api
Who uses itThe three clients above
Built withHono 4
Runs onCloudflare Workers
05
Inside the product
01
Cheki ticketing
cheki_events · cheki_event_members
Pre-order and door sales are two channels on one event, each with its own quota, bundle price and form. Quotas can be capped per member or pooled, and a form can gain a field without a migration because the answers land in JSONB.
02
The fifteen minute hold
pg_cron, every 60 seconds
A fan holds a slot while they transfer the money. A database function expires the hold, returns the quota and flips the state in one statement, so a slow transfer cannot lock inventory for the rest of the day.
03
Early access enforced in Postgres
RLS policies on cheki_submissions
Member presales are timing windows the database evaluates against the fan's membership tier. A leaked link, an edited request body or a hand written curl call meets the same policy, because there is no check in the client to remove.
04
Door sales on a tablet
Cashier · check-in · shift recap
The on-the-spot flow is built for one hand and a queue of two hundred: carts across several members, bundle pricing applied automatically, a recap at the end of each shift, and a batch code the crew writes on the back of the physical polaroid.
05
Collectibles and achievements
card_claims · achievements.rules_config
A purchase can claim a collectible card into a digital binder and earn one of 40 badges. Each badge is a row with a rule, a hint, a progress style and two visual states, so a new achievement is an insert rather than a deploy.
06
A currency that cannot pay twice
volt_ledgers · stamp_ledgers
VOLT and loyalty stamps are append only ledgers with a unique key on user, reference and type. Balance is a sum, a replayed reward becomes a constraint violation, and every grant can be traced back to what caused it.
06
Technical approach
Quota arithmetic in the database
The sold count is a denormalised column on purpose, because counting submissions on every read would put an aggregate in the hottest screen of the app. It increments only inside the booking function, decrements on rejection and on delete, and a repair function can rebuild it, so drift is recoverable rather than fatal.
one statement, four guards
A hold that cannot become a lock
Expiry is not a client timer. A scheduled database job calls one function a minute, updates the expired rows and returns their quantity to the member quota in the same transaction.
release_expired_bookings()
Early access with no client gate
The presale window is a row level policy. The API never receives an early access decision to make, which means no future client can skip it.
row level security
Ledgers instead of balances
Rewards are written as immutable rows and balances are summed from them. A retried request that would pay twice fails on the constraint instead of quietly crediting twice.
UNIQUE (user_id, reference_id, transaction_type)
Achievements stored as data
Forty badges in five categories share one eligibility function that reads a JSONB rule per row. Adding an achievement is an insert, and its progress style (bar, streak, timer, hidden) travels with the row.
check_vault_eligibility()
One typed contract for four runtimes
Order status, badges and events are declared once and imported by every app. A change to a payment state that would break a screen fails the build first.
@nexta/types
Built with
- Monorepo
- Turborepo 2pnpm workspacesTypeScript 5.6, strict
- Front end
- React 19Next.js 15Vite 6TailwindRadixTanStack Queryreact-hook-form + Zod
- Back end
- Hono 4Cloudflare WorkersSupabase Edge Functions
- Data
- PostgreSQLRow level securitySECURITY DEFINER functionspg_cron
- Integrations
- ImageKitFirebase Cloud MessagingMonaco editorjspdf
- Translations
- next-intli18next
07
Screens
Fan app, order history
Order states from pending payment to approved, with the invoice the fan can download.
Cashier, live session
The on-the-spot cart at the door, with per member quantities and bundle pricing.
Revenue analytics
Per member revenue, fill rate and the split across idol, management and photographer.
Voltage ID
The public fan profile: equipped badge, oshi and collection highlights.
08
Where it stands
01
No automated tests yet
Correctness rests on database constraints, type checks and manual QA at live events. SQL tests for booking expiry, stamp redemption and card claims come first, then tests around bundle pricing and date handling.
02
Two shared packages are declared, not extracted
The shared UI still lives twice, once in the fan app and once in the admin app. The package exists and the build already resolves it; the move itself has not happened.
03
The gacha terminal is built and switched off
It ships behind a flag that defaults to off. Drop rates and currency sinks need tuning before it can open.
04
The admin console is Indonesian only
Operations staff are internal, so localising it would add maintenance without a reader.
05
These screenshots are schematics
The repository is private and the artwork belongs to the band. Each frame on this page is a placeholder that can be swapped for a real capture once permission is in writing.
09
Outcome
At a live event the platform covers the whole loop: registration, pre-order, payment approval, door sales, card claims, badges, loyalty vouchers and the payout report. The numbers that would make the strongest case (orders processed, members onboarded, hours of reconciliation removed) belong to the band, so they are not published here. What is public is the shape of the system and the rules it enforces.