work details

PaketJet — P2P Freight and Cargo Marketplace

A mobile-first logistics marketplace where carriers publish routes and spare capacity while senders find suitable transport options and unlock contact access.

Client

PaketJet

Start

01 Mar 2026

Completed

12 Mar 2026
PaketJet — P2P Freight and Cargo Marketplace interface and project preview
Description

A mobile-first logistics marketplace where carriers publish routes and spare capacity while senders find suitable transport options and unlock contact access.

Case study

Challenge

PaketJet launched with a booking- and wallet-based model, but the business had moved to selling contact access. Code, copy and schemas still described the old model, public listing data exposed too much, the mobile first load was slow and address entry was limited to a city list.

Approach

We treated the project as a full renewal: closed the legacy write paths and archived the history, tightened privacy and session checks, and made payments and credit use atomic. We redesigned the interface, built the address and map component as a shared package, and rewrote the SEO and GEO layer for the new model.

Outcome

paketjet.com is live with the new design, the step-by-step listing form and address-level search. In mobile lab measurements LCP fell to 0.73 seconds, and every delivery was documented with automated tests, real-browser flows and live checks.

Tools

Next.js 15, React 19, TypeScript, Fastify 5, Bun, MySQL 8, Drizzle ORM, Zod, Zustand, Tailwind CSS 4

Key Features
  • Free carrier listings, paid contact access for senders

  • Autocomplete search down to village, street and full address

  • Explained alternatives from the same province if no exact match

  • Four-step listing form with final review and admin approval

  • Route map from stored coordinates, loaded only on demand

  • Single purchases or packages of 1, 5 or 10 access credits

  • Atomic credit use and a retry- and race-safe payment flow

  • Admin panel for approvals, credits, disputes and support

  • Mobile lab LCP from 5.6 s to 0.73 s, CLS from 0.19 to 0.002

Technologies Used
  • Next.js 15

  • React 19

  • TypeScript

  • Fastify 5

  • Bun

  • MySQL 8

  • Drizzle ORM

  • Zod

  • Zustand

  • Tailwind CSS 4

Design Highlights
  • Lavender and light blue surfaces, navy headings, an animated route

  • Animation and video pause off-screen and stop for reduced motion

  • A 1.2-second intro shown once per tab and dismissed on any input

  • Layouts tested in light/dark, 360–1440 px and at 200% zoom

  • Accessible form that moves keyboard focus to each step heading

Details

PaketJet is a marketplace where carriers travelling between cities post their routes and spare capacity, and senders find a suitable listing and buy access to the carrier's contact details. Carriers post listings for free; the price and details of the transport are agreed between the two parties. The platform does not promise parcel tracking, escrow or a delivery guarantee. Its job is to connect the right sender with the right carrier.

Context and requirements

The first version of PaketJet launched with a ride-sharing-style structure built around bookings, a wallet, ratings and subscriptions. Over time the business model became simpler: the real product was access to a carrier's contact details and prepaid access credits. But the code, copy and schemas still described the old model; the home page promised bookings, and public listing data exposed more than it should. The first load on mobile was slow, and address entry was tied to a fixed list of cities.

The project was therefore treated as a renewal rather than a feature build: reflect the business model correctly in every layer of the code, harden privacy and the payment flow, redesign the interface, and verify each step with tests and in the live environment.

Solution and scope

Search and listings

The home page opens with a search form (From, To, Date) and an animated route scene. The address field is not tied to a city list: suggestions for provinces, districts, neighbourhoods, villages and full addresses come from the same component, and a manually typed address is accepted when nothing is found. Origin and destination swap with one button. If there are no listings for the exact address, listings from the same province are shown as explained alternatives, keeping the date, vehicle and direction of travel. The listing page uses crawlable pagination links.

Listing detail and contact access

The detail page shows the origin and destination, a map drawn from stored coordinates, the date and vehicle information. The carrier's identity and contact details are not part of the public data; the sender unlocks them with a single purchase or a package credit. Purchased contact details are recorded exactly as they were at the time of purchase.

Step-by-step listing form

Carriers create a listing in four steps: route and date, vehicle and details, contact, final review. Entered data is kept when going back, unvalidated steps cannot be skipped, and any section can be edited from the final review. Errors such as past dates, a departure after arrival, or invalid phone numbers and emails are caught immediately. Saved listings go live after admin approval.

User account

The account area keeps the main site navigation and uses horizontal tabs: my listings, my purchases, access credits and packages, notifications and profile. The profile screen supports uploading, changing and removing a photo, and refreshes the session after a password change. Transport rules, data protection, privacy and terms pages share a common reading layout with a section menu and print-friendly styling.

Admin panel

The admin panel runs on its own subdomain and includes: listing approval and filtering, carriers and users, contact purchases, user credits with audited credit adjustments, package pricing, payment settings and payment records, disputes, support tickets and FAQ, reports, email templates, Telegram notifications, theme, pages, media storage and an audit log. Old wallet and booking records are kept readable as an archive instead of being deleted.

Technical architecture

There are three separate applications: an API built with Fastify 5, Drizzle ORM and MySQL 8; the website built with Next.js, React 19 and Tailwind CSS 4; and the admin panel built with Next.js, RTK Query and Shadcn. The runtime is Bun and processes are managed with PM2. The address component is a local package shared by both Next.js apps.

  • Payments and credits: credit consumption is atomic; repeated payment notifications have a single effect, two simultaneous purchases cannot collide, and provider, amount and currency are verified on the server. Unclear outcomes go to a review queue without recording revenue or granting credits.
  • Privacy and sessions: identity and contact fields were removed from public listing data; role, active-account and session-version checks, secure cookies and return-URL validation were added.
  • Legacy model: new writes for bookings, wallet and ratings were closed, while historical records and old payment callbacks were kept for reconciliation; unused web files were identified through the import graph and removed.
  • Performance: under the same mobile lab conditions, median LCP dropped from 5.6 seconds to 0.73 seconds and CLS from 0.19 to 0.002; the automatic video load on first visit was removed.
  • SEO and GEO: page-specific canonicals, noindex on filtered results, a shared and tested metadata layer, a 1200×630 social card, a sitemap that fails loudly instead of returning partial results, structured data that matches the visible FAQ, and llms.txt.
  • Testing and operations: more than 130 automated backend and frontend tests, end-to-end flows in a real browser, versioned release directories, database and file backups with an isolated restore drill, and a checksummed migration log.

Integrations

Address suggestions come from Photon, which uses OpenStreetMap data; the API adds rate limiting, deduplication of identical queries and caching, and manual address entry keeps working if the service is down. Maps use OpenStreetMap and only load when the user opens them. The payment layer supports iyzico and PayTR and is closed by default, so no charge can start unless a provider is configured. Admin alerts go to Telegram, and transactional emails use the template system.

Outcome

PaketJet is live at paketjet.com as a marketplace that describes its business model honestly, reveals contact details only to the person who paid for them and protects the payment flow against retries and race conditions. The new design, the step-by-step listing form, village- and street-level search and the faster mobile load went live in the same renewal, with every delivery documented through tests and live browser checks.