The Problem

Turning a LinkedIn profile into a personal website is manual, repetitive work. Most people either copy-paste content into a template or pay for a site builder. This generator automates the conversion by extracting structured resume data from a PDF export and rendering it as a deployable Next.js site.

What This Does

self.so is a Next.js app that ingests a LinkedIn PDF export, extracts resume data via a Together.ai LLM, and publishes it to a public route at /[username]. The pipeline lives in app/api/resume/route.ts (PDF processing) and app/api/username/route.ts (site publication). The resume editor is split between read-only components (components/resume/) and editing components (components/resume/editing/) that manage work experience, education, and skills.

The project is a fork of Nutlope/self.so (3,067 stars). It uses Clerk for auth, Upstash Redis as the database (lib/server/redisActions.ts), S3 for PDF storage (app/api/s3-upload/route.ts), and the Vercel AI SDK with Together.ai for LLM calls. A components/ui/ folder holds 30+ shadcn-style primitives.

How It Is Wired

The import graph maps 124 internal modules with 55 edges and zero circular dependencies. The two most connected modules are components/resume/editing/EditResume (imports 8 modules) and components/resume/FullResume (imports 7 modules)—both are leaf components with no importers, meaning they're mounted directly from pages. components/ui/label is the most-depended-upon module (5 importers, 0 dependencies), and lib/server/redisActions is the main effect carrier (4 importers, 1 dependency).

Execution flow: app/(private)/upload/page.tsxapp/api/s3-upload/route.ts (writes to S3) → app/api/resume/route.ts (calls Together.ai, writes parsed data to Redis) → app/[username]/page.tsx (reads Redis, renders FullResume). The app/[username]/og/route.tsx generates Open Graph images. A preview path exists at app/(private)/preview/ for editing before publish.

The wiring is clean—no cycles, short dependency chains. The risk is in components/resume/editing/EditResume.tsx, which orchestrates all editing dialogs (add skill, education, work experience) and carries the widest blast radius for UI changes.

How To Use It

Setup (from README):

pnpm install
pnpm run dev

Configuration: Copy .example.env to .env and fill in keys for Together AI, Upstash Redis, AWS S3, Clerk, and Helicone. The README documents each provider.

Running tests: pnpm test:run, pnpm test:ui, or pnpm test.

Real-World Use

A user logs in via Clerk, uploads a LinkedIn PDF. The app stores it in S3, runs Llama Guard for safety, sends it to Qwen via Together.ai with JSON-mode structured outputs, and writes the parsed resume to Redis. The user edits sections in the preview page, then publishes—their data is live at self.so/username with a shareable Open Graph card.

Code Health & Issues

Static analysis found 7 issues (3 high, 4 medium) across 3 kinds:

  • High – Deep nesting: components/ui/date-range-picker.tsx, components/ui/monthpicker.tsx, components/ui/chart.tsx hit max indentation depth 6. Control flow is hard to follow; extract guard clauses.
  • High – Duplicated code: 163 repeated 6-line blocks across 22 files, including app/(private)/layout.tsx, app/layout.tsx, and app/(private)/preview/client.tsx. Extract shared helpers.
  • Medium – Oversized file: components/ui/sidebar.tsx at 706 lines. Split by responsibility.

SDLC observations: tests exist (5 files), CI is configured (GitHub Actions), no Dockerfile, no committed secrets. 19 of 72 dependencies are behind major versions—react-dropzone is 6 majors behind (14.3.8 → 20.1.0), @types/node is 4 behind, and typescript is 2 behind (^5 → 7.0.2).

The Bottom Line

A functional, well-structured resume-to-site generator with a clean module graph and real CI. The main maintenance burden is the duplicated UI logic and the aging dependency tree. Worth using if you want a self-hosted LinkedIn-to-personal-site pipeline and can tolerate the LLM/Redis/S3 setup cost.