feat: rework starter kit for self-hosted deploy (Vite/React + Fastify/Drizzle/Postgres)
Replace Next.js/Supabase/Vercel stack with a self-hostable setup: Vite+React frontend, Fastify+Drizzle+PostgreSQL backend with own JWT auth, deploy via git pull + rsync + systemd (checkout/service separated, updates pulled from Gitea). Adds a reference auth route (register/login/me) as the canonical pattern for new routes. Updates all skills/rules/agents/docs to match. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BMdBJfD9TT6XUU2NqGG1PN
This commit is contained in:
1 parent
a893084a88
commit
4a19335a1e
99 files changed
+8898
-9835
No files matched your search
@@ -15,21 +15,23 @@
|
||||
// Bad - unoptimized, no lazy loading
|
||||
<img src="/large-image.jpg" />
|
||||
|
||||
// Good - Next.js Image component
|
||||
import Image from 'next/image'
|
||||
<Image src="/large-image.jpg" width={800} height={600} alt="Description" />
|
||||
// Good - explicit dimensions + native lazy loading
|
||||
<img src="/large-image.jpg" width={800} height={600} loading="lazy" alt="Description" />
|
||||
```
|
||||
Next.js Image automatically: resizes, lazy-loads, serves WebP format.
|
||||
Pre-resize/compress source images (e.g. with `sharp` in a build step, or an image CDN) — Vite does not do this automatically like Next.js Image did.
|
||||
|
||||
### Large JavaScript Bundle
|
||||
Use dynamic imports for heavy components that aren't needed on initial load:
|
||||
```tsx
|
||||
import dynamic from 'next/dynamic'
|
||||
import { lazy, Suspense } from "react";
|
||||
|
||||
const HeavyChart = dynamic(() => import('./HeavyChart'), {
|
||||
loading: () => <p>Loading chart...</p>,
|
||||
})
|
||||
const HeavyChart = lazy(() => import("./HeavyChart"));
|
||||
|
||||
<Suspense fallback={<p>Loading chart...</p>}>
|
||||
<HeavyChart />
|
||||
</Suspense>
|
||||
```
|
||||
Vite code-splits dynamic imports automatically — check `npm run build`'s output chunk sizes.
|
||||
|
||||
### Missing Loading States
|
||||
Always show feedback during data fetching:
|
||||
@@ -41,27 +43,15 @@ if (isLoading) return <Skeleton className="h-12 w-full" />
|
||||
```
|
||||
|
||||
### No Caching Strategy
|
||||
Cache slow database queries with `unstable_cache`:
|
||||
```typescript
|
||||
import { unstable_cache } from 'next/cache'
|
||||
|
||||
export const getStats = unstable_cache(
|
||||
async () => {
|
||||
const { data } = await supabase.from('stats').select('*')
|
||||
return data
|
||||
},
|
||||
['dashboard-stats'],
|
||||
{ revalidate: 3600 } // Refresh every hour
|
||||
)
|
||||
```
|
||||
Cache slow backend queries in-process (see [database-optimization.md](database-optimization.md)) rather than re-querying Postgres on every request for rarely-changing data.
|
||||
|
||||
## Quick Wins Checklist
|
||||
- [ ] All images use `next/image` component
|
||||
- [ ] Heavy components use dynamic imports
|
||||
- [ ] Images have explicit width/height and `loading="lazy"` where below the fold
|
||||
- [ ] Heavy components use `React.lazy` + `Suspense`
|
||||
- [ ] Loading states show skeleton/spinner
|
||||
- [ ] Fonts loaded with `next/font`
|
||||
- [ ] No unnecessary client-side JavaScript (`"use client"` only when needed)
|
||||
- [ ] Fonts loaded via `@font-face`/Google Fonts `<link>`, with `font-display: swap`
|
||||
- [ ] Production build (`npm run build`) checked for unexpectedly large chunks
|
||||
|
||||
## Automated Monitoring
|
||||
- **Vercel Analytics** - Automatic on Pro plan, shows Core Web Vitals
|
||||
- **Vercel Speed Insights** - Real user performance data
|
||||
- Self-hosted **Plausible** or **Umami** for privacy-friendly traffic analytics
|
||||
- `web-vitals` npm package to report Core Web Vitals from real users to your own backend/logging
|
||||
Reference in new issue
Block a user