Portfolio
The public editorial product: CMS-backed Work and Writing, no static fallback catalog, and no service-role key in the public app.
- Next.js Server Components
- Supabase and PostgreSQL CMS
- Google Drive PDF export
- Resend contact workflow

The product tension
A personal site that lives in the repo is always a week behind the work. I needed a public product that founders and engineers can read without a deploy for every sentence, and without a second copy of the same facts in TypeScript.
The trap is a brochure: screenshots, a résumé dump, and no evidence. The other trap is a CMS that the public app cannot trust—so the site falls back to hardcoded pages and the two stories diverge. Portfolio had to be the live narrative of what I build, with Work as the proof, not a gallery.
What I built
I built jayantgoyal.com as a Next.js editorial product that reads only from the portfolio schema. There is no static fallback catalog. If a Work row is not visible, or a case study is not published, the detail route is a 404.
Read path. Server Components load typed public columns through @jayantgoyal/portfolio-contracts. The loader maps CMS rows into the editorial view model and caches them under the portfolio-content tag with a one-minute ISR window. Person name is not CMS-editable; shared identity supplies Jayant so Admin cannot rebrand the public person.
Write path. Admin owns mutations. Portfolio never creates a service-role client. Contact mail, résumé PDF, GitHub stats, and aggregate analytics stay on the server. Request admission lives in src/proxy.ts.
Proof. A case study is a structured JSON document—problem, solution, architecture (including Mermaid), decisions, security, tradeoffs, outcome—not a markdown file in git. The public renderer and the Admin form share that shape.
Walk it. This is already a public note, not a second product: how a record moves from Admin edit to the public page, and why the family is four apps with one data model.
How the system fits together
The public site is an editorial shell, not the Studio/Admin application chrome. Shared packages stop at identity, URLs, SEO helpers, and contracts. Content ownership stays in Postgres.
The calls that shaped the build
- 01
I refused a second copy of the work
If the public page can render without the CMS row, the CMS is decoration. I made published case studies the only way a Work detail exists, and I keep visibility and publication as explicit columns.
- 02
I kept privileged keys out of Portfolio
Portfolio and Auth use the anonymous key and RLS. Service-role work belongs in Admin after a live capability check. That is slower to operate and the reason a public deploy cannot silently become a CMS.
- 03
I split editorial UI from the product shell
Studio, Admin, and Auth share an application chrome. Portfolio does not. Recruiters should not feel like they logged into a console. Identity and URLs still come from packages so hosts do not drift.
- 04
I show four systems on home, not every experiment
CodeSync, this site, Studio, and Admin share the home grid. Auth and Studio's internal tools stay in the catalog or the Work archive so a formatter does not compete with a production RCM loop.
Trust boundaries
Public selects are column-limited. Administrative writes never run in this app. Contact submissions are validated and throttled; I do not persist raw network identifiers as a lead database. Analytics on the public site are aggregate Cloudflare signals, not visitor dossiers.
What I chose—and accepted
A CMS-backed site has cache and schema cost a static export does not. ISR can show a minute of stale copy after an Admin save. I accept that over dual-maintaining markdown and SQL. Four featured cards can look like a product list; I keep each one a full study so the grid is range, not filler.
Why the work matters
The live site is the same contract Admin edits. Work details fail closed without a published study. Home shows CodeSync, this publishing system, Studio, and Admin. Auth stays in the Work archive. The walkable demo is the CMS note, not another dashboard.
What I would improve next
I would add preview and a field-level diff in Admin before publication so a case-study save is reviewable, then keep the public cache tag. I will not add a fifth featured card to fill space.