This document provides a comprehensive technical reference for the Portfolio & Content Management System at atul.ople.in, explaining what works, how each component is implemented, architectural patterns, and targets for deployment and future expansion.
A production-grade, full-stack personal portfolio and admin management console designed for speed, visual craftsmanship, and seamless content updates without code deployments.
- Primary Target: Self-hosted Docker container on Oracle Cloud (Always Free Tier) orchestrated via Dokploy.
- Alternative Targets: Node/Bun standalone server via
srvx, VPS, or containerized serverless hosting. - Zero-Cost Architecture: Uses free-tier cloud resources (Neon Serverless PostgreSQL, Cloudinary free media tier, Oracle Cloud Ampere compute).
| Layer | Technology | Version | Purpose |
|---|---|---|---|
| Framework | TanStack Start (@tanstack/react-start) |
1.168.x |
Full-stack React SSR framework |
| Routing | TanStack Router (@tanstack/react-router) |
1.170.x |
100% type-safe file-based routing |
| Bundler | Rsbuild (@rsbuild/core) |
2.1.x |
High-performance Rust-powered Rspack build tool |
| Runtime | Bun / Node.js | 1.4.x / v20+ |
Fast JavaScript runtime and package manager |
| UI Library | React 19 (react, react-dom) |
19.2.x |
Modern component rendering |
| Styling | Tailwind CSS v4 (tailwindcss) |
4.3.x |
Utility-first responsive design & custom glass themes |
| Animations | Motion (motion) |
12.x |
Spring physics micro-interactions & animated reveals |
| Icons | Lucide React (lucide-react) |
0.513.x |
Scalable vector iconography |
| Database | PostgreSQL / Neon Serverless | 0.10.x |
Relational domain and auth storage |
| ORM | Drizzle ORM (drizzle-orm) |
0.45.x |
Type-safe SQL query builder and migrations |
| Auth | Better-Auth (better-auth) |
1.6.x |
Authentication, sessions, and security |
| Media Storage | Cloudinary (cloudinary) |
2.10.x |
Unsigned widget uploads & signed asset cleanup |
[ Client Browser ]
│ │
Initial SSR Request │ │ Client Navigation & Prefetch
▼ ▼
┌────────────────────────────────────────────────────────────────────────────────┐
│ TanStack Start Application │
│ │
│ [ Public Layout: _public.tsx ] [ Admin Layout: admin.tsx ] │
│ ├── Home (/) ├── Dashboard (/admin) │
│ ├── Projects (/projects, /projects/$slug) ├── Projects (/admin/projects) │
│ ├── Certifications (/certifications) ├── Certs (/admin/certifications)│
│ ├── About (/about) ├── Skills (/admin/skills) │
│ ├── Resume (/resume, /resume/pdf) ├── Experience (/admin/exp) │
│ └── Contact (/contact) └── Profile (/admin/profile) │
│ │
│ [ Client Data Cache (lib/public-data-cache.ts) ] │
│ └── Background warm cache after initial render for zero-latency page transitions
└───────────────────────┬──────────────────────────────────┬─────────────────────┘
│ │
Server Functions (createServerFn) Cloudinary Upload Widget
│ │
▼ ▼
┌──────────────────────────────┐ ┌───────────────────┐
│ Drizzle ORM (db/index.ts) │ │ Cloudinary │
└──────────────┬───────────────┘ │ Asset Storage │
│ └───────────────────┘
▼
┌──────────────────────────────┐
│ PostgreSQL / Neon DB │
└──────────────────────────────┘
- Problem Solved: Eliminates page flicker and loader spinners when navigating between public pages (Home, Projects, Certifications, About, Resume).
- How It Works:
- On first load of
_public.tsx, an initial SSR fetch loadsprofile. - Immediately after mount,
prefetchPublicSiteData()triggers a single parallel query (getPublicSiteData()) fetching all published projects, certifications, skills, and resume content. - Subsequent page loads check
getCachedPublicSiteData()orgetWarmPublicDataCache(). If available, data renders synchronously from memory. - Any admin modification calls
clearPublicDataCache(), guaranteeing fresh data when returning to the public site.
- On first load of
- Public Surface (
/certifications):- Filterable by issuing organization (e.g. AWS, Meta, Google, freeCodeCamp).
- Real-time search across titles, issuers, credential IDs, and skill tags.
- Credential ID display with single-click clipboard copy (
navigator.clipboard.writeText) and confirmation toast. - Direct "Verify Credential" external link opening the official verification platform (Credly, Accredible, Coursera, etc.).
- "View Certificate" document preview modal supporting both image formats and interactive embedded PDF documents.
- Admin Management (
/admin/certifications):- Comprehensive list with inline status toggles (
Published/Draft). - Creation form (
/admin/certifications/new) and editing form (/admin/certifications/$id). - Integrated Cloudinary upload widget targeting the
portfolio/certificationsfolder. - Deletion of a certification triggers Cloudinary API cleanup to prevent orphaned media storage.
- Comprehensive list with inline status toggles (
- Protected Layout (
src/routes/admin.tsx):beforeLoadhook callsgetSession(). If unauthenticated, it throws a redirect to/login.- Responsive collapsible sidebar with active state glow, badge counters, and direct link to the live portfolio.
- Header with breadcrumbs, dark/light mode toggle, and responsive mobile navigation drawer.
- Dashboard Command Center (
src/routes/admin/index.tsx):- Key performance indicator (KPI) metric cards for Projects, Certifications, Skills, and Experience.
- 4 quick-action launch buttons ("Add Certification", "Add Project", "Update Resume", "Edit Profile").
- Status overview tables for quick access and status auditing.
- Replaced outdated arbitrary 1–5 progress bars with a categorized engineering skill matrix (
src/components/portfolio/skills-grid.tsx). - Categorized into: Languages, Frontend, Backend, Databases & ORMs, Frameworks, Tools & DevOps, and Systems.
- Proficiency levels represented by distinct badge levels: Expert / Core, Advanced, Proficient, and Working Knowledge.
- Powered by
better-authwith the Drizzle ORM adapter. - First-time setup route (
/setup) checkscheckUsersExist(). If no admin exists, it presents an account creation form. Once an account exists,/setupautomatically locks and redirects to/login. - Session tokens are stored in HTTP cookies and verified securely on each server function call via
requireAdmin().
- Client Upload: Uses Cloudinary's official hosted upload widget loaded asynchronously in
useCloudinaryUpload.ts. - Upload Presets: Uses an unsigned upload preset (
portfolio) allowing direct client-to-Cloudinary image and PDF delivery. - Server Deletion:
deleteCloudinaryAssetinsrc/server/functions/cloudinary.tssigns a SHA-1 authorization hash usingCLOUDINARY_API_KEYandCLOUDINARY_API_SECRETto securely destroy replaced or deleted files.
All domain models are defined in src/db/schema.ts using Drizzle ORM:
CREATE TABLE "certifications" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
"title" text NOT NULL,
"issuer" text NOT NULL DEFAULT '',
"issue_date" text NOT NULL DEFAULT '',
"expiration_date" text,
"credential_id" text,
"credential_url" text NOT NULL DEFAULT '',
"certificate_url" text,
"certificate_public_id" text,
"skills" text[] NOT NULL DEFAULT '{}',
"description" text NOT NULL DEFAULT '',
"sort_order" integer NOT NULL DEFAULT 0,
"is_published" boolean NOT NULL DEFAULT true,
"created_at" timestamp NOT NULL DEFAULT now(),
"updated_at" timestamp NOT NULL DEFAULT now()
);user,session,account,verification: Managed by Better-Auth for authentication.profile: Full name, role title, short bio (hero), long bio (about), social links, resume URL.projects: Title, slug, descriptions, tech stack array, tags array, thumbnail URL, demo URLs, source links, sort order, and publication status.project_images: Secondary gallery images and screenshots for project detail pages.skills: Skill name, category, proficiency level, and sort order.experiences: Role, company, description, start date, end date, is current role, and sort order.resume_content: Section titles and formatted markdown/text content for the online resume page.
Create or configure a .env file in the project root:
# Database Connection (PostgreSQL or Neon Serverless)
DATABASE_URL="postgresql://user:password@host/dbname?sslmode=require"
# Better Auth Configuration
BETTER_AUTH_SECRET="your-secure-random-secret-key-at-least-32-chars"
BETTER_AUTH_URL="https://atul.ople.in" # or http://localhost:3000 in local dev
# Cloudinary Media Configuration
CLOUDINARY_CLOUD_NAME="your-cloud-name"
CLOUDINARY_UPLOAD_PRESET="portfolio"
CLOUDINARY_API_KEY="your-api-key"
CLOUDINARY_API_SECRET="your-api-secret"# Install dependencies
bun install
# Start development server with hot-module replacement
bun run dev# Run standalone migration script
bun run src/db/migrate.ts
# Or push schema directly with Drizzle Kit
bunx drizzle-kit push# Generate optimized client and SSR server bundles
bun run build
# Start production server
bun run start- Blog / Writing Engine:
- Add a
poststable with markdown/MDX support to showcase technical articles and deep dives.
- Add a
- Contact Form Email Integration:
- Currently, the contact form opens
mailto:. Integrate Resend or Nodemailer in a server function for in-page message delivery and database archiving.
- Currently, the contact form opens
- Analytics Dashboard:
- Integrate Umami or custom lightweight pageview tracking for project clicks and resume downloads.