A bilingual personal portfolio for Daniel Juyung Seo, with an English/Korean public archive and a private Firebase-backed content workspace.
?lang=en or ?lang=ko and remembered on the device.The bundled fallback portrait is stored at public/seoz.jpg. Replacing that file updates the public hero while preserving the CMS and seed-data path. The hero uses a direct impact-focused promise, a neutral editorial frame, and only the broad San Francisco Bay Area rather than a precise location. Press cards use checked-in source imagery under public/press/; their companion links and bilingual labels are defined in the public page component. The Connect call-to-action sits on an elevated surface so it remains visually distinct from the cream archive canvas that follows it.
If Firebase is temporarily unavailable, slow to connect, or has not been configured yet, the public portfolio immediately uses the included seed content instead of showing an empty archive. Live records replace the fallback as they arrive. Items with missing section metadata remain visible in a bilingual fallback group. The Archive fallback is curated from Daniel’s public profile.md: talks are grouped into compact yearly digests, while career, mentoring, open-source work, research, awards, and education retain dedicated records in English and Korean.
/admin.seojuyung@gmail.com.All other Google accounts are signed out and denied. Firebase rules independently enforce the same allowlist for every database and storage mutation.
Requirements: Node.js 22.13 or newer, a Firebase project, and a Google OAuth provider enabled in Firebase Authentication.
npm install
cp .env.example .env.local
npm run dev
Fill .env.local with the Web App configuration from the seoz-com Firebase project. Browser Firebase identifiers use NEXT_PUBLIC_ because Firebase Web SDK configuration identifies the project but does not authorize access; Firestore and Storage rules are the security boundary. Never place service-account JSON, Gemini keys, or other server credentials in a NEXT_PUBLIC_ variable.
Open the local URL printed by the development server. The default port is normally 3000.
In the Firebase console:
npm run firebase:deploy
GOOGLE_APPLICATION_CREDENTIALS when it is set and otherwise uses the account from npx firebase login:npm run seed
The seed operation is idempotent for the included document IDs. It overwrites those seed records, so run it deliberately on the intended project.
| Variable | Purpose | Exposure |
|---|---|---|
NEXT_PUBLIC_FIREBASE_API_KEY |
Firebase Web App identifier | Browser |
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN |
OAuth redirect/auth domain | Browser |
NEXT_PUBLIC_FIREBASE_PROJECT_ID |
Firebase project | Browser |
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET |
Portfolio image bucket | Browser |
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID |
Firebase Web App configuration | Browser |
NEXT_PUBLIC_FIREBASE_APP_ID |
Firebase Web App identifier | Browser |
NEXT_PUBLIC_FIREBASE_DATABASE_ID |
Named Firestore database used by the web app (seozcom) |
Browser |
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID |
Optional Analytics identifier | Browser |
NEXT_PUBLIC_SITE_URL |
Canonical metadata origin | Browser/build |
FIREBASE_PROJECT_ID |
Project used by the server-side seed script | Server only |
FIREBASE_DATABASE_ID |
Named Firestore database used by the seed script (seozcom) |
Server only |
GOOGLE_APPLICATION_CREDENTIALS |
Optional local path to service-account JSON for seeding | Server only; never commit |
No Gemini integration is used. If one is added later, keep its key in Firebase Secret Manager or the hosting platform’s server-only secret store.
profile/main: bilingual identity, headline, summary, contact, portrait, and social links.timeline/{id}: bilingual activity records with dates, roles, organizations, Markdown descriptions, links, labels, images, sections, and ordering. An optional bounded highlights list adds bilingual per-line notes, images, and links while preserving the Markdown description as a compatibility fallback.sections/{id}: bilingual group names, visibility, and ordering.labels/{id}: bilingual filter labels and accessible accent colors.portfolio/*: uploaded public portfolio images.npm run lint
npm run build
Security and architecture decisions are documented in DESIGN.md.
Production uses a standard Next.js static export, served by Firebase Hosting. npm run build creates out/ with separate public and admin routes. The historical Vite/Vinext/Sites files are retained for reference and are not used by the production scripts. Tailwind runs through postcss.config.mjs.
The default project in .firebaserc is seoz-com; Firestore uses the named database seozcom. Set all required NEXT_PUBLIC_FIREBASE_* values before building, and set NEXT_PUBLIC_SITE_URL=https://seoz.com. These public values are embedded in the browser build; changing them requires a rebuild. Never commit .env.local or credential JSON.
npm ci
npx firebase login
npm run deploy:hosting
deploy:hosting runs lint, builds, then deploys only Hosting. npm start serves the exported files with the Firebase Hosting emulator. Deploy backend rules separately with npm run firebase:deploy. Do not run npm run seed during deployments: it overwrites matching seeded records.
Before DNS cutover, verify https://seoz-com.web.app and /admin/, both languages, images, Google sign-in, Firestore content, image uploads, unauthorized write denial, and the bundled content fallback. Add production hostnames to Firebase Authentication’s authorized domains.
Keep domain registration and renewal at Gabia. DNS records must be edited at the authoritative nameserver provider; registration at Gabia does not by itself make Gabia authoritative. Back up the complete current DNS zone and Cafe24 files/database before retiring services. Preserve mail and verification records, including MX, SPF, DKIM, and DMARC.
If moving DNS to Gabia, first copy the full existing zone with the old website destination, then change the nameservers and verify resolution. Keep any DNSSEC delegation consistent with the new provider. Do not cancel Cafe24 if DNS or email still depends on it.
In Firebase Hosting, connect seoz.com using Advanced Setup, complete the ownership and certificate challenges, and wait for certificate readiness before changing web traffic records. Follow the current console’s exact DNS values; challenge tokens can change. Keep the ownership TXT record for renewal. Connect www.seoz.com separately and redirect it to seoz.com. Firebase manages HTTPS certificates.
Verify HTTP-to-HTTPS behavior, both hostnames, direct /admin/ navigation, sign-in, HTTPS-only assets, old URLs, and email after cutover. Keep Cafe24 and old DNS values available for rollback during propagation. Use Firebase Hosting release history to roll back application releases. Cancel old hosting only after checking its remaining DNS, mail, and file dependencies.