oguzkaganhan.com
Bilingual portfolio that syncs GitHub repositories and lets every section be edited from an admin panel. Next.js 16, Supabase, Turborepo, Bun.
README
oguzkaganhan.com
Bilingual (TR/EN) personal portfolio with an admin panel. Public GitHub repositories are synced automatically, projects can also be added by hand, and every section of the site is edited, reordered and toggled from the admin.
Structure
apps/web Public site (Next.js 16, next-intl) :3000
apps/admin Admin panel (Next.js 16, shadcn/ui, GitHub login) :3001
packages/content zod schemas + locale helpers shared by both apps
packages/db Supabase clients, generated types, queries
packages/ui Design tokens (Tailwind v4) shared by both apps
supabase/ config, migrations, seed, github-sync edge function
docs/supabase-local.md What each local Docker container does
Local setup
Requirements: Bun 1.3+, Docker Desktop.
bun install
bun run db:start # local Supabase in Docker (see docs/supabase-local.md)
-
Copy env files and fill in values from
bunx supabase status:apps/web/.env.example→apps/web/.env.localapps/admin/.env.example→apps/admin/.env.local(sameREVALIDATE_SECRETas web)supabase/functions/.env.example→supabase/functions/.env
-
Admin login needs a GitHub OAuth App (callback
http://127.0.0.1:54321/auth/v1/callback). Put its id/secret insupabase/.env(seesupabase/.env.example), thenbun run db:stop && bun run db:start. Only the GitHub account inprivate.admin_allowlistcan sign up (see the init migration). -
Let the cron job reach the sync function (once per database reset):
select vault.create_secret('http://supabase_kong_oguzkaganhan:8000', 'project_url'); select vault.create_secret('<SYNC_SECRET from supabase/functions/.env>', 'github_sync_secret');
bun run dev # both apps
bun run db:reset # re-apply migrations + seed.sql
bun run db:types # regenerate packages/db/src/database.types.ts after schema changes
Checks
bun run lint # Biome
bun run typecheck
bun run test # bun test (schemas, project resolution, GitHub mapping)
bun run build
How content reaches the site
Pages read Supabase through a fetch cache tagged with table names (apps/web/lib/data.ts). The admin's server actions and the GitHub sync call POST /api/revalidate with those tags after writing, so a save shows up on the next page load without a redeploy.