Rukon
Back to Project Overview
Full Case StudyWeb Apps / Software

rukon-link

rukon-link: QR link hub with gated contact details and first-party analytics

rukon-link
Role
Sole Developer

Product, Full-Stack Build, Database, Security & Analytics

Affiliation / Client
Personal project
Timeline
October 2026 (live)
Documentation Level
4 Engineering Sections

Operational Workflow & State Machine

End-to-End Pipeline
  1. 01Scan the QR code
  2. 02Open a social link, or ask for contact details
  3. 03Validate and rate-limit the request
  4. 04Release only the methods asked for
  5. 05Review requests and traffic in the admin panel
01

Overview

rukon-link is the page behind a personal QR code. It lists social profiles and links to this portfolio openly, while email, phone and WhatsApp are kept off the page and released only when a visitor asks. Behind it is a private admin panel for the requests and for the page's own analytics.

02

Contact details on request

The private details exist only in server-side environment variables, so they are never in the HTML or the JavaScript a visitor downloads. A visitor who wants them fills in a short form with their name, email and consent. The server checks that the request came from the same site, rejects a filled honeypot or a form completed implausibly fast, validates and cleans every field, and applies a rate limit stored in the database against a hashed address: three requests in ten minutes, ten a day.

It then returns only the methods the visitor asked for, with a signed token, compared in constant time, that lets them see the details again for 24 hours without asking twice. Every request lands in the admin inbox, where it can be marked contacted or handled, or deleted.

03

Privacy-first analytics

Page views and link clicks are recorded by the site itself, with no third-party tracker and no cookies. The raw IP address is never stored: only a hash of it with a secret salt and the date, which changes daily and cannot be linked across days. Country and city come from the hosting edge, and bot traffic is filtered out.

The admin dashboard shows views, visits, unique visitors, link clicks and contact requests for today, yesterday, seven or thirty days, this month or a custom range, with a traffic chart and breakdowns by source, device, operating system, browser and location. A single SQL function computes the whole dashboard in one round trip. Events older than the retention period, 90 days by default and adjustable from the admin panel, are rolled up into daily totals and deleted by a scheduled job.

04

Architecture and security

One Next.js 16 application on Vercel with Supabase PostgreSQL behind it. Four tables, all prefixed and with Row Level Security enabled and no policies, so only the server can read or write them. Admin access is Supabase email sign-in restricted to one confirmed address, checked in every admin page and API route; the request proxy only refreshes the session and marks admin responses private and not indexable.

The site sends a standard set of security headers, generates its own social share images, and keeps the admin area and API out of search engines.

What's live, and what isn't

“It exists in the repository” and “it runs in production” are different claims. This is the difference, stated rather than left to be assumed.

  • ImplementedPublic link page, contact requests, analytics, admin panelLive at link.rukon.dev on Vercel, with the admin area and API excluded from search engines.
  • Not connectedNotifications for new contact requestsNot built: new requests appear only in the admin inbox.

Technical stack

Application

Next.js 16 (App Router)React 19TypeScriptTailwind CSS v4Server actions

Data

Supabase PostgreSQLRow Level Security (deny by default)SQL aggregate functionspg_cron retention job

Security

Supabase Auth (admin)HMAC-signed reveal tokensSame-site checksRate limitingSecurity headers

Limitations

  • No automated tests or CI; lint and type checks only.
  • No Content Security Policy yet, and the analytics endpoint's rate limit is per server instance.
  • One admin account.
  • The repository is private. Access can be granted on request.

Finished the technical breakdown?

Return to the concise HR overview or discover other projects in the portfolio.

Writing from this work

Vercel or a Self-Hosted Ubuntu Box? How I Chose Hosting App by App

Eight deployed apps, three hosting shapes: Vercel for pages and serverless APIs, a self-hosted Ubuntu box for a stateful API, and a hybrid for a model Vercel can't run.

Two Ways I Use Supabase Row Level Security: Deny-All Behind a Server, or Per-Owner Policies

Across five projects, RLS does one of two jobs: close the Data API while a trusted server does the work, or let Postgres enforce who owns each row. When to pick which.

Sharing One Supabase Project Between Two Apps Safely

A second app inside an existing Supabase project: prefixed tables, RLS on with no policies, revoked grants, server-only access and an admin check for a shared user list.

One SQL Function for a Whole Analytics Dashboard

Every figure on a small analytics dashboard, totals, a gap-free time series and ranked breakdowns, returned as one JSON document by one PostgreSQL function, in the owner's time zone.

Refreshing a Supabase Session in Next.js 16's proxy.ts, and Not Authorising There

Next.js 16 renamed Middleware to Proxy. Using proxy.ts only to refresh a Supabase session cookie for admin routes, while every page and API route checks authorisation itself.

Keeping Your Email and Phone Off a Public Link Page Until Someone Asks

A QR link page that never ships private contact details in its HTML or JavaScript. Server-only values, a short request form, only the asked-for methods returned, and a 24-hour signed re-reveal.

First-Party Analytics Without Cookies: A Daily-Rotating Salted IP Hash

Page views and link clicks recorded by the site itself, with no third-party tracker and no cookies. A salted IP hash that changes daily, beacons that survive navigation, and what is still stored.

Contact Form Spam Protection Without a CAPTCHA, and Its Honest Limits

A honeypot, a minimum fill time, a same-site check, strict validation and a database-backed rate limit on a Next.js contact form. What each layer stops, and which ones a script can walk past.

Keeping Analytics for 90 Days: Roll Up, Then Delete, With pg_cron

Detailed analytics events kept for a set period, then summarised into daily totals and deleted by a scheduled PostgreSQL function. The retention setting, the job, and the time-zone trade-offs.