Building AutoPort solo: a multilingual vehicle-export SaaS on Next.js 16, Hono & Supabase
AutoPort is a B2B operations platform for Japanese used-car exporters — vehicle and shipment pipelines, container tracking, multi-currency buyer ledgers, and staff role-based access, in four languages including two right-to-left ones. I designed, built, and shipped it as the sole developer.
The problem
Japanese used-car export is a business of state transitions. A vehicle is bought at auction, inspected, assigned to a buyer, loaded into a container, shipped, cleared, and settled — and money moves in a different currency at nearly every step. Most exporters run this on spreadsheets and chat messages, where the truth about "where is this car and who has paid for it" lives in several places at once and disagrees with itself.
AutoPort's job is to make that single-sourced: one pipeline, one ledger per buyer, one place where staff see exactly what their role permits.
Why Next.js and Hono, rather than Next.js alone
Next.js Route Handlers would have been the path of least resistance. I split the backend into a standalone Hono service instead, for three reasons that mattered more than the convenience:
- The API outlives the frontend. Exporters want integrations. A framework- independent HTTP service can be consumed by a mobile client or a partner system without dragging Next.js rendering concerns along.
- Independent runtimes. The web app deploys to Vercel; the API runs on Heroku as a long-lived Node process. Image processing, PDF generation, and webhook fan-out are poor fits for serverless invocation limits.
- Honest boundaries. With a network hop between them, it is impossible to reach past the API and query the database from a component by accident. Every rule about who may see what lives in one place.
The cost is real and worth naming: two deploy targets, cross-origin auth, and shared types that must be kept in sync through the Turborepo workspace. For a solo developer that is a deliberate trade, not a free win.
Multi-tenancy and access control
Every request resolves to an exporter (the tenant) and a role before it touches data. Authorization is enforced server-side in the API's shared middleware rather than in the UI, so hiding a button is a usability decision, never a security one.
| Concern | Where it lives |
|---|---|
| Tenant resolution & buyer scoping | Hono middleware, applied before route handlers |
| Row-level isolation | Supabase RLS policies keyed on the tenant |
| Privileged operations | Separate super-admin surface with its own auth path |
| Abuse control | Rate limiting in shared middleware |
Files are an access-control problem too
Export paperwork — invoices, bills of lading, inspection sheets — is sensitive. Uploads are validated by inspecting magic bytes rather than trusting the declared MIME type or file extension, so a renamed executable cannot masquerade as a JPEG. Downloads are served through short-lived signed URLs, with a cache in front so that listing a container's twenty documents does not mint twenty fresh signatures on every render.
Multi-currency ledgers
A buyer pays deposits in one currency against vehicles priced in another, with FX moving between the two. The ledger is append-only: balances are derived from entries rather than stored and mutated. Recomputing a balance is cheap; explaining a balance that drifted is not. FX rates are captured with the entry, so a historical statement reproduces exactly what the buyer saw at the time — not what the rate happens to be today.
Four languages, two of them right-to-left
AutoPort ships in English, Japanese (日本語), Arabic (العربية) and Urdu (اردو) via
next-intl. Arabic and Urdu are right-to-left, which is where most
internationalization work quietly falls apart.
-
Logical CSS properties throughout.
margin-inline-start, notmargin-left. Direction-aware layout is a styling discipline, not a per-component conditional. - Icons that encode direction must flip; icons that encode meaning must not. A "next" chevron mirrors. A logo does not.
- Numbers, currency, and dates are locale-formatted, not string-concatenated. Mixing LTR digits into RTL text is where bidirectional rendering gets genuinely subtle.
- Japanese changes layout density. Japanese labels are often far shorter than their English equivalents, so fixed-width columns tuned for English look broken.
The translation catalogues total well over half a megabyte across the four locales — a reminder that i18n is an ongoing content obligation, not a one-time integration.
Observability
Both the web app and the API are instrumented with OpenTelemetry — distributed traces across the two services, plus metrics on the API. When an exporter reports that a shipment update "took forever," the question is answerable with a trace rather than a guess. Product analytics run through PostHog, kept deliberately separate from operational telemetry.
The stack, in full
| Layer | Choice |
|---|---|
| Web | Next.js 16, React 19, TypeScript, Tailwind, TanStack Query, React Hook Form + Zod |
| API | Hono 4, TypeScript, Zod validation, Sharp, Svix webhooks |
| Data & auth | Supabase (Postgres, RLS, Auth, Storage) |
| i18n | next-intl — en / ja / ar / ur, with RTL support |
| Telemetry | OpenTelemetry traces + metrics, PostHog |
| Infrastructure | Turborepo monorepo, Vercel (web), Heroku (API) |
What I would revisit
The two-service split is the right call for this product, but it front-loads work that a
single Next.js app defers — auth across origins, shared contracts, two pipelines to keep
green. If I were starting a product whose API surface would never leave the browser, I
would not repeat it. The RTL work, by contrast, is worth doing on day one every time:
retrofitting direction-awareness into a codebase already full of margin-left
is dramatically more expensive than starting with logical properties.
See it live: useautoport.com
I'm Usman Ghani, a full-stack developer in Islamabad, Pakistan, working across Next.js, Flutter, and machine learning. If you're building something in this territory, get in touch.