LinkSky is a polished bio-link builder built on Next.js App Router, Prisma and PostgreSQL. It lets creators ship a personal profile page with music, custom media, social links, live preview and a styled public URL.
- username or email authentication
- session cookies with tighter validation and rate limiting
- dashboard with autosave, upload flows and drag-and-drop social ordering
- avatar cropper and 16:9 background cropper
- audio, cursor and image processing before persistence
- Backblaze B2 private-bucket storage via server-side proxy
- public profile analytics: views, clicks, CTR, referrers and 14-day trend
- generated Open Graph image per profile
- preview route with dummy data and no database dependency
- custom 404, robots.txt and sitemap.xml
- test coverage for validation, URL sanitization and rate limiting
- Next.js 15
- React 19
- TypeScript
- Prisma ORM
- PostgreSQL
- Tailwind CSS 4
- Motion
- Zod
- Sharp
- Vitest
- Install dependencies.
npm install- Copy the environment template.
Copy-Item .env.example .env- Fill in
DATABASE_URL, set your site URL values, and add your Backblaze B2 credentials.
NEXT_PUBLIC_SITE_URL=http://localhost:3000
SITE_URL=http://localhost:3000
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/linksky
MEDIA_STORAGE_DRIVER=b2- Add the B2 bucket credentials.
MEDIA_STORAGE_DRIVER=b2
B2_REGION=us-west-004
B2_ENDPOINT=https://s3.us-west-004.backblazeb2.com
B2_BUCKET=linksky-media
B2_KEY_ID=...
B2_APPLICATION_KEY=...- Generate Prisma client and apply migrations.
npm run prisma:generate
npm run prisma:migrate -- --name init- Start the dev server.
npm run devnpm run dev
npm run build
npm run start
npm run lint
npm run test
npm run test:watch
npm run clean
npm run prisma:generate
npm run prisma:migrate -- --name init
npm run prisma:deployapp/routes, layouts, metadata and UIapp/dashboard/authenticated editor and analytics widgetsapp/[username]/public profile page, OG image and loading stateapp/api/auth, profile, upload, media and analytics endpointscomponents/ui/shared client UI such as toast providerlib/Prisma helpers, validation, upload processing, rate limiting and URL sanitizersprisma/schema and migrationstests/Vitest coverage for core helpers
Media now lives only in Backblaze B2. PostgreSQL stores profile data only.
What happens in the current setup:
- upload type is validated server-side
- MIME sniffing is done from file bytes, not only browser metadata
- avatar and background images are resized and normalized through
sharp - cursor uploads are constrained and converted safely
- replaced owned media is deleted when no longer referenced by any profile
/api/media/:idstays stable and streams files from the private bucket
- bcrypt password hashing
- per-route in-memory rate limiting on auth, profile updates, uploads and analytics events
- Zod validation on auth, profile, username availability, upload kind and social clicks
- CSP,
X-Content-Type-Options,Referrer-Policy,X-Frame-OptionsandPermissions-Policy - asset URL sanitization and external URL normalization
- global metadata in
app/layout.tsx - dynamic metadata for
/:username - generated OG image at
/:username/opengraph-image robots.txtandsitemap.xml- preview pages marked
noindex
Run the current automated checks:
npm run lint
npm run test
npm run buildAt the time of the latest update all three pass.
- set
NEXT_PUBLIC_SITE_URLandSITE_URLto the production domain - run
npm run prisma:deployduring deploys - use managed Postgres or Supabase for production
- keep the B2 bucket private
- set
MEDIA_STORAGE_DRIVER=b2 - provide
B2_REGION,B2_ENDPOINT,B2_BUCKET,B2_KEY_ID,B2_APPLICATION_KEY