Website portfolio pribadi dengan CMS dashboard admin, dibangun dengan SolidStart 2.0.0-alpha.2.
| Layer | Teknologi |
|---|---|
| Framework | SolidStart 2.0.0-alpha.2 + @solidjs/vite-plugin-nitro-2 |
| Database | MariaDB via Prisma 7.8.0 + @prisma/adapter-mariadb |
| Styling | Tailwind CSS v4 (custom token via @theme {}) |
| Auth | JWT (jose) + argon2 password hashing |
| Runtime | Bun >= 1.1.0 |
| Validasi | Zod 4.4.3 (semua server action) |
| Icon | Solid Icons (solid-icons) |
| Animasi | GSAP 3.15.0 + ScrollTrigger |
src/
middleware/ → auth guard + security headers
index.ts → entry (didaftarkan di vite.config.ts)
auth.ts → adminAuthMW — blok /dashboard/** tanpa session
security.ts → securityHeadersMW — CSP, X-Frame, dll
lib/
client/ → kode browser-only (NProgress)
server/ → kode server-only: session, assets, github
shared/ → shared helpers + Zod validation schemas
server/
db/
client.ts → Prisma singleton dengan @prisma/adapter-mariadb
portfolio.ts → query data landing page + pencarian server-side
dashboard.ts → query-query halaman dashboard
contact.ts → query data pesan kontak
actions/ → semua server action (mutasi database) dengan Zod validation
features/
landing/ → section homepage (Hero, About, Education, Experience, Projects, Volunteering, GitHubStats, Contact)
dashboard/ → komponen dashboard (Layout, Sidebar, FileUpload)
components/
ui/ → Button, Card, Skeleton, LazyAsset, ConfirmModal
form/ → FormField, Input, Textarea, Select, CustomSelect, SaveStatus
shared/ → Header (dark mode toggle), Footer, ScrollToTop
routes/
index.tsx → halaman utama portfolio (lazy loaded sections dengan ambient gradient)
login.tsx → login admin
[...404].tsx → 404 catch-all
projects/index.tsx → halaman semua proyek + server-side debounced search & custom select
experience/index.tsx → halaman semua pengalaman kerja + server-side debounced search
volunteering/index.tsx → halaman semua kegiatan volunteering + server-side debounced search
dashboard/ → 8 halaman CRUD (profile, education, experience, projects, volunteering, assets, contact)
stores/
profile.ts → ProfileContext, useProfileMeta(), buildTitle()
auth.ts → AuthContext, useSessionUser() stub
providers.tsx → ProfileProvider (context wrapper di app.tsx)
types/
github.ts → ContribDay, GithubStats interfaces
index.ts → re-export semua shared types
server.mjs → production HTTP server (melayani dist/client + uploads dari UPLOAD_DIR)
cp .env.example .envEdit .env:
DATABASE_URL="mysql://user:password@localhost:3306/portfolio_db"
JWT_SECRET="random-secret-minimal-32-karakter"
ADMIN_EMAIL="email@anda.com"
ADMIN_PASSWORD="password-kuat"
GITHUB_USERNAME="username-github" # opsional
GITHUB_TOKEN="ghp_yourtoken" # opsional, untuk statistik kontribusi
UPLOAD_DIR="/var/www/portfolio/uploads" # opsional; default: public/uploads di root projectbun installbun run db:generate # generate Prisma client
bun run db:push # buat tabel dari schema
bun run db:seed # buat akun adminbun run dev # development
bun run build # production build
bun run start # production serverSolidStart membangun output statis ke dist/client/ dan SSR ke dist/server/. server.mjs melayani keduanya. File upload disimpan di luar dist/ agar tidak terhapus saat redeploy.
Upload via dashboard → server action → UPLOAD_DIR
(default: <project-root>/public/uploads/)
Browser request /uploads/<file> → server.mjs → baca dari UPLOAD_DIR
Penting: Jangan arahkan UPLOAD_DIR ke dist/client/uploads/ karena folder dist/ dihapus setiap build. Gunakan path absolut di luar project, misalnya /var/www/portfolio/uploads.
[Unit]
Description=Portfolio SolidStart
After=network.target
[Service]
Type=simple
WorkingDirectory=/var/www/portfolio
ExecStart=/usr/bin/bun run server.mjs
Restart=on-failure
Environment=NODE_ENV=production
Environment=PORT=3000
Environment=UPLOAD_DIR=/var/www/portfolio/uploads
EnvironmentFile=/var/www/portfolio/.env
[Install]
WantedBy=multi-user.targetBuat folder upload dan pastikan writable oleh user service:
mkdir -p /var/www/portfolio/uploads
chown -R www-data:www-data /var/www/portfolio/uploads- SEO lengkap: meta tag per halaman, og:type, twitter:card, canonical URL
- Dark mode: toggle cahaya/gelap, disimpan ke localStorage, tanpa flash saat load
- GSAP animations: animasi scroll-triggered di semua section landing
- Code splitting: lazy loading untuk section below-fold (Experience, Projects, dll)
- Custom scrollbar: desain tipis sesuai brand
- Scroll to top: tombol muncul setelah scroll 400px
- Contact form: pesan dari visitor tersimpan di database, divalidasi Zod
- Halaman detail:
/projects,/experience,/volunteeringdengan data lengkap - Server-side Search & Filter: pencarian debounced (300ms) dan filter dropdown kustom (
CustomSelect) diolah langsung oleh server/database untuk performa maksimal - Responsive: mobile-first, mobile hamburger header
- Ambient Glow: gradien latar belakang bergaya premium dengan pendaran cahaya oranye lembut (
#ff6b00) yang dinamis menyesuaikan mode gelap/terang
- CRUD penuh: Profil, Pendidikan, Pengalaman, Proyek, Volunteering, Asset, dan Pesan Masuk
- Upload file:
uploadAssetActionserver action,fs.writeFile(), validasi tipe/ukuran, max 10MB - Solid Icons: ikon modern dan konsisten menggunakan library
solid-icons/tb - Mobile sidebar: drawer halus dengan animasi transisi CSS (
transform&opacity) - Full-width & Statik Sidebar: tata letak desktop didesain lebar penuh dengan sidebar statik agar navigasi tetap kokoh
- Confirm Modal: dialog konfirmasi kustom (
ConfirmModal) untuk semua aksi penghapusan data dan logout
- Server actions only: tidak ada API route publik, semua mutasi via
action() - Zod validation: validasi di layer server action, bukan hanya client
- CSP:
Content-Security-Policydengan'unsafe-inline'(kompatibel SolidStart hydration) - HttpOnly cookie: sesi JWT tidak bisa diakses JavaScript
- argon2id: hashing password dengan memory cost tinggi (64MB)
bun run db:generate # generate Prisma client setelah ubah schema
bun run db:push # push schema (development, tanpa migration history)
bun run db:migrate # buat migration file (production)
bun run db:seed # buat/reset admin user dari .env
bun run db:studio # buka Prisma Studio di browser- Prisma 7: koneksi di
prisma.config.ts(CLI) dansrc/server/db/client.ts(runtime).engineType = "library"dischema.prisma. Adapter:@prisma/adapter-mariadb. - SolidStart v2 alpha: konfigurasi di
vite.config.ts, bukanapp.config.ts. - Middleware: registrasi path
./src/middleware/index.tsdivite.config.ts. - Password hashing:
argon2(bukanBun.password) — typeargon2id, memoryCost 64MB. - File upload:
uploadAsset()di~/lib/server/assets.tstulis keUPLOAD_DIR(defaultpublic/uploads/).server.mjsmelayani/uploads/<file>langsung dariUPLOAD_DIR— tidak bergantung padadist/client/. - Tailwind v4: variabel CSS custom via
@theme {}diapp.css, bukantailwind.config.js. - Global stores:
ProfileProviderdiapp.tsxmenyediakan data profil ke seluruh route via context; menghindari flash judul pada client-side navigation. - Query deduplication: setiap page memanggil
getProfileMeta()diroute.preload; SolidStart query cache men-deduplikasi agar tidak ada request ganda.