@ojokesusu/lintasai 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish-npm.yml +40 -0
- package/.github/workflows/validate.yml +93 -0
- package/AUDIT_POST_SETUP_PROMPT_v1.md +280 -0
- package/BOOTSTRAP_PROJECT_DOCS_PROMPT_v1.md +3 -0
- package/CHANGELOG.md +313 -0
- package/CLAUDE_universal_v1.md +1021 -0
- package/CONTRIBUTING.md +101 -0
- package/FIRST_SESSION_PROMPT_v1.md +7 -0
- package/JALANKAN_KIT.md +188 -0
- package/LICENSE +21 -0
- package/MULAI_DI_SINI.md +145 -0
- package/PROJECT_KICKOFF_PROMPT_v1.md +3 -0
- package/PROJECT_LIFECYCLE_PROMPT_v1.md +536 -0
- package/PROJECT_MIGRATION_PROMPT_v1.md +3 -0
- package/README.md +505 -0
- package/SETUP_POLA_B_PROMPT_v1.md +5 -0
- package/SPLIT_REPO_MIGRATION_PROMPT_v1.md +485 -0
- package/TEAM_ROLLOUT_GUIDE_v1.md +172 -0
- package/UPDATE_DOCS_PROMPT_v1.md +3 -0
- package/UPDATE_KIT_PROMPT_v1.md +213 -0
- package/bin/lintasai.js +81 -0
- package/docs/SIGNED_RELEASE.md +162 -0
- package/install-windows.ps1 +225 -0
- package/kit.ps1 +508 -0
- package/lib/agents-md.ps1 +174 -0
- package/lib/git-helpers.ps1 +104 -0
- package/lib/kit-files.psd1 +133 -0
- package/lib/manifest-signing.ps1 +65 -0
- package/lib/manifest.ps1 +267 -0
- package/lib/rollback.ps1 +241 -0
- package/lib/safety.ps1 +193 -0
- package/lib/template-deploy.ps1 +242 -0
- package/lib/version-detect.ps1 +161 -0
- package/package.json +36 -0
- package/setup-pola-b.ps1 +687 -0
- package/templates/ANALOGI_LIBRARY.md +7 -0
- package/templates/CLAUDE_TEAM_GUIDE.md +505 -0
- package/templates/CROSS_REPO_TYPES_PIPELINE.md +473 -0
- package/templates/DB_SCHEMA_SCAN_PROMPT.md +194 -0
- package/templates/DISCORD_BOT_INTEGRATION.md +187 -0
- package/templates/GLOSSARY_NON_PROGRAMMER.md +361 -0
- package/templates/INDEX.md +157 -0
- package/templates/MCP_SETUP.md +1145 -0
- package/templates/MIGRATE_TO_SUBFOLDER_PROMPT_v1.md +220 -0
- package/templates/ONBOARDING.md +172 -0
- package/templates/PROJECT_STARTER_TEMPLATES.md +264 -0
- package/templates/PROMPT_LIBRARY.md +790 -0
- package/templates/RLS_SETUP_PROMPT.md +167 -0
- package/templates/SECURITY_INCIDENT_PLAYBOOK.md +191 -0
- package/templates/SPLIT_REPO_AGENTS_TEMPLATES.md +32 -0
- package/templates/SPLIT_REPO_NON_PROGRAMMER_PROMPTS.md +604 -0
- package/templates/SPLIT_REPO_TOOLS_SETUP.md +388 -0
- package/templates/STACK_DETECTION_PATTERN.md +261 -0
- package/templates/STACK_GUIDE.md +564 -0
- package/templates/STACK_MIGRATION_GUIDE.md +154 -0
- package/templates/STACK_VERSIONS.md +31 -0
- package/templates/UPDATE_GUIDE.md +246 -0
- package/templates/_EXAMPLE.md +110 -0
- package/templates/_PATTERNS.md +173 -0
- package/templates/architecture.md +180 -0
- package/templates/architecture_auto.md +61 -0
- package/templates/decisions/README.md +108 -0
- package/templates/decisions/_TEMPLATE.md +84 -0
- package/templates/feature-flags-advanced.md +171 -0
- package/templates/github/CODEOWNERS.template +61 -0
- package/templates/github/GENERATE_TYPES_SCRIPT.md +77 -0
- package/templates/github/PUBLISH_SHARED_WORKFLOW.yml +52 -0
- package/templates/github/RECEIVE_BACKEND_UPDATE.yml +106 -0
- package/templates/github/RENOVATE_FRONTEND.json +28 -0
- package/templates/github/TRIGGER_FRONTEND_UPDATE.yml +29 -0
- package/templates/github/pull_request_template.md +44 -0
- package/templates/github/scripts/ai-review.js +153 -0
- package/templates/github/workflows/ai-review.yml +61 -0
- package/templates/github/workflows/backup-schemas.yml +169 -0
- package/templates/glossary.md +110 -0
- package/templates/split-agents/BACKEND.md +149 -0
- package/templates/split-agents/FRONTEND.md +141 -0
- package/templates/split-agents/SHARED.md +82 -0
- package/templates/split-agents/TOOLS.md +77 -0
- package/tests/Run-Tests.ps1 +19 -0
- package/tests/lib-safety.Tests.ps1 +66 -0
- package/tests/rollback.Tests.ps1 +66 -0
- package/tests/uninstall.Tests.ps1 +265 -0
- package/tests/update-kit.Tests.ps1 +78 -0
- package/uninstall.ps1 +794 -0
- package/update-kit.ps1 +907 -0
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
# templates/STACK_GUIDE.md — Panduan Stack Standar Tim AI-first
|
|
2
|
+
|
|
3
|
+
> Versi 1 · 2026-06-01
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Pengantar
|
|
8
|
+
|
|
9
|
+
File ini = **panduan opinionated** untuk stack standar tim AI-first.
|
|
10
|
+
Target stack default:
|
|
11
|
+
|
|
12
|
+
- **Frontend + Backend** → Next.js (lihat STACK_VERSIONS.md untuk versi terbaru) (App Router) + TypeScript 5+
|
|
13
|
+
- **Hosting primary** → Vercel (deploy auto dari Git)
|
|
14
|
+
- **Database** → Supabase (PostgreSQL managed)
|
|
15
|
+
- **UI** → Tailwind 4 + shadcn/ui
|
|
16
|
+
- **Future migration path** → Railway / Render (kalau butuh background worker native atau biaya Vercel kelebihan)
|
|
17
|
+
|
|
18
|
+
### Filosofi opinionated
|
|
19
|
+
|
|
20
|
+
- **Satu cara untuk satu hal** — jangan campur Pages Router & App Router, jangan campur Server Action & client-fetch untuk mutation.
|
|
21
|
+
- **Server-first** — default Server Component, baru ke Client kalau butuh interaktivitas.
|
|
22
|
+
- **Vendor-aware tapi tidak vendor-locked** — pakai Vercel sampai mahal, lalu pindah ke Railway/Render. Jangan pakai fitur Vercel-only kalau ada padanan portable.
|
|
23
|
+
- **AI-first** — semua konvensi di file ini juga dibaca AI tiap sesi → AI nulis kode yang konsisten tanpa user perlu ulang-ulang aturan.
|
|
24
|
+
|
|
25
|
+
> *Opinionated* = punya pendapat tegas soal cara kerja. Lawannya *unopinionated* (bebas pilih cara apa saja, tapi tim jadi berantakan).
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 2. Next.js App Router Convention
|
|
30
|
+
|
|
31
|
+
### 2.1. Server Component vs Client Component
|
|
32
|
+
|
|
33
|
+
**Default = Server Component** (tanpa `'use client'` di atas file).
|
|
34
|
+
|
|
35
|
+
Kapan pakai `'use client'`:
|
|
36
|
+
|
|
37
|
+
| Butuh | Pakai `'use client'`? |
|
|
38
|
+
|--------------------------------------------------|------------------------|
|
|
39
|
+
| `useState`, `useEffect`, `useRef` | YA |
|
|
40
|
+
| Event handler (`onClick`, `onChange`, `onSubmit`)| YA |
|
|
41
|
+
| Browser API (`window`, `localStorage`, `navigator`) | YA |
|
|
42
|
+
| Library client-only (mis. Framer Motion, Chart.js) | YA |
|
|
43
|
+
| Cuma render data dari DB / API | TIDAK (Server saja) |
|
|
44
|
+
| Form static (pakai Server Action) | TIDAK (Server saja) |
|
|
45
|
+
|
|
46
|
+
**Pola yang benar**: Server Component sebagai *shell*, Client Component sebagai *island* interaktif kecil.
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
// app/dashboard/page.tsx — Server Component (default)
|
|
50
|
+
import { db } from '@/lib/db'
|
|
51
|
+
import { LikeButton } from './like-button' // ini Client
|
|
52
|
+
|
|
53
|
+
export default async function DashboardPage() {
|
|
54
|
+
const posts = await db.post.findMany() // fetch langsung di Server
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div>
|
|
58
|
+
{posts.map((post) => (
|
|
59
|
+
<article key={post.id}>
|
|
60
|
+
<h2>{post.title}</h2>
|
|
61
|
+
<LikeButton postId={post.id} /> {/* island interaktif */}
|
|
62
|
+
</article>
|
|
63
|
+
))}
|
|
64
|
+
</div>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
// app/dashboard/like-button.tsx — Client Component
|
|
71
|
+
'use client'
|
|
72
|
+
import { useState } from 'react'
|
|
73
|
+
|
|
74
|
+
export function LikeButton({ postId }: { postId: string }) {
|
|
75
|
+
const [liked, setLiked] = useState(false)
|
|
76
|
+
return <button onClick={() => setLiked(!liked)}>{liked ? '♥' : '♡'}</button>
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 2.2. Data Fetching
|
|
81
|
+
|
|
82
|
+
**WAJIB**: fetch data di **Server Component** pakai `async/await` langsung.
|
|
83
|
+
**JANGAN**: pakai `useEffect` + `fetch` di Client Component untuk initial data.
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
// BENAR — Server Component
|
|
87
|
+
export default async function Page() {
|
|
88
|
+
const data = await fetch('https://api.example.com/data', {
|
|
89
|
+
next: { revalidate: 60 } // ISR: re-fetch tiap 60 detik
|
|
90
|
+
}).then(r => r.json())
|
|
91
|
+
return <div>{data.title}</div>
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// SALAH — useEffect di Client Component untuk initial data
|
|
95
|
+
'use client'
|
|
96
|
+
export default function Page() {
|
|
97
|
+
const [data, setData] = useState(null)
|
|
98
|
+
useEffect(() => { fetch('...').then(...) }, []) // loading spinner, SEO buruk
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Kapan pakai client-fetch (`SWR`, `React Query`, `useEffect+fetch`): **hanya** kalau data harus auto-refresh per interval atau triggered by user action setelah halaman load.
|
|
103
|
+
|
|
104
|
+
### 2.3. Metadata SEO
|
|
105
|
+
|
|
106
|
+
Setiap route export `metadata` (static) atau `generateMetadata` (dynamic).
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
// Static metadata
|
|
110
|
+
export const metadata = {
|
|
111
|
+
title: 'Dashboard | Akses',
|
|
112
|
+
description: 'Panel admin proyek akses.',
|
|
113
|
+
openGraph: {
|
|
114
|
+
title: 'Dashboard | Akses',
|
|
115
|
+
description: 'Panel admin proyek akses.',
|
|
116
|
+
images: ['/og-dashboard.png'],
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Dynamic metadata (per slug / per ID)
|
|
121
|
+
export async function generateMetadata({ params }: { params: { slug: string } }) {
|
|
122
|
+
const post = await db.post.findUnique({ where: { slug: params.slug } })
|
|
123
|
+
return {
|
|
124
|
+
title: `${post.title} | Akses`,
|
|
125
|
+
description: post.excerpt,
|
|
126
|
+
openGraph: { images: [post.coverImage] },
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Di `app/layout.tsx` root, set default site-wide:
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
export const metadata = {
|
|
135
|
+
metadataBase: new URL('https://akses.app'),
|
|
136
|
+
title: { default: 'Akses', template: '%s | Akses' },
|
|
137
|
+
description: 'Manajemen akses & dashboard.',
|
|
138
|
+
robots: { index: true, follow: true },
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 2.4. Form & Mutation
|
|
143
|
+
|
|
144
|
+
**WAJIB**: pakai **Server Action** untuk semua mutation (create, update, delete).
|
|
145
|
+
**JANGAN**: pakai client `fetch('/api/...', { method: 'POST' })` untuk form internal.
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
// app/posts/new/page.tsx — form static + Server Action
|
|
149
|
+
import { createPost } from './actions'
|
|
150
|
+
|
|
151
|
+
export default function NewPostPage() {
|
|
152
|
+
return (
|
|
153
|
+
<form action={createPost}>
|
|
154
|
+
<input name="title" required />
|
|
155
|
+
<textarea name="content" required />
|
|
156
|
+
<button type="submit">Simpan</button>
|
|
157
|
+
</form>
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
// app/posts/new/actions.ts — Server Action
|
|
164
|
+
'use server'
|
|
165
|
+
import { redirect } from 'next/navigation'
|
|
166
|
+
import { db } from '@/lib/db'
|
|
167
|
+
import { revalidatePath } from 'next/cache'
|
|
168
|
+
|
|
169
|
+
export async function createPost(formData: FormData) {
|
|
170
|
+
const title = formData.get('title') as string
|
|
171
|
+
const content = formData.get('content') as string
|
|
172
|
+
|
|
173
|
+
// validasi (pakai Zod di produksi)
|
|
174
|
+
if (!title || title.length < 3) throw new Error('Title min 3 char')
|
|
175
|
+
|
|
176
|
+
await db.post.create({ data: { title, content } })
|
|
177
|
+
revalidatePath('/posts')
|
|
178
|
+
redirect('/posts')
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Kalau butuh feedback interaktif (loading, error) di form → wrap dengan Client Component pakai `useActionState` (React 19).
|
|
183
|
+
|
|
184
|
+
### 2.5. Loading & Error State (File Convention)
|
|
185
|
+
|
|
186
|
+
Next.js App Router auto-render file ini di tiap route segment:
|
|
187
|
+
|
|
188
|
+
```text
|
|
189
|
+
app/
|
|
190
|
+
├── dashboard/
|
|
191
|
+
│ ├── page.tsx // halaman utama
|
|
192
|
+
│ ├── loading.tsx // muncul saat page.tsx masih loading data
|
|
193
|
+
│ ├── error.tsx // muncul kalau page.tsx throw error
|
|
194
|
+
│ └── not-found.tsx // muncul kalau notFound() dipanggil
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
```tsx
|
|
198
|
+
// app/dashboard/loading.tsx
|
|
199
|
+
export default function Loading() {
|
|
200
|
+
return <div className="animate-pulse">Memuat data...</div>
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// app/dashboard/error.tsx — WAJIB 'use client'
|
|
204
|
+
'use client'
|
|
205
|
+
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
|
|
206
|
+
return (
|
|
207
|
+
<div>
|
|
208
|
+
<h2>Ada error</h2>
|
|
209
|
+
<p>{error.message}</p>
|
|
210
|
+
<button onClick={reset}>Coba lagi</button>
|
|
211
|
+
</div>
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### 2.6. Image & Font Optimization
|
|
217
|
+
|
|
218
|
+
**Gambar**: pakai `next/image` (auto-resize, lazy-load, AVIF/WebP).
|
|
219
|
+
|
|
220
|
+
```tsx
|
|
221
|
+
import Image from 'next/image'
|
|
222
|
+
|
|
223
|
+
<Image
|
|
224
|
+
src="/hero.jpg"
|
|
225
|
+
alt="Hero proyek akses"
|
|
226
|
+
width={1200}
|
|
227
|
+
height={600}
|
|
228
|
+
priority // untuk above-the-fold (LCP)
|
|
229
|
+
/>
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
**Font**: pakai `next/font` (self-hosted, no layout shift, no FOUT).
|
|
233
|
+
|
|
234
|
+
```tsx
|
|
235
|
+
// app/layout.tsx
|
|
236
|
+
import { Inter } from 'next/font/google'
|
|
237
|
+
|
|
238
|
+
const inter = Inter({ subsets: ['latin'], display: 'swap' })
|
|
239
|
+
|
|
240
|
+
export default function RootLayout({ children }) {
|
|
241
|
+
return (
|
|
242
|
+
<html lang="id" className={inter.className}>
|
|
243
|
+
<body>{children}</body>
|
|
244
|
+
</html>
|
|
245
|
+
)
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## 3. Vercel Setup (Primary Hosting)
|
|
252
|
+
|
|
253
|
+
### 3.1. Connect Repo
|
|
254
|
+
|
|
255
|
+
1. Login https://vercel.com pakai akun GitHub tim.
|
|
256
|
+
2. **Add New → Project** → pilih repo dari GitHub.
|
|
257
|
+
3. Framework Preset: **Next.js** (auto-detect).
|
|
258
|
+
4. Root directory: `./` (kecuali monorepo).
|
|
259
|
+
5. Build command default: `next build` (jangan ganti kecuali perlu).
|
|
260
|
+
|
|
261
|
+
### 3.2. Environment Variables (Production / Preview / Development)
|
|
262
|
+
|
|
263
|
+
Vercel punya 3 environment terpisah:
|
|
264
|
+
|
|
265
|
+
| Env | Kapan dipakai |
|
|
266
|
+
|-------------|------------------------------------------------------|
|
|
267
|
+
| Production | Deploy dari branch `main` (= public domain) |
|
|
268
|
+
| Preview | Deploy auto per PR / branch lain (= URL random) |
|
|
269
|
+
| Development | Saat `vercel dev` lokal (jarang dipakai) |
|
|
270
|
+
|
|
271
|
+
**WAJIB**: split env vars per environment. Production pakai DB produksi, Preview pakai DB staging (kalau ada).
|
|
272
|
+
|
|
273
|
+
Contoh isi env vars di dashboard Vercel:
|
|
274
|
+
|
|
275
|
+
```text
|
|
276
|
+
Production:
|
|
277
|
+
DATABASE_URL = postgresql://...@prod-db.supabase.co:6543/postgres
|
|
278
|
+
NEXT_PUBLIC_SITE_URL = https://akses.app
|
|
279
|
+
|
|
280
|
+
Preview:
|
|
281
|
+
DATABASE_URL = postgresql://...@staging-db.supabase.co:6543/postgres
|
|
282
|
+
NEXT_PUBLIC_SITE_URL = https://akses-preview.vercel.app
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Variabel `NEXT_PUBLIC_*` = ter-expose ke browser (jangan taruh secret di sini). Tanpa prefix = server-only.
|
|
286
|
+
|
|
287
|
+
### 3.3. Custom Domain + SSL
|
|
288
|
+
|
|
289
|
+
1. Project Settings → **Domains** → Add domain (mis. `akses.app`).
|
|
290
|
+
2. Update DNS di registrar: tambah record `A` ke `76.76.21.21` atau `CNAME` ke `cname.vercel-dns.com`.
|
|
291
|
+
3. SSL otomatis (Let's Encrypt) — tunggu 1-5 menit.
|
|
292
|
+
4. Tambah subdomain `www` → set redirect ke apex (atau sebaliknya).
|
|
293
|
+
|
|
294
|
+
### 3.4. Edge Function (untuk Middleware)
|
|
295
|
+
|
|
296
|
+
File `middleware.ts` di root → otomatis jalan di Edge Runtime (cepat, deploy global).
|
|
297
|
+
|
|
298
|
+
```ts
|
|
299
|
+
// middleware.ts — proteksi route /dashboard
|
|
300
|
+
import { NextResponse } from 'next/server'
|
|
301
|
+
import type { NextRequest } from 'next/server'
|
|
302
|
+
|
|
303
|
+
export function middleware(req: NextRequest) {
|
|
304
|
+
const token = req.cookies.get('session')?.value
|
|
305
|
+
if (!token && req.nextUrl.pathname.startsWith('/dashboard')) {
|
|
306
|
+
return NextResponse.redirect(new URL('/login', req.url))
|
|
307
|
+
}
|
|
308
|
+
return NextResponse.next()
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export const config = { matcher: ['/dashboard/:path*'] }
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### 3.5. Web Analytics + Speed Insights
|
|
315
|
+
|
|
316
|
+
Gratis di plan Hobby. Wajib pasang untuk SEO + Core Web Vitals monitoring.
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
npm i @vercel/analytics @vercel/speed-insights
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
```tsx
|
|
323
|
+
// app/layout.tsx
|
|
324
|
+
import { Analytics } from '@vercel/analytics/react'
|
|
325
|
+
import { SpeedInsights } from '@vercel/speed-insights/next'
|
|
326
|
+
|
|
327
|
+
export default function RootLayout({ children }) {
|
|
328
|
+
return (
|
|
329
|
+
<html>
|
|
330
|
+
<body>
|
|
331
|
+
{children}
|
|
332
|
+
<Analytics />
|
|
333
|
+
<SpeedInsights />
|
|
334
|
+
</body>
|
|
335
|
+
</html>
|
|
336
|
+
)
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Aktifkan di dashboard: Project → Analytics → Enable; Speed Insights → Enable.
|
|
341
|
+
|
|
342
|
+
### 3.6. Preview Deploy Workflow
|
|
343
|
+
|
|
344
|
+
- Push ke branch apa saja (kecuali `main`) → Vercel auto-deploy **Preview** dengan URL unik (mis. `akses-git-feat-login-team.vercel.app`).
|
|
345
|
+
- Buka PR di GitHub → Vercel bot auto-comment URL Preview.
|
|
346
|
+
- QA test di Preview URL, kalau OK → merge ke `main`.
|
|
347
|
+
|
|
348
|
+
### 3.7. Production Deploy
|
|
349
|
+
|
|
350
|
+
- Merge PR ke `main` → Vercel auto-deploy ke Production (domain custom).
|
|
351
|
+
- Deploy time biasanya 30-90 detik (Next.js + Tailwind).
|
|
352
|
+
- Build log lengkap di dashboard → Project → Deployments → klik deployment.
|
|
353
|
+
|
|
354
|
+
### 3.8. Rollback
|
|
355
|
+
|
|
356
|
+
Kalau production rusak setelah deploy:
|
|
357
|
+
|
|
358
|
+
1. Dashboard → Project → **Deployments**.
|
|
359
|
+
2. Cari deployment lama yang stabil.
|
|
360
|
+
3. Klik `...` (tiga titik) → **Promote to Production**.
|
|
361
|
+
4. Selesai dalam <10 detik — traffic langsung pindah ke deployment lama.
|
|
362
|
+
|
|
363
|
+
> Rollback **tidak** memutar balik DB. Kalau migrasi DB sudah jalan, rollback aplikasi saja bisa bikin error skema. Solusi: pakai migrasi backward-compatible (additive only).
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## 4. Migration ke Railway / Render (Advanced --- Post-Launch)
|
|
368
|
+
|
|
369
|
+
> Default tim = **Vercel saja**. Section ini cuma jadi pointer.
|
|
370
|
+
|
|
371
|
+
Migrasi ke Railway atau Render = **operasi advanced** yang baru relevan kalau salah satu kondisi ini terjadi: (a) bill Vercel sudah lewat budget (mis. >$100/bulan untuk satu project), atau (b) butuh **background worker persistent** / **WebSocket long-lived** yang tidak cocok di model serverless Vercel. Untuk Day 0--1 staff IT non-programmer: **abaikan section ini**, pakai Vercel + Supabase saja. Detail step-by-step setup (provision, env vars, Dockerfile, worker, cron, healthcheck) ada di **`templates/STACK_MIGRATION_GUIDE.md`** --- file terpisah supaya STACK_GUIDE tetap fokus ke default workflow. Decision Matrix vendor (Vercel vs Railway vs Render) tetap dipertahankan di section 9 sebagai bahan pertimbangan kapan harus migrasi.
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
## 6. SEO Checklist Mandatory
|
|
376
|
+
|
|
377
|
+
Kategori prioritas:
|
|
378
|
+
|
|
379
|
+
- **P0** = WAJIB pre-launch. Tanpa ini, situs tidak ter-index Google atau muncul broken.
|
|
380
|
+
- **P1** = affect ranking. Boleh nyusul minggu pertama setelah launch.
|
|
381
|
+
- **P2** = boost ranking. Optimasi lanjutan, nice-to-have.
|
|
382
|
+
|
|
383
|
+
### P0 (wajib pre-launch)
|
|
384
|
+
|
|
385
|
+
- [ ] `metadata.title` & `metadata.description` di tiap route (bukan default Next.js).
|
|
386
|
+
- [ ] `metadataBase` di root `layout.tsx` (untuk URL absolut di OG image).
|
|
387
|
+
- [ ] `robots.txt` di `app/robots.ts` atau `public/robots.txt` — pastikan **tidak** disallow `/` di production.
|
|
388
|
+
- [ ] `sitemap.xml` di `app/sitemap.ts` (Next.js auto-generate dari array).
|
|
389
|
+
- [ ] `lang="id"` (atau bahasa sesuai target) di tag `<html>`.
|
|
390
|
+
- [ ] Canonical URL di metadata (`alternates: { canonical: '...' }`) untuk halaman dengan query string.
|
|
391
|
+
- [ ] HTTPS aktif (Vercel auto, tidak perlu config).
|
|
392
|
+
- [ ] No `noindex` accidental di production (cek `robots` meta tag).
|
|
393
|
+
|
|
394
|
+
### P1 (affect ranking)
|
|
395
|
+
|
|
396
|
+
- [ ] Open Graph image (`og-image.png`, 1200x630px) per route penting.
|
|
397
|
+
- [ ] Structured data (JSON-LD) untuk artikel, produk, FAQ (pakai `<script type="application/ld+json">`).
|
|
398
|
+
- [ ] Alt text di semua `<Image>` (jangan kosong, jangan filename mentah).
|
|
399
|
+
- [ ] Heading hierarchy benar (`h1` satu per halaman, `h2/h3` ter-nest logis).
|
|
400
|
+
- [ ] Internal linking antar-halaman (anchor text deskriptif, bukan "klik di sini").
|
|
401
|
+
- [ ] Core Web Vitals: LCP <2.5s, CLS <0.1, INP <200ms (monitor via Speed Insights).
|
|
402
|
+
- [ ] Mobile responsive (test di Chrome DevTools device mode).
|
|
403
|
+
|
|
404
|
+
### P2 (boost ranking)
|
|
405
|
+
|
|
406
|
+
- [ ] Breadcrumb structured data.
|
|
407
|
+
- [ ] FAQ structured data di halaman dengan Q&A.
|
|
408
|
+
- [ ] Page speed: convert image ke AVIF/WebP (next/image auto-handle).
|
|
409
|
+
- [ ] Preconnect ke domain eksternal sering dipakai (`<link rel="preconnect" href="https://fonts.googleapis.com">`).
|
|
410
|
+
- [ ] Lazy-load iframe (YouTube embed, map).
|
|
411
|
+
- [ ] hreflang tag kalau multi-bahasa.
|
|
412
|
+
|
|
413
|
+
Contoh `app/robots.ts`:
|
|
414
|
+
|
|
415
|
+
```ts
|
|
416
|
+
import type { MetadataRoute } from 'next'
|
|
417
|
+
|
|
418
|
+
export default function robots(): MetadataRoute.Robots {
|
|
419
|
+
return {
|
|
420
|
+
rules: { userAgent: '*', allow: '/', disallow: ['/admin/', '/api/'] },
|
|
421
|
+
sitemap: 'https://akses.app/sitemap.xml',
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
Contoh `app/sitemap.ts`:
|
|
427
|
+
|
|
428
|
+
```ts
|
|
429
|
+
import type { MetadataRoute } from 'next'
|
|
430
|
+
import { db } from '@/lib/db'
|
|
431
|
+
|
|
432
|
+
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|
433
|
+
const posts = await db.post.findMany({ select: { slug: true, updatedAt: true } })
|
|
434
|
+
return [
|
|
435
|
+
{ url: 'https://akses.app', lastModified: new Date(), priority: 1 },
|
|
436
|
+
...posts.map(p => ({
|
|
437
|
+
url: `https://akses.app/post/${p.slug}`,
|
|
438
|
+
lastModified: p.updatedAt,
|
|
439
|
+
priority: 0.8,
|
|
440
|
+
})),
|
|
441
|
+
]
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
## 7. Security Checklist
|
|
448
|
+
|
|
449
|
+
### 7.1. Env Vars
|
|
450
|
+
|
|
451
|
+
- [ ] Tidak ada secret hard-coded di repo (cek pakai `gitleaks` atau `truffleHog`).
|
|
452
|
+
- [ ] `.env.local` di `.gitignore`.
|
|
453
|
+
- [ ] Secret di Vercel env vars di-mark **Sensitive** (icon mata) — encrypted at rest.
|
|
454
|
+
- [ ] `NEXT_PUBLIC_*` cuma untuk data yang aman ke browser (URL, feature flag boolean) — JANGAN API key, JANGAN service_role_key.
|
|
455
|
+
- [ ] Rotate secret tiap quarter atau saat staff keluar.
|
|
456
|
+
|
|
457
|
+
### 7.2. Auth
|
|
458
|
+
|
|
459
|
+
- [ ] Session token via cookie `HttpOnly` + `Secure` + `SameSite=Lax`.
|
|
460
|
+
- [ ] Password hash pakai `bcrypt` (cost ≥10) atau `argon2`. JANGAN MD5/SHA1.
|
|
461
|
+
- [ ] CSRF protection di Server Action (Next.js handle by default via origin check, tapi kalau cross-domain → tambah token manual).
|
|
462
|
+
- [ ] Rate limit login endpoint (`@upstash/ratelimit` atau Vercel Edge Config).
|
|
463
|
+
- [ ] Lock account setelah 5 percobaan gagal (15 menit).
|
|
464
|
+
|
|
465
|
+
### 7.3. DB Connection
|
|
466
|
+
|
|
467
|
+
- [ ] Pakai **connection pooling** Supabase (`port 6543`, mode `transaction`) untuk serverless Vercel.
|
|
468
|
+
- [ ] JANGAN expose `service_role_key` ke client — itu bypass RLS.
|
|
469
|
+
- [ ] Pakai `anon_key` (limited) di browser, `service_role_key` di Server Action saja.
|
|
470
|
+
- [ ] Row-Level Security (RLS) aktif di semua tabel publik.
|
|
471
|
+
|
|
472
|
+
### 7.4. CSP Header
|
|
473
|
+
|
|
474
|
+
Content Security Policy mencegah XSS injection.
|
|
475
|
+
|
|
476
|
+
```ts
|
|
477
|
+
// next.config.js
|
|
478
|
+
module.exports = {
|
|
479
|
+
async headers() {
|
|
480
|
+
return [{
|
|
481
|
+
source: '/(.*)',
|
|
482
|
+
headers: [
|
|
483
|
+
{ key: 'X-Frame-Options', value: 'DENY' },
|
|
484
|
+
{ key: 'X-Content-Type-Options', value: 'nosniff' },
|
|
485
|
+
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
|
|
486
|
+
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
|
|
487
|
+
{ key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains; preload' },
|
|
488
|
+
],
|
|
489
|
+
}]
|
|
490
|
+
},
|
|
491
|
+
}
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
---
|
|
495
|
+
|
|
496
|
+
## 8. Feature Flag Pattern (ADVANCED — Post-Launch Only)
|
|
497
|
+
|
|
498
|
+
> ⚠️ **Default workflow tim TIDAK pakai feature flag.** Untuk early-stage project (belum launch / progress <50%), staging via **Vercel Preview Deploy per PR** sudah cukup. Lihat `CLAUDE_TEAM_GUIDE.md` section 7b (Risk Level Decision Tree) untuk default workflow.
|
|
499
|
+
>
|
|
500
|
+
> Feature flag = advanced operation yang butuh owner familiar dengan Vercel env vars + redeploy cycle. **Tambahkan post-launch** kalau project sudah punya user aktif dan butuh:
|
|
501
|
+
> - Kill switch instant untuk fitur kritis (mis. payment toggle saat Black Friday)
|
|
502
|
+
> - A/B test gradual rollout (10% → 50% → 100%)
|
|
503
|
+
> - Per-user targeting (beta tester subset)
|
|
504
|
+
|
|
505
|
+
Detail implementasi lengkap (decision tree, naming convention, cleanup ritual, testing pattern, per-user hash) di **`./.claude-kit/templates/feature-flags-advanced.md`** — file terpisah supaya tidak ngebebanin kit default workflow.
|
|
506
|
+
|
|
507
|
+
**Untuk early-stage akses (progress ~5%)**: skip section ini, pakai Risk Level (CLAUDE_TEAM_GUIDE.md 7b) + staging-only.
|
|
508
|
+
|
|
509
|
+
---
|
|
510
|
+
|
|
511
|
+
## 9. Decision Matrix: Vercel vs Railway vs Render
|
|
512
|
+
|
|
513
|
+
| Aspek | Vercel | Railway | Render |
|
|
514
|
+
|---------------------------------|-----------------------|----------------------|----------------------|
|
|
515
|
+
| **Setup speed** | ★★★★★ (auto Next.js) | ★★★★ (Nixpacks) | ★★★ (manual config) |
|
|
516
|
+
| **DX (Developer Experience)** | ★★★★★ | ★★★★ | ★★★ |
|
|
517
|
+
| **Preview deploy per PR** | YA (default) | YA (default) | YA (paid plan) |
|
|
518
|
+
| **Background worker persistent**| TIDAK (serverless) | YA | YA |
|
|
519
|
+
| **Cron native** | Vercel Cron (Pro+) | YA (gratis) | YA (gratis) |
|
|
520
|
+
| **WebSocket / SSE long-lived** | Terbatas (Edge) | YA | YA |
|
|
521
|
+
| **PostgreSQL bundled** | TIDAK (pakai Supabase)| YA (plugin) | YA (managed) |
|
|
522
|
+
| **Pricing transparency** | ★★★ (function invoke) | ★★★★ (per-resource) | ★★★★★ (flat) |
|
|
523
|
+
| **Free tier (small project)** | Hobby gratis cukup | $5 credit/bulan | Free 750 jam/bulan |
|
|
524
|
+
| **Vendor lock-in** | Tinggi (Edge runtime) | Rendah (Docker) | Rendah (Docker) |
|
|
525
|
+
| **Best untuk** | Marketing site, SaaS, dashboard | App butuh worker / WS | Stabilitas + predictable |
|
|
526
|
+
|
|
527
|
+
### Rekomendasi default
|
|
528
|
+
|
|
529
|
+
- **0 → MVP**: Vercel (deploy 5 menit, gratis).
|
|
530
|
+
- **MVP → 1000 user**: Tetap Vercel, monitor cost. Pakai Supabase untuk DB.
|
|
531
|
+
- **1000 → 10k user**: Cek bill Vercel. Kalau >$100/bulan & butuh worker → pindah ke Railway.
|
|
532
|
+
- **Enterprise / butuh on-premise**: Render (lebih konservatif) atau self-host Docker.
|
|
533
|
+
|
|
534
|
+
---
|
|
535
|
+
|
|
536
|
+
## 10. Checklist Pre-Launch
|
|
537
|
+
|
|
538
|
+
Sebelum announce launch produksi:
|
|
539
|
+
|
|
540
|
+
- [ ] Semua P0 SEO terisi (section 6).
|
|
541
|
+
- [ ] Semua Security checklist terisi (section 7).
|
|
542
|
+
- [ ] Speed Insights + Analytics aktif.
|
|
543
|
+
- [ ] Custom domain + SSL aktif (bukan `.vercel.app`).
|
|
544
|
+
- [ ] Rollback plan dipahami (section 3.8).
|
|
545
|
+
- [ ] Backup DB Supabase aktif (Settings → Database → Backups).
|
|
546
|
+
- [ ] Error monitoring (Sentry / Vercel logs) aktif.
|
|
547
|
+
- [ ] Healthcheck endpoint `/api/health` ada (untuk uptime monitor).
|
|
548
|
+
- [ ] Robots & sitemap di-submit ke Google Search Console.
|
|
549
|
+
- [ ] Feature flag default = stable path (rollout fitur baru bertahap).
|
|
550
|
+
|
|
551
|
+
---
|
|
552
|
+
|
|
553
|
+
## Referensi Eksternal
|
|
554
|
+
|
|
555
|
+
- Next.js App Router docs: https://nextjs.org/docs/app
|
|
556
|
+
- Vercel docs: https://vercel.com/docs
|
|
557
|
+
- Railway docs: https://docs.railway.app
|
|
558
|
+
- Render docs: https://render.com/docs
|
|
559
|
+
- Google Search Central (SEO): https://developers.google.com/search
|
|
560
|
+
- Web.dev (Core Web Vitals): https://web.dev/vitals
|
|
561
|
+
|
|
562
|
+
---
|
|
563
|
+
|
|
564
|
+
> **Update file ini** tiap kali tim ganti vendor, ganti versi major Next.js, atau ada keputusan stack baru. Catat di `CHANGELOG.md` kit + bump versi.
|