When the Codes Meet the Vibes 🚀
VibeDev ID adalah komunitas vibrant developer, AI enthusiasts, dan tech innovators Indonesia yang punya visi dan passion yang sama untuk bikin produk digital yang keren. Kami menghubungkan creator yang sepikiran untuk kolaborasi, belajar, dan berkembang bareng.
Indonesia's premier community for developers, vibe coders, and AI enthusiasts. Showcase projects, collaborate, network, and level up your skills in web, mobile, and AI development.
- 🔐 User Authentication - Secure login with Better Auth (email + Google/GitHub OAuth)
- 👤 Developer Profiles - Customizable profiles with bio, skills, and social links
- 📝 Project Showcase - Share dan showcase project keren lo
- 💬 Community Interaction - Comments, likes, dan diskusi project
- 🤝 Networking & Collaboration - Connect sama developer yang sepikiran
- 📰 Blog System - Rich text editor dengan Novel/TipTap untuk artikel teknis
- 📊 Views Tracking - Session-based analytics untuk project insights
- 🤖 AI Leaderboard - Ranking dan showcase AI tools favorit komunitas
- 🗓️ Community Calendar - Event dan activity tracker
- 🎉 Events System - Submit dan browse community events dengan approval workflow
- 📈 User Dashboard - Personal dashboard untuk manage projects dan activity
- 🛠️ Admin Dashboard - Full admin panel dengan moderation tools
- ⌨️ Command Palette - Quick navigation dan search (cmdk)
- 🎬 Vibe Videos - Video content section untuk tutorial dan highlights
- 🌙 Dark/Light Mode - UI theme yang nyaman mata
- 📱 Responsive Design - Perfect di semua device
- 🏷️ Project Categories - Personal Web, SaaS, Landing Page, dan lainnya
- ❤️ Like System - Like project yang lo suka
- 🔍 Discovery - Filter dan cari project berdasarkan kategori
- 🖼️ Progressive Image Loading - Blur placeholders dengan lazy loading
- 🌍 Internationalization - Full support English dan Indonesia (react-i18next)
- 🛡️ Spam Protection - Email domain whitelist dan bot protection
- 📊 Analytics Dashboard - Charts dan data visualization (recharts)
- ❓ FAQ System - Frequently asked questions management
- 🚨 Content Moderation - Report dan moderation system
- 🔎 SEO - SSR meta/Open Graph, JSON-LD, dynamic sitemap, robots.txt, canonical, dan
noindexdi halaman privat
- Framework: TanStack Start (Vite + Nitro) with
@tanstack/react-routerfile-based routing - Build/Dev: Vite 8 + Nitro server output
- Language: TypeScript 5.x
- Database: Neon Postgres (Drizzle ORM) — migrated from Supabase. All server data access uses Drizzle via
getDb()with Better Auth session checks (requireUser,requireAdminOrModeratorUser). One-time Supabase → Neon scripts live inscripts/migrate-to-neon.ts. - Authentication: Better Auth (
/api/auth/*) with email/password + Google/GitHub OAuth - Styling: Tailwind CSS v4
- UI Components: Radix UI + shadcn/ui (50+ components)
- Animations: Motion (Framer Motion) — shared scroll-reveal primitives in
components/ui/motion-wrapper.tsx(ScrollReveal,StaggerContainer/StaggerItem,ScaleIn) applied across the landing page and the/project/list,/blog,/event/list, and/[username]profile pages (header/stats entrance reveal, staggered project/blog card grids, scaled tab-content reveals); Radix dropdown menus (e.g. avatar menu) and the FAQ accordion animate open/close via Framer Motion, all withprefers-reduced-motionsupport - Rich Text: Novel + TipTap
- Icons: Lucide React + Tabler Icons + LobeHub Icons
- Internationalization: react-i18next
- Forms: React Hook Form + Zod
- AI Integration: AI SDK + OpenRouter Provider
- File Uploads: UploadThing + Better Upload
- Command Palette: cmdk
- Charts: Recharts
- Dates: date-fns + date-fns-tz
- Testing: Playwright (E2E) + Vitest (unit)
- Code Quality: Biome (linter + formatter)
- Hosting: Cloudflare Workers (Nitro
cloudflare_modulepreset) - Toast: Sonner
Note: this app was migrated from Next.js 16 App Router to TanStack Start. Some
app/subfolders still use Next.js-style names but are now plain view/component modules imported by route files inapp/routes/.
- Node.js 18+ or Bun (recommended)
- Vite+ (
vpCLI) — this project uses the Vite+ unified toolchain - A Neon Postgres database
- Better Auth OAuth apps (Google + GitHub) for social login
- Clone the repository:
git clone https://github.com/yourusername/vibedevid_v0.git
cd vibedevid_v0- Install dependencies:
vp installVite+ ships
vite/vitestindevDependenciesas npm aliases to@voidzero-dev/vite-plus-core/@voidzero-dev/vite-plus-test, and theoverridesfield inpackage.jsonpins both to those same alias specs. Thevitestoverride must match thedevDependencies.vitestspec exactly (i.e. thenpm:@voidzero-dev/vite-plus-test@latestalias, not a plain version like4.1.9). Bun tolerates a mismatch, but npm/npx(used bynpx wrangler deployon Cloudflare) rejects it withEOVERRIDE: Override for vitest conflicts with direct dependency. If you upgradevite-plusandvp installchanges the alias, re-syncoverrides.vitestto matchdevDependencies.vitest. Do not route this pin through acatalog:reference.
- Set up environment variables:
cp .env.example .env.local- Update
.env.local:
DATABASE_URL=postgresql://user:pass@ep-xxx.neon.tech/neondb?sslmode=require
BETTER_AUTH_SECRET=your-secret-min-32-chars
BETTER_AUTH_URL=http://localhost:3000
VITE_BETTER_AUTH_URL=http://localhost:3000
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
GITHUB_CLIENT_ID=...
GITHUB_CLIENT_SECRET=...
UPLOADTHING_TOKEN=your-uploadthing-token-here
NEXT_PUBLIC_SITE_URL=http://localhost:3000
VITE_SITE_URL=http://localhost:3000
OPENROUTER_API_KEY=sk-or-v1-...
RESEND_API_KEY=re_...
EMAIL_FROM=noreply@yourdomain.com- Set up the database:
bun run migrate:schema # first-time Neon schema
# Migrating from Supabase (set SUPABASE_DB_URL, SUPABASE_URL, SUPABASE_ANON_KEY in .env.local):
bun run migrate:staging -- --force # auth.users → staging (destructive staging reset)
bun run migrate:users # staging → Better Auth
bun run migrate:data -- --force # public tables (destructive target reset)
bun run migrate:verify -- --fail-on-mismatch
bun run migrate:status # checkpoints + Neon counts
# Or: bun run migrate:run # schema → staging → users → data → verify, skipping checkpointsSee docs/migrations/neon-better-auth.md for the full migration guide.
- Run the development server:
bun run dev
# or
npm run dev
# or
pnpm dev- Open http://localhost:3000 in your browser.
# Install dependencies
vp install
# Development server (vite dev, port 3000)
bun run dev
# Build for production (vite build — Nitro server output in .output/)
# Pre-generates responsive AVIF/WebP image variants via scripts/optimize-images.mjs first
bun run build
# Regenerate optimized image variants only (public/optimized/)
bun run optimize:images
# Recompress existing UploadThing project/video images (dry run by default;
# add --apply to write). Needs DATABASE_URL + UPLOADTHING_TOKEN.
bun run backfill:images
# Start the production server
bun run start
# Type checking (required for type safety)
bunx tsc --noEmit
# Linting & Formatting (Biome)
bun run lint # changed files only
bun run lint:all # full repo
bun run format
# Unit tests (Vitest)
bun run test
bun run test:watch
# E2E tests (Playwright)
bun run test:e2e
bun run test:e2e:headed # see browser
bun run test:e2e:debug # step through
# Run a single Playwright spec / test by name
bunx playwright test tests/views-tracking.spec.ts
bunx playwright test -g "should track views when visiting project page"| Variable | Description | Required |
|---|---|---|
DATABASE_URL |
Neon Postgres connection string (pooled) | Yes |
BETTER_AUTH_SECRET |
Random secret for Better Auth (min 32 chars) | Yes |
BETTER_AUTH_URL |
Public app URL for auth callbacks | Yes |
VITE_BETTER_AUTH_URL |
Same URL, exposed to browser | Yes |
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET |
Google OAuth | Yes |
GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET |
GitHub OAuth | Yes |
UPLOADTHING_TOKEN |
UploadThing API token (keep secret!) | Yes |
NEXT_PUBLIC_SITE_URL |
Canonical site URL (server + SEO) | Yes |
VITE_SITE_URL |
Same URL, exposed to browser | Yes |
OPENROUTER_API_KEY |
AI blog features | Yes |
RESEND_API_KEY |
Resend API key for auth verification/reset | Yes |
EMAIL_FROM |
Verified sender address for Resend | Yes |
The app deploys to Cloudflare Workers using the Nitro cloudflare_module preset
(configured in vite.config.ts) plus wrangler.jsonc at the repo root.
Production site: https://vibedevid.com.
# 1. Build with production client URLs baked in (VITE_* are compile-time)
NEXT_PUBLIC_SITE_URL=https://vibedevid.com \
VITE_SITE_URL=https://vibedevid.com \
VITE_BETTER_AUTH_URL=https://vibedevid.com \
bun run build
# 2. Preview locally on Workers runtime (copy .env.local → .dev.vars)
bunx wrangler dev
# 3. Set Worker secrets (runtime — see list below), then deploy
bun run scripts/sync-wrangler-secrets.ts # reads .env.local → wrangler secret bulk
bunx wrangler deployNotes:
compatibility_flags: ["nodejs_compat"]and compatibility date2024-09-19(required for Workers Static Assets) are set inwrangler.jsonc.- Server-only secrets must be read via
getServerRuntimeSecrets()(lib/server/runtime-secrets.ts), notprocess.envdirectly. On Workers, bindings are exposed onglobalThis.__env__per request. VITE_*values are inlined at build time — rebuild before deploy wheneverVITE_BETTER_AUTH_URLorVITE_SITE_URLchanges.- Worker secrets (runtime):
DATABASE_URL,BETTER_AUTH_SECRET,BETTER_AUTH_URL,GOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRET,GITHUB_CLIENT_ID,GITHUB_CLIENT_SECRET,NEXT_PUBLIC_SITE_URL,UPLOADTHING_TOKEN,OPENROUTER_API_KEY,RESEND_API_KEY, andEMAIL_FROM. - For
wrangler dev, create a gitignored.dev.varsfile with the same keys. - Add OAuth redirect URLs in Google/GitHub developer consoles:
https://vibedevid.com/api/auth/callback/googlehttps://vibedevid.com/api/auth/callback/github
- After cutover, remove legacy Supabase secrets from the Worker if still present.
users - Extended auth profiles with role system (0=admin, 1=moderator, 2=user), social links, bio, and location
projects - Project showcase with slug-based URLs for SEO, tags, and category
comments - Unified comments system for both Blog and Projects
likes - User likes with unique constraint (one like per user per project/post)
views - Session-based views tracking with 30-minute timeout, IP + User Agent fingerprinting
posts - Blog posts with rich text content (JSON), featured flag, read time
post_tags - Blog tag categorization
blog_post_tags - Many-to-many relationship between posts and tags
events - Community events with approval workflow, status (upcoming/past), location details
categories - Project categories with icon and color
faqs - FAQ content with sort order and active status
blog_reports - Comment/report moderation system
vibe_videos - Video content for tutorials and highlights
- Authorization enforced in application code (Drizzle queries +
requireUser/requireAdminOrModeratorUser) — no Supabase RLS on Neon - Authenticated insert/update untuk data milik user sendiri
- Guest comments diizinkan dengan
author_namefield - Admin/moderator role diperlukan untuk moderation
- Email domain whitelist untuk registration
├── app/
│ ├── routes/ # TanStack Router file-based routes (source of truth)
│ │ ├── __root.tsx # Root layout, head/meta, providers
│ │ ├── index.tsx # Homepage
│ │ ├── $username.tsx # Dynamic user profile pages
│ │ ├── project.list.tsx / project.$slug.tsx / project.submit.tsx
│ │ ├── blog.tsx (layout) / blog.index.tsx / blog.$slug.tsx / blog.editor*.tsx
│ │ ├── event.list.tsx / event.$slug.tsx
│ │ ├── _admin/ # Protected admin route group (role-gated layout)
│ │ ├── admin.tsx # Standalone admin page (role-gated via beforeLoad)
│ │ ├── api/ # API route handlers (server.handlers blocks)
│ │ └── auth.callback.ts
│ ├── routeTree.gen.ts # Generated route tree (do not hand-edit)
│ ├── router.tsx # Router factory
│ ├── start.ts # TanStack Start instance + request middleware
│ └── <feature>/ # Legacy Next.js-named folders, now view/component
│ # modules imported by routes (blog, project,
│ # event, [username], (admin), user/auth, ...)
├── components/
│ ├── ui/ # 50+ shadcn/ui components
│ ├── sections/ # Page sections (hero, showcase, faq)
│ ├── blog/ # Blog-specific components
│ ├── project/ # Project-specific components
│ ├── admin-panel/ # Admin dashboard components
│ ├── event/ # Event-specific components
│ └── profile/ # Profile-specific components
├── hooks/ # Custom React hooks
├── lib/
│ ├── actions/ # Server data/mutations + *.functions.ts (createServerFn)
│ ├── db/ # Drizzle schema + `getDb()` (Neon serverless)
│ ├── auth/ # Better Auth server/client config
│ ├── server/ # Server-only utilities (auth, runtime secrets)
│ ├── routes/ # Route helpers (server locale/translations)
│ ├── uploadthing.ts # UploadThing server router
│ ├── uploadthing-client.ts # Client upload helpers
│ ├── uploadthing-router.ts # Client-safe router types
│ └── ai/ # AI integration (OpenRouter)
├── i18n/ # react-i18next config (index.ts, routing.ts)
├── types/ # TypeScript type definitions
├── scripts/ # Database migrations (20+ SQL files)
├── tests/ # Vitest unit tests + Playwright E2E tests
├── messages/ # i18n messages (en.json, id.json)
├── docs/ # Documentation (security, database, deployment)
├── biome.json # Biome configuration
├── vite.config.ts # Vite + TanStack Start + Nitro configuration
└── tsconfig.json # TypeScript configuration
Project list (/project/list) dan homepage memakai fetchProjectsWithSorting
(lib/actions.ts) lewat server function fetchProjectsWithSortingFn.
Filter kategori bersifat resilient terhadap dua representasi nilai yang
tersimpan di kolom projects.category:
- Project baru menyimpan category
name(slug, mis.landing-page). - Project lama / seed menyimpan display text (mis.
Landing Page).
Nilai filter dari UI di-resolve ke kedua bentuk lalu dimatch dengan in(...),
sehingga semua project pada satu kategori tetap muncul.
Sorting tersedia dalam tiga mode:
newest— urutcreated_atdesc (limit langsung di SQL). Default.top— all-time best, murni berdasarkan total likes (tiebreak terbaru).trending— likes diberi bobot recency (likes / umur-hari).
Untuk top/trending, likes dihitung terpisah dan tidak bisa di-order di
SQL, jadi query mengambil candidate window yang lebih lebar dulu, lalu sort +
truncate ke limit di JS agar project lama dengan banyak likes tidak terpotong.
Pilihan filter & sort di UI tidak mengubah URL. Nilai awal tetap di-seed dari search params saat load pertama (deep link tetap jalan), tapi mengganti dropdown setelahnya hanya mengubah state lokal tanpa menyentuh query string.
Centralized comments component yang works untuk both Blog dan Project.
import { CommentSection } from "@/components/ui/comment-section";
<CommentSection
entityType="post" // or "project"
entityId={id}
initialComments={comments}
isLoggedIn={!!user}
/>;Session-based analytics dengan:
- 30-minute session timeout
- IP + User Agent fingerprinting
- Bot protection (user agent filtering)
- Unique visitor counting
- SVG blur placeholders
- Intersection Observer lazy loading
- AVIF/WebP automatic optimization
- Client-safe processing (no sharp in client bundle)
Homepage performance is tuned for Core Web Vitals (LCP/TBT):
- Build-time responsive images -
scripts/optimize-images.mjs(sharp) pre-generates AVIF + WebP variants of large public images intopublic/optimized/at multiple widths. Runs automatically beforebun run build. The hero (the LCP element) drops from a ~660KB 2880×1800 PNG to ~90KB at its 1200px breakpoint. OptimizedImagecomponent (components/ui/optimized-image.tsx) - Renders a<picture>with AVIF/WebPsrcsetpointing at the generated variants. The hero usespriority(eager load +fetchpriority="high") and is preloaded in the home routehead().- Right-sized remote avatars - GitHub avatars request
?s=64; testimonial avatars use the 128px optimized variants instead of full-size source PNGs. - Code-split below-the-fold sections - The homepage lazy-loads non-critical sections (video showcase, community features, AI tools, reviews, FAQ, CTA, footer) with
React.lazy+Suspenseso they no longer block initial hydration (reduces Total Blocking Time). - Long-lived asset caching -
routeRulesinvite.config.tsemitcache-controlheaders (written to the generated.output/public/_headers) for/optimized/*and/fonts/*(immutable). Only non-overlapping directory rules are used: Cloudflare_headerswildcards match across/and concatenate every matching rule's value, so broad extension rules like/*.avifare avoided (they would corrupt the/optimized/*header). - Faster server response (TTFB) - The homepage previously made several redundant per-request DB/auth roundtrips.
getServerSession()(lib/server/auth.ts) is now memoized per request (keyed on the request object via aWeakMap), so the session resolves once instead of being re-fetched by the rootbeforeLoad, route loaders, andgetBatchLikeStatus. The home route loader also reuses the user already resolved in the rootbeforeLoadinstead of re-querying it, and verbose per-requestconsole.logcalls in the hot data path were removed. - Client-side upload compression - User-uploaded project images go through
lib/image-compression.ts(onBeforeUploadBeginon the UploadThing buttons), which downscales to 1600px and re-encodes to WebP in the browser before upload. UploadThing (ufs.sh) has no on-the-fly resizing andsharpcannot run on Cloudflare Workers, so compressing client-side is the only no-cost option — it typically turns a 380KB+ upload into ~80-120KB. Falls back to the original file if compression fails or does not reduce size. - Backfill for existing images -
scripts/backfill-image-compression.ts(run withbun run backfill:images, or--applyto write) recompresses already-uploaded project/video images: download → WebP via sharp → re-upload to UploadThing → update the DB row → delete the old file. Runs on Node/Bun (needsDATABASE_URL+UPLOADTHING_TOKEN), defaults to a dry run. Use this once to fix images uploaded before client-side compression was added.
Search-engine optimization is handled at the route level:
- Server-rendered meta - Per-route
head()blocks emit title, description, Open Graph, and Twitter Card tags (rendered in SSR HTML, verifiable with a Googlebot user-agent). - Structured data - Organization + WebSite JSON-LD in the root route.
- Dynamic sitemap -
app/routes/sitemap[.]xml.tsqueries Neon (Drizzle) for published posts, projects, approved events, and public profiles, plus static routes. Auth-gated pages are excluded;lastmoduses real content timestamps with a fallback. - robots.txt -
app/routes/robots[.]txt.tsserves a singleUser-agent: *group, disallows private/API paths, and references the sitemap. Note: if Cloudflare's managed robots.txt is enabled it will shadow this route — keep only one source of truth. - Canonical URLs - Self-referencing canonicals on content and list pages; the homepage and
/project/listconsolidate?filter/?sortvariants onto their clean URLs. - noindex - Admin, dashboard, blog editor, project submit, and auth routes emit
robots: noindex, nofollowvia the sharedNOINDEX_METAhelper inlib/seo/site-url.ts. - Dynamic OG image -
app/routes/api/og.tsrenders a branded 1200×630 SVG from atitlequery param (dependency-free, Cloudflare Workers-safe). - LCP-friendly images - Below-the-fold images use
loading="lazy"; preload is reserved for the hero/logo (above the fold).
Registration dibatasi ke domain terpercaya:
- gmail.com, yahoo.com, outlook.com
- dan 20+ domain edukasi/tech lain
Built-in AI features using OpenRouter:
- AI-powered content suggestions
- Leaderboard for AI tool rankings
- Integration with Agentic workflow
Full-featured events management:
- Event submission with approval workflow
- Status tracking (upcoming, past)
- Location types (online, offline, hybrid)
- Organizer profiles
- Cover image support
Comprehensive admin tools:
- User management (role: admin, moderator, user)
- Content moderation (comments, reports)
- Event approval workflow
- Analytics dashboard
This project includes AI agent skills for enhanced development:
- motion-design - Motion design principles for emotionally-driven, technically sound animations
See .agents/skills/ for workspace skills.
Kami welcome kontribusi dari semua developer! 🎉
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Test thoroughly
- Commit your changes (
git commit -m 'feat: Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Submit a pull request
- Follow TypeScript strict mode
- Use Tailwind CSS for styling
- Biome will auto-format on commit
- Write descriptive commit messages (Conventional Commits)
- Test your changes on both desktop and mobile
- Maintain the informal but professional Indonesian tone in UI copy
- 2-space indentation, no semicolons, single quotes
- Biome enforces unified linting + formatting
@/prefix untuk absolute imports- Group imports: React → Third-party → Internal
For detailed security documentation, see:
This project is licensed under the MIT License.
Built with ❤️ by VibeDev ID Community