High-performance portfolio platform built with Next.js 16, Drizzle ORM, and PostgreSQL. Features a dynamic project showcase, interactive timeline, admin dashboard, versioning system, and real-time maintenance controls.
Caution
This portfolio was built for a specific environment (Vercel deployment, Supabase database, dedicated environment variables, etc.). It is not guaranteed to work out of the box if cloned into a different setup. You will likely need to adjust parts of the code (configuration, environment variables, endpoints, etc.) to make it compatible with your own environment.
Important
This project was originally built for my own personal use, so you might come across things like custom popups or specific messages tailored for my site. I'm currently working on v7 of the portfolio, where everything will be manageable through an admin panel instead of being hardcoded directly in the codebase.
| Category | Technology |
|---|---|
| Framework | Next.js 16.2 (App Router, Turbopack) |
| Language | TypeScript 5 (strict mode) |
| Styling | Tailwind CSS v4, tw-animate-css, OKLCH color space |
| Database | PostgreSQL (via Neon serverless) |
| ORM | Drizzle ORM 0.45 |
| Auth | Custom cookie-based (bcryptjs, httpOnly sessions, 24h expiry) |
| UI Primitives | Radix UI (20+ components), shadcn/ui style |
| Icons | Lucide React |
| Fonts | Inter (sans), Outfit (display) via next/font/google |
| Notifications | Sonner |
| Deployment | Vercel (production), Codeberg (source) |
| Infrastructure | OVHcloud VPS, Nginx, PM2, Docker |
flowchart LR
subgraph Client["Client Layer"]
Browser["Browser"]
React["React 19 + RSC"]
end
subgraph NextJS["Next.js 16 App Router"]
Layout["layout.tsx β fonts, footer, popups"]
Pages["12 routes: / /about /contact /update /privacy /terms /copyright /tags-info /maintenance /[slug]/update /admin + dashboard"]
API["Server actions β CRUD projects, admins, versions, settings, site updates"]
end
subgraph Components["Components"]
Public["Public β ProjectCard, TechStack, PortfolioContent, TagFilter, VersionSelector"]
Admin["Admin β DashboardClient, Forms, Dialogs, Toggles"]
UI["UI primitives β 20 Radix + shadcn/ui components"]
end
subgraph Data["Data Layer"]
Drizzle["Drizzle ORM"]
DB[("PostgreSQL β 5 tables")]
Schema["projects Β· admins Β· settings Β· site_updates Β· versions"]
end
Client --> NextJS
NextJS --> Components
NextJS --> API
API --> Drizzle
Drizzle --> DB
Components --> UI
| Route | Type | Description |
|---|---|---|
/ |
Dynamic | Homepage β hero (badge links to /update or custom URL), project grid, tech stack marquee, CTA |
/about |
Dynamic | Developer story, philosophy cards, personal note |
/contact |
Dynamic | Contact info, availability status, email copy |
/update |
Dynamic | System roadmap with countdown + changelog history |
/[slug]/update |
Dynamic | Per-project changelog (auto: /update for drayko.xyz) |
/admin |
Dynamic | Admin login form (cookie-based session) |
/admin/dashboard |
Dynamic | Full admin panel β CRUD for all entities |
/maintenance |
Dynamic | Maintenance mode display (server-redirected) |
/privacy |
Static | Privacy policy |
/terms |
Static | Terms of service |
/copyright |
Static | Copyright & license info |
/tags-info |
Dynamic | Tag/status legend for project badges |
/sitemap.xml |
Generated | Dynamic sitemap (8 routes, weekly/monthly) |
/robots.txt |
Generated | Disallows /admin, /maintenance, /update |
erDiagram
projects {
serial id PK
text title
text slug UK
text description
text image_url
text[] tags
text project_url
text github_url
boolean in_development
text development_status
boolean is_completed
boolean is_archived
integer development_progress
jsonb changelog
timestamp created_at
}
admins {
serial id PK
text email UK
text password
timestamp created_at
}
settings {
text key PK
jsonb value
timestamp updated_at
}
site_updates {
serial id PK
timestamp next_update_date
boolean no_update_planned
jsonb planned_features
jsonb changelog
text latest_update_text
boolean show_last_update_prefix
text hero_link_type
text hero_custom_url
timestamp updated_at
}
versions {
serial id PK
text name
text description
text link
boolean is_current
timestamp created_at
}
flowchart LR
subgraph Public["Public Pages"]
Home["Home Page
page.tsx"]
About["About Page
about/page.tsx"]
Contact["Contact Page
contact/page.tsx"]
Update["Update Page
update/page.tsx"]
end
Home --> Portfolio["PortfolioContent
client component"]
Home --> Tech["TechStack
client component"]
Home --> Version["VersionSelector"]
Home --> PF["ProjectCard
(per project)"]
Portfolio --> Tag["TagFilter"]
Portfolio --> Search["Search bar"]
Tech --> TechCard["TechCard
(47 technologies, 3 marquee rows)"]
Update --> Changelog["ChangelogList"]
Update --> CountdownComponent["Countdown
(Odometer.js CDN)"]
Contact --> CopyBtn["CopyEmail
clipboard button"]
flowchart LR
subgraph Admin["Admin /dashboard"]
Dashboard["AdminDashboardClient
531-line client component"]
end
Dashboard --> Sections["Accordion Sections"]
Sections --> Section1["Projects
CRUD + cards"]
Sections --> Section3["Versions
CRUD + cards"]
Sections --> Section4["Admins
CRUD + cards"]
Sections --> Section5["Site Settings
UpdateDialog
BadgeDialog (headline + prefix + link destination)
MaintenanceToggle
AvailabilityToggle"]
Section1 --> ProjDialog["ProjectDialog"]
Section1 --> ProjForm["ProjectForm
(2 tabs: details + changelog)"]
Section1 --> ProjCard["AdminProjectCard"]
Section1 --> DeleteProj["DeleteProjectDialog"]
Section3 --> VerDialog["VersionDialog"]
Section3 --> VerForm["VersionForm"]
Section3 --> VerCard["AdminVersionCard"]
Section4 --> AdmDialog["AdminDialog"]
Section4 --> AdmForm["AdminForm"]
Section4 --> AdmCard["AdminCard"]
Section4 --> DeleteAdm["DeleteAdminDialog"]
sequenceDiagram
participant B as Browser
participant S as Next.js Server
participant D as PostgreSQL
Note over B,S: Public page request
B->>S: GET / (any page)
S->>S: checkMaintenance() + isLocalRequest()
alt Maintenance ON & not local
S->>B: 302 Redirect /maintenance
else Normal
S->>D: SELECT projects, versions, site_updates
D-->>S: data rows
S->>S: Serialize dates (toISOString)
S-->>B: Rendered HTML + RSC payload
end
Note over B,S: Admin login
B->>S: POST login (email, password)
S->>D: SELECT admin WHERE email = ?
S->>S: bcrypt.compare(password, hash)
alt Valid
S->>S: Set httpOnly cookie (24h)
S->>B: 302 Redirect /admin/dashboard
else Invalid
S-->>B: { success: false, error }
end
Note over B,S: Admin CRUD
B->>S: Server Action (create/update/delete)
S->>S: checkAdminSession()
S->>D: INSERT / UPDATE / DELETE
D-->>S: result
S->>S: revalidatePath("/")
S-->>B: { success: true, data }
- Dynamic Project Grid β Searchable, filterable by tags, responsive 3-column grid
- Project Cards β Rich status indicators (WIP, paused, finished, archived) with progress bars, live demo/code links, and update history
- Tech Stack Marquee β 47 technology icons in 3 auto-scrolling rows with hover pause
- Journey Timeline β Alternating left/right cards with Lucide icons, animated on viewport
- System Roadmap β Next-update countdown (Odometer.js) + versioned changelog
- Hero Badge β Dynamic badge with configurable link destination (update page or custom URL)
- Version Selector β Dropdown to switch between portfolio versions with old-version detection popup
- Availability Badge β Real-time "Open for projects" indicator on contact page
- Maintenance Mode β Server-side redirect with bypass for local requests
- Full CRUD β Projects, Versions, Admins
- Project Editor β Two-tab form (details + reorderable changelog)
- Site Updates β Edit roadmap, changelog, planned features, badge text, and badge link destination (update page / custom URL)
- Maintenance Toggle β Enable/disable site-wide with custom message and ETA
- Availability Toggle β "Open for projects" switch
- Version Management β Create named versions with links (e.g., v5, v6-beta)
- Cookie-based Auth β httpOnly, secure (production), sameSite=lax, 24h expiry
- Lazy DB Proxy β Postgres connection deferred until first query; mock fallback in dev without DATABASE_URL
- TypeScript Strict β Full strict mode with build-time type checking disabled
- Force-dynamic β All pages server-rendered on each request (no stale data)
- Dynamic Sitemap β Auto-generated sitemap.xml + robots.txt
- Console Easter Egg β Hidden message in browser devtools
Before: 22β30s
After cleanup: ~8β12s (estimated on Vercel)
Local build: ~3s (compilation: 2.7s)
Optimizations applied:
- Removed 28 unused dependencies (91 packages removed from
node_modules) - Moved type packages to
devDependencies typescript.ignoreBuildErrors: truenpm ci --prefer-offlineon Vercel installs- Turbopack (default in Next.js 16)
- Node.js 20+
- PostgreSQL database (or Neon serverless)
- npm
# Required
DATABASE_URL=postgresql://user:password@host:5432/db
# or
POSTGRES_URL=postgresql://user:password@host:5432/dbgit clone https://codeberg.org/ddrayko/v6-portfolio.git
cd v6-portfolio
npm install
npm run dev # http://localhost:3000npm run build
npm start # production server on :3000npm run db:push # Push schema to database
npm run db:generate # Generate migration files
npm run db:studio # Open Drizzle Studio GUISeed scripts are in scripts/:
001_create_projects_table.sqlto005_update_admin_password.sqlhash-password.jsβ generate bcrypt hashes for admin passwords
v6-portfolio/
βββ app/ # Next.js App Router pages
β βββ layout.tsx # Root layout (fonts, footer, popups)
β βββ page.tsx # Homepage
β βββ about/
β βββ contact/
β βββ update/
β βββ admin/
β β βββ page.tsx # Login
β β βββ dashboard/ # Admin panel
β βββ [slug]/update/ # Per-project changes
β βββ sitemap.ts # Dynamic sitemap
βββ components/
β βββ ui/ # 20 shadcn/ui primitives
β βββ project-card.tsx
β βββ tech-stack.tsx
β βββ portfolio-content.tsx
β βββ ... (33 custom components)
βββ db/
β βββ schema.ts # 6 Drizzle tables
β βββ index.ts # Lazy DB client
βββ lib/
β βββ actions.ts # 16 server actions
β βββ admin-auth.ts # Cookie auth system
β βββ server-utils.ts # Local request detection
β βββ types.ts # TypeScript interfaces
β βββ utils.ts # cn() helper
βββ hooks/
β βββ use-mobile.ts
β βββ use-toast.ts
βββ public/
β βββ assets/tech/ # 47 technology icons
β βββ ... # Favicon, logos
βββ scripts/ # SQL migrations + utilities
βββ next.config.mjs
βββ vercel.json
βββ package.json
This project is free software β see COPYRIGHT.md for details.