Personal site and CMS for Brandon Soong: public identity pages, writing, projects, resume, and a password-protected admin that publishes without a redeploy.
- Node.js 20+
- Docker (for local Postgres)
docker run -d --name portfolio-pg \
-e POSTGRES_USER=postgres \
-e POSTGRES_PASSWORD=postgres \
-e POSTGRES_DB=portfolio \
-p 54329:5432 \
postgres:16If the container already exists: docker start portfolio-pg.
cp .env.example .envSet at least:
| Variable | Notes |
|---|---|
DATABASE_URL |
postgresql://postgres:postgres@localhost:54329/portfolio |
AUTH_SECRET |
openssl rand -base64 32 |
PREVIEW_SECRET |
openssl rand -hex 32 |
ADMIN_EMAIL |
Sign-in email |
ADMIN_PASSWORD_HASH |
From npm run hash-password -- "your-password" |
NEXT_PUBLIC_SITE_URL |
http://localhost:3000 locally |
Next.js expands $VAR in .env files, which mangles bcrypt hashes. Escape every $ as \$. The hash-password script prints that escaped form for .env and the raw hash for the Vercel dashboard.
When DATABASE_URL is local Postgres, leave BLOB_READ_WRITE_TOKEN empty so uploads go to ./.uploads. When it is production Neon, set the Blob token (and re-upload the resume once) so /resume.pdf is not a missing local path.
scripts/ is local-only and not in git. A fresh clone will not include seed, hash-password, or content dumps until those files are copied onto the machine. package.json still points at them (db:seed, hash-password).
npm install
npx drizzle-kit migrate # or: npm run db:migrate
npm run db:seed
npm run devProduction-style check:
npm run build
npm startOpen http://localhost:3000. Admin is at /admin.
After seeding with the committed .env defaults used in development:
- Email:
bsoong99@gmail.com - Password:
admin1234
Change these before deploying. The seed script wipes existing CMS rows and reloads from local scripts/ (not in git) — apply npm run db:migrate and npm run db:seed (ALLOW_DB_SEED=true when the URL is not localhost) against the target database before prod.
| Script | Purpose |
|---|---|
npm run db:generate |
Generate a Drizzle migration from schema changes |
npm run db:migrate |
Apply migrations (commit the SQL; execute against the target DB before prod) |
npm run db:seed |
Reset and load local scripts/ dumps (ALLOW_DB_SEED=true for non-local DBs; directory is gitignored) |
npm run hash-password -- "…" |
Print bcrypt hash (escaped + raw) |
Next.js (App Router) · PostgreSQL · Drizzle ORM · Auth.js credentials · Tailwind CSS v4 · Vercel Analytics