start-vibing-stacks 2.32.0 → 2.33.0
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/package.json +1 -1
- package/stacks/_shared/skills/docker-patterns/SKILL.md +12 -2
- package/stacks/_shared/skills/kubernetes-patterns/SKILL.md +312 -0
- package/stacks/_shared/skills/laravel-octane-inertia/SKILL.md +213 -0
- package/stacks/_shared/skills/podman-patterns/SKILL.md +260 -0
- package/stacks/_shared/skills/react-server-components/SKILL.md +194 -0
- package/stacks/nodejs/stack.json +5 -2
- package/stacks/php/stack.json +7 -3
- package/stacks/python/stack.json +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: docker-patterns
|
|
3
|
-
version: 2.
|
|
3
|
+
version: 2.1.0
|
|
4
4
|
description: "Docker patterns for 2026: BuildKit + Buildx Bake (default builder for Docker Compose since June 2025), multi-stage builds, distroless / chiseled bases (90% size reduction, no shell), non-root users, .dockerignore, healthchecks, additional_contexts for service deps, secrets via build mounts (not COPY), reproducible builds. Stack examples: Node 22, Python 3.13 (uv), PHP 8.4 (FPM + Nginx)."
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -86,7 +86,16 @@ RUN apk add --no-cache libzip-dev oniguruma-dev icu-dev \
|
|
|
86
86
|
&& rm -rf /var/cache/apk/*
|
|
87
87
|
WORKDIR /var/www/html
|
|
88
88
|
COPY --from=vendor /app/vendor ./vendor
|
|
89
|
-
|
|
89
|
+
# Only copy what the runtime actually needs (never .env, tests, .git, node_modules, etc.)
|
|
90
|
+
COPY --chown=www-data:www-data artisan composer.json composer.lock ./
|
|
91
|
+
COPY --chown=www-data:www-data app/ ./app/
|
|
92
|
+
COPY --chown=www-data:www-data bootstrap/ ./bootstrap/
|
|
93
|
+
COPY --chown=www-data:www-data config/ ./config/
|
|
94
|
+
COPY --chown=www-data:www-data database/ ./database/
|
|
95
|
+
COPY --chown=www-data:www-data public/ ./public/
|
|
96
|
+
COPY --chown=www-data:www-data resources/ ./resources/
|
|
97
|
+
COPY --chown=www-data:www-data routes/ ./routes/
|
|
98
|
+
COPY --chown=www-data:www-data storage/ ./storage/
|
|
90
99
|
USER www-data
|
|
91
100
|
EXPOSE 9000
|
|
92
101
|
HEALTHCHECK --interval=30s --timeout=3s CMD php-fpm-healthcheck || exit 1
|
|
@@ -203,3 +212,4 @@ For Kubernetes the Dockerfile HEALTHCHECK is informational only — the cluster
|
|
|
203
212
|
- `security-baseline` (2025-A03) — supply chain: pin actions by SHA, sign images (Sigstore/cosign)
|
|
204
213
|
- `observability` — `/healthz` + `/readyz` semantics
|
|
205
214
|
- `ci-pipelines` — building & pushing in CI
|
|
215
|
+
- `podman-patterns` — rootless execution, Quadlet, `podman play kube`, and daemonless alternative (same Dockerfiles work)
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kubernetes-patterns
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
description: "Kubernetes 2026 production patterns: Pod Security Standards (restricted), NetworkPolicy default-deny, graceful shutdown with preStop + SIGTERM draining, zero-downtime rolling updates (maxUnavailable:0 + readiness), HPA with custom metrics, PDBs, topology spread, resource QoS, and day-2 readiness checklist. Use when deploying to EKS, GKE, AKS or self-managed clusters."
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Kubernetes Patterns (2026 Production)
|
|
8
|
+
|
|
9
|
+
**Invoke when writing Deployments, Services, NetworkPolicies, HPA, PodDisruptionBudgets, or any workload manifest for production clusters.**
|
|
10
|
+
|
|
11
|
+
> 2026 reality: Pod Security Standards (PSS) + NetworkPolicy default-deny is the baseline. Zero-downtime requires readiness probe + preStop sleep + `maxUnavailable: 0`. Graceful shutdown is mandatory — pods that ignore SIGTERM cause request drops during rollouts and drains.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 1. The Day-2 Readiness Checklist (Non-Negotiable)
|
|
16
|
+
|
|
17
|
+
Every production workload must have:
|
|
18
|
+
|
|
19
|
+
| Control | Why | How |
|
|
20
|
+
|---------|-----|-----|
|
|
21
|
+
| **Readiness probe** | Traffic only reaches healthy pods | `httpGet /readyz` or `exec` |
|
|
22
|
+
| **Liveness probe** | Restart stuck containers | `httpGet /livez` |
|
|
23
|
+
| **Startup probe** (slow apps) | Give slow containers time to boot | `httpGet /startupz` |
|
|
24
|
+
| **Resource requests + limits** | QoS class + scheduling | `requests.memory/cpu`, `limits` |
|
|
25
|
+
| **PodDisruptionBudget** | Survive voluntary disruptions (drains) | `minAvailable: 1` or `maxUnavailable: 1` |
|
|
26
|
+
| **Topology spread** | High availability across zones/nodes | `topologySpreadConstraints` |
|
|
27
|
+
| **SecurityContext** | Non-root + read-only + drop ALL | See §Security |
|
|
28
|
+
| **NetworkPolicy** | Default-deny + explicit allow | See §NetworkPolicy |
|
|
29
|
+
| **Graceful shutdown** | Drain in-flight requests on SIGTERM | `preStop` + handler |
|
|
30
|
+
| **Rolling update strategy** | Zero downtime | `maxUnavailable: 0`, `maxSurge: 1` |
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 2. Zero-Downtime Rolling Update (The 2026 Pattern)
|
|
35
|
+
|
|
36
|
+
```yaml
|
|
37
|
+
apiVersion: apps/v1
|
|
38
|
+
kind: Deployment
|
|
39
|
+
metadata:
|
|
40
|
+
name: api
|
|
41
|
+
namespace: production
|
|
42
|
+
spec:
|
|
43
|
+
replicas: 3
|
|
44
|
+
strategy:
|
|
45
|
+
type: RollingUpdate
|
|
46
|
+
rollingUpdate:
|
|
47
|
+
maxUnavailable: 0 # Never drop below desired replicas
|
|
48
|
+
maxSurge: 1 # Add one extra pod during rollout
|
|
49
|
+
minReadySeconds: 10 # Wait before marking new pod "ready"
|
|
50
|
+
selector:
|
|
51
|
+
matchLabels:
|
|
52
|
+
app: api
|
|
53
|
+
template:
|
|
54
|
+
metadata:
|
|
55
|
+
labels:
|
|
56
|
+
app: api
|
|
57
|
+
spec:
|
|
58
|
+
terminationGracePeriodSeconds: 45 # > preStop sleep + longest request
|
|
59
|
+
containers:
|
|
60
|
+
- name: api
|
|
61
|
+
image: myorg/api:v1.2.3
|
|
62
|
+
ports:
|
|
63
|
+
- containerPort: 3000
|
|
64
|
+
readinessProbe:
|
|
65
|
+
httpGet:
|
|
66
|
+
path: /readyz
|
|
67
|
+
port: 3000
|
|
68
|
+
initialDelaySeconds: 5
|
|
69
|
+
periodSeconds: 5
|
|
70
|
+
failureThreshold: 3
|
|
71
|
+
livenessProbe:
|
|
72
|
+
httpGet:
|
|
73
|
+
path: /livez
|
|
74
|
+
port: 3000
|
|
75
|
+
initialDelaySeconds: 15
|
|
76
|
+
periodSeconds: 10
|
|
77
|
+
failureThreshold: 3
|
|
78
|
+
lifecycle:
|
|
79
|
+
preStop:
|
|
80
|
+
exec:
|
|
81
|
+
command: ["sh", "-c", "sleep 10"] # Give kube-proxy time to remove endpoint
|
|
82
|
+
resources:
|
|
83
|
+
requests:
|
|
84
|
+
memory: "256Mi"
|
|
85
|
+
cpu: "250m"
|
|
86
|
+
limits:
|
|
87
|
+
memory: "512Mi"
|
|
88
|
+
cpu: "500m"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Why the preStop sleep?**
|
|
92
|
+
|
|
93
|
+
When a pod is deleted, Kubernetes removes it from Endpoints **asynchronously**. For a few seconds, traffic can still arrive at a `Terminating` pod. The `sleep 10` in `preStop` delays SIGTERM long enough for endpoint propagation to finish.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## 3. Graceful Shutdown in Code (Node.js / Python / PHP)
|
|
98
|
+
|
|
99
|
+
Your application **must** handle `SIGTERM` and stop accepting new connections + drain in-flight requests.
|
|
100
|
+
|
|
101
|
+
### Node.js (Express / Fastify)
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
const server = app.listen(3000);
|
|
105
|
+
|
|
106
|
+
process.on('SIGTERM', () => {
|
|
107
|
+
server.close(() => {
|
|
108
|
+
// Close DB connections, flush logs, etc.
|
|
109
|
+
process.exit(0);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Python (FastAPI / Uvicorn)
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
import signal
|
|
118
|
+
import sys
|
|
119
|
+
from contextlib import asynccontextmanager
|
|
120
|
+
|
|
121
|
+
@asynccontextmanager
|
|
122
|
+
async def lifespan(app: FastAPI):
|
|
123
|
+
yield
|
|
124
|
+
# Cleanup on shutdown
|
|
125
|
+
await db.disconnect()
|
|
126
|
+
|
|
127
|
+
app = FastAPI(lifespan=lifespan)
|
|
128
|
+
|
|
129
|
+
def handle_sigterm(signum, frame):
|
|
130
|
+
sys.exit(0)
|
|
131
|
+
|
|
132
|
+
signal.signal(signal.SIGTERM, handle_sigterm)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### PHP (Laravel Octane / FrankenPHP)
|
|
136
|
+
|
|
137
|
+
Laravel Octane already handles graceful shutdown. Just make sure your services are stateless between requests (see `laravel-octane-inertia` skill).
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 4. Pod Security Standards (Restricted Profile)
|
|
142
|
+
|
|
143
|
+
Apply at namespace level:
|
|
144
|
+
|
|
145
|
+
```yaml
|
|
146
|
+
apiVersion: v1
|
|
147
|
+
kind: Namespace
|
|
148
|
+
metadata:
|
|
149
|
+
name: production
|
|
150
|
+
labels:
|
|
151
|
+
pod-security.kubernetes.io/enforce: restricted
|
|
152
|
+
pod-security.kubernetes.io/audit: restricted
|
|
153
|
+
pod-security.kubernetes.io/warn: restricted
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**What `restricted` enforces:**
|
|
157
|
+
|
|
158
|
+
- No privileged containers
|
|
159
|
+
- No hostNetwork / hostPID / hostIPC
|
|
160
|
+
- No hostPath volumes
|
|
161
|
+
- Must run as non-root (`runAsNonRoot: true`)
|
|
162
|
+
- Must drop ALL capabilities
|
|
163
|
+
- `readOnlyRootFilesystem: true` (recommended)
|
|
164
|
+
- `allowPrivilegeEscalation: false`
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## 5. NetworkPolicy — Default Deny + Explicit Allow
|
|
169
|
+
|
|
170
|
+
```yaml
|
|
171
|
+
apiVersion: networking.k8s.io/v1
|
|
172
|
+
kind: NetworkPolicy
|
|
173
|
+
metadata:
|
|
174
|
+
name: default-deny-all
|
|
175
|
+
namespace: production
|
|
176
|
+
spec:
|
|
177
|
+
podSelector: {}
|
|
178
|
+
policyTypes:
|
|
179
|
+
- Ingress
|
|
180
|
+
- Egress
|
|
181
|
+
---
|
|
182
|
+
apiVersion: networking.k8s.io/v1
|
|
183
|
+
kind: NetworkPolicy
|
|
184
|
+
metadata:
|
|
185
|
+
name: allow-api
|
|
186
|
+
namespace: production
|
|
187
|
+
spec:
|
|
188
|
+
podSelector:
|
|
189
|
+
matchLabels:
|
|
190
|
+
app: api
|
|
191
|
+
policyTypes:
|
|
192
|
+
- Ingress
|
|
193
|
+
- Egress
|
|
194
|
+
ingress:
|
|
195
|
+
- from:
|
|
196
|
+
- namespaceSelector:
|
|
197
|
+
matchLabels:
|
|
198
|
+
name: ingress-nginx
|
|
199
|
+
ports:
|
|
200
|
+
- protocol: TCP
|
|
201
|
+
port: 3000
|
|
202
|
+
egress:
|
|
203
|
+
- to:
|
|
204
|
+
- podSelector:
|
|
205
|
+
matchLabels:
|
|
206
|
+
app: postgres
|
|
207
|
+
ports:
|
|
208
|
+
- protocol: TCP
|
|
209
|
+
port: 5432
|
|
210
|
+
- to: [] # Allow DNS egress
|
|
211
|
+
ports:
|
|
212
|
+
- protocol: UDP
|
|
213
|
+
port: 53
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## 6. HorizontalPodAutoscaler + PodDisruptionBudget
|
|
219
|
+
|
|
220
|
+
```yaml
|
|
221
|
+
apiVersion: autoscaling/v2
|
|
222
|
+
kind: HorizontalPodAutoscaler
|
|
223
|
+
metadata:
|
|
224
|
+
name: api
|
|
225
|
+
namespace: production
|
|
226
|
+
spec:
|
|
227
|
+
scaleTargetRef:
|
|
228
|
+
apiVersion: apps/v1
|
|
229
|
+
kind: Deployment
|
|
230
|
+
name: api
|
|
231
|
+
minReplicas: 3
|
|
232
|
+
maxReplicas: 20
|
|
233
|
+
metrics:
|
|
234
|
+
- type: Resource
|
|
235
|
+
resource:
|
|
236
|
+
name: cpu
|
|
237
|
+
target:
|
|
238
|
+
type: Utilization
|
|
239
|
+
averageUtilization: 60
|
|
240
|
+
- type: Pods
|
|
241
|
+
pods:
|
|
242
|
+
metric:
|
|
243
|
+
name: http_requests_per_second
|
|
244
|
+
target:
|
|
245
|
+
type: AverageValue
|
|
246
|
+
averageValue: "1000"
|
|
247
|
+
---
|
|
248
|
+
apiVersion: policy/v1
|
|
249
|
+
kind: PodDisruptionBudget
|
|
250
|
+
metadata:
|
|
251
|
+
name: api
|
|
252
|
+
namespace: production
|
|
253
|
+
spec:
|
|
254
|
+
minAvailable: 2
|
|
255
|
+
selector:
|
|
256
|
+
matchLabels:
|
|
257
|
+
app: api
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## 7. SecurityContext (Every Pod)
|
|
263
|
+
|
|
264
|
+
```yaml
|
|
265
|
+
securityContext:
|
|
266
|
+
runAsNonRoot: true
|
|
267
|
+
runAsUser: 10001
|
|
268
|
+
runAsGroup: 10001
|
|
269
|
+
fsGroup: 10001
|
|
270
|
+
seccompProfile:
|
|
271
|
+
type: RuntimeDefault
|
|
272
|
+
containers:
|
|
273
|
+
- name: app
|
|
274
|
+
securityContext:
|
|
275
|
+
allowPrivilegeEscalation: false
|
|
276
|
+
readOnlyRootFilesystem: true
|
|
277
|
+
capabilities:
|
|
278
|
+
drop: ["ALL"]
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## FORBIDDEN
|
|
284
|
+
|
|
285
|
+
| Pattern | Reason |
|
|
286
|
+
|---------|--------|
|
|
287
|
+
| `maxUnavailable: 1` on user-facing services | Drops capacity during rollout |
|
|
288
|
+
| No readiness probe | Traffic can hit pods that are still starting |
|
|
289
|
+
| Ignoring SIGTERM | Requests dropped during drains/rollouts |
|
|
290
|
+
| `runAsUser: 0` or `privileged: true` | Container escape risk |
|
|
291
|
+
| No NetworkPolicy | Lateral movement after compromise |
|
|
292
|
+
| `latest` tag | Unreproducible, hard to rollback |
|
|
293
|
+
| No resource limits | Noisy neighbor + OOMKills |
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## See Also
|
|
298
|
+
|
|
299
|
+
- `podman-patterns` — rootless local development that mirrors production security posture
|
|
300
|
+
- `docker-patterns` — building the images that run on Kubernetes
|
|
301
|
+
- `secrets-management` — how to mount secrets without baking them into images
|
|
302
|
+
- `ci-pipelines` — GitOps deployment patterns with Argo CD / Flux
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## Memory Optimization Trigger
|
|
307
|
+
|
|
308
|
+
If this skill grows beyond ~300 lines or accumulates many near-duplicate Deployment examples, run:
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
npx start-vibing-stacks memory optimize --dry-run
|
|
312
|
+
```
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: laravel-octane-inertia
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
description: "Laravel 12 + Octane (FrankenPHP/Swoole) + Inertia.js + React 19 production patterns for 2026. Covers stateless architecture, worker recycling (--max-requests), scoped bindings, Octane::concurrently(), Inertia SSR, React Server Components inside Inertia pages, queue integration with Horizon, and FrankenPHP as the recommended driver."
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Laravel 12 + Octane + Inertia + React 19 (2026)
|
|
8
|
+
|
|
9
|
+
**Invoke when building high-performance Laravel applications with Inertia + React frontend and Octane workers.**
|
|
10
|
+
|
|
11
|
+
> 2026 reality: **FrankenPHP is the recommended Octane driver** for most teams. Octane gives 5-10x throughput, but requires strict stateless code. Inertia + React 19 lets you build SPA-like UX without maintaining a separate API layer.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 1. Octane Mental Model
|
|
16
|
+
|
|
17
|
+
Octane boots Laravel **once** and reuses the application instance across thousands of requests.
|
|
18
|
+
|
|
19
|
+
**Consequences:**
|
|
20
|
+
|
|
21
|
+
- Singletons and static properties live forever in the worker.
|
|
22
|
+
- Request-scoped data must be re-created on every request.
|
|
23
|
+
- Memory leaks accumulate until the worker is recycled.
|
|
24
|
+
|
|
25
|
+
**Rule:** Treat every request as if it were the first request the worker has ever seen.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 2. Stateless Architecture Rules
|
|
30
|
+
|
|
31
|
+
### Never do this
|
|
32
|
+
|
|
33
|
+
```php
|
|
34
|
+
// Bad — lives in memory forever
|
|
35
|
+
private static $user;
|
|
36
|
+
|
|
37
|
+
public function __construct()
|
|
38
|
+
{
|
|
39
|
+
self::$user = auth()->user(); // captured once
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Do this instead
|
|
44
|
+
|
|
45
|
+
```php
|
|
46
|
+
// Good — resolved at runtime
|
|
47
|
+
public function handle(Request $request)
|
|
48
|
+
{
|
|
49
|
+
$user = $request->user(); // fresh on every request
|
|
50
|
+
// or
|
|
51
|
+
$user = app('auth')->user();
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Use scoped bindings for request-scoped services
|
|
56
|
+
|
|
57
|
+
```php
|
|
58
|
+
// AppServiceProvider
|
|
59
|
+
$this->app->scoped(ExpensiveService::class, function () {
|
|
60
|
+
return new ExpensiveService(...);
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 3. Worker Recycling (`--max-requests`)
|
|
67
|
+
|
|
68
|
+
Always set a recycle limit:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
php artisan octane:start --server=frankenphp --max-requests=1000
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
This forces workers to die after 1000 requests, releasing any leaked memory.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 4. Inertia + React 19 (No Separate API)
|
|
79
|
+
|
|
80
|
+
Inertia lets you use Laravel routes + controllers while rendering React components.
|
|
81
|
+
|
|
82
|
+
```php
|
|
83
|
+
// routes/web.php
|
|
84
|
+
Route::get('/dashboard', function () {
|
|
85
|
+
return Inertia::render('Dashboard', [
|
|
86
|
+
'user' => auth()->user(),
|
|
87
|
+
'posts' => Post::with('author')->latest()->take(10)->get(),
|
|
88
|
+
]);
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
// resources/js/Pages/Dashboard.tsx (React 19 Server Component inside Inertia)
|
|
94
|
+
export default function Dashboard({ user, posts }: PageProps) {
|
|
95
|
+
return (
|
|
96
|
+
<div>
|
|
97
|
+
<h1>Welcome, {user.name}</h1>
|
|
98
|
+
<PostList posts={posts} />
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Inertia handles the SSR + hydration automatically.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 5. React Server Components inside Inertia
|
|
109
|
+
|
|
110
|
+
You can use React Server Components inside Inertia pages (Next.js-style data fetching).
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
// resources/js/Pages/Posts/Show.tsx
|
|
114
|
+
import { getPost } from '@/actions/posts';
|
|
115
|
+
|
|
116
|
+
export default async function Show({ id }: { id: number }) {
|
|
117
|
+
const post = await getPost(id); // runs on the server
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<article>
|
|
121
|
+
<h1>{post.title}</h1>
|
|
122
|
+
<Content>{post.body}</Content>
|
|
123
|
+
</article>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## 6. Concurrent Tasks with `Octane::concurrently()`
|
|
131
|
+
|
|
132
|
+
```php
|
|
133
|
+
use Laravel\Octane\Facades\Octane;
|
|
134
|
+
|
|
135
|
+
[$user, $posts, $notifications] = Octane::concurrently([
|
|
136
|
+
fn () => User::find($id),
|
|
137
|
+
fn () => Post::where('user_id', $id)->get(),
|
|
138
|
+
fn () => Notification::where('user_id', $id)->get(),
|
|
139
|
+
], 5000); // 5 second timeout
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
This runs the closures in parallel using Swoole/RoadRunner task workers.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## 7. Queues + Horizon (Move Heavy Work Out of Workers)
|
|
147
|
+
|
|
148
|
+
Anything that takes > 200ms should be queued.
|
|
149
|
+
|
|
150
|
+
```php
|
|
151
|
+
// In a controller or action
|
|
152
|
+
dispatch(new ProcessVideo($video))->onQueue('videos');
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Monitor with Horizon:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
php artisan horizon
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## 8. FrankenPHP vs Swoole vs RoadRunner (2026 Recommendation)
|
|
164
|
+
|
|
165
|
+
| Driver | Ease of use | Performance | Production maturity | Recommendation |
|
|
166
|
+
|--------|-------------|-------------|---------------------|----------------|
|
|
167
|
+
| **FrankenPHP** | Highest | Excellent | Very high (Caddy-based) | **Default choice** |
|
|
168
|
+
| Swoole | Medium | Highest | High | When you need extreme concurrency |
|
|
169
|
+
| RoadRunner | Medium | Very high | High | When you prefer Go-based workers |
|
|
170
|
+
|
|
171
|
+
**Recommendation:** Start with FrankenPHP unless you have a specific reason not to.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 9. Production Docker + Octane Checklist
|
|
176
|
+
|
|
177
|
+
- Use multi-stage build (see `docker-patterns`)
|
|
178
|
+
- Run as non-root (`www-data` or `10001`)
|
|
179
|
+
- Set `--max-requests=1000`
|
|
180
|
+
- Enable OPcache with `opcache.validate_timestamps=0` and `opcache.jit=tracing`
|
|
181
|
+
- Use `readOnlyRootFilesystem: true` + `emptyDir` for `/tmp` and `storage/framework/cache`
|
|
182
|
+
- Healthcheck on `/healthz`
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## FORBIDDEN
|
|
187
|
+
|
|
188
|
+
| Pattern | Reason |
|
|
189
|
+
|---------|--------|
|
|
190
|
+
| Storing request data in singletons or static properties | Leaks between users |
|
|
191
|
+
| Using `Date::now()` or `Carbon::now()` in service constructors | Frozen time across requests |
|
|
192
|
+
| Not setting `--max-requests` | Memory leaks eventually OOM the worker |
|
|
193
|
+
| Heavy work inside Octane workers | Blocks the event loop |
|
|
194
|
+
| Passing Eloquent models with relations into Inertia props without `only()` | N+1 queries + bloated payloads |
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## See Also
|
|
199
|
+
|
|
200
|
+
- `laravel-api-architecture` — controller / form request / policy / resource structure
|
|
201
|
+
- `docker-patterns` / `podman-patterns` — containerizing Octane apps
|
|
202
|
+
- `task-queues` — Horizon patterns (when implemented)
|
|
203
|
+
- Official Laravel Octane docs
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Memory Optimization Trigger
|
|
208
|
+
|
|
209
|
+
If this skill grows beyond ~250 lines or accumulates duplicate Octane configuration examples, run:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
npx start-vibing-stacks memory optimize --dry-run
|
|
213
|
+
```
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: podman-patterns
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
description: "Podman 2026 patterns: rootless containers (no daemon, no root), Quadlet (systemd units), podman play kube, podman compose (Docker Compose compatibility), podman machine (macOS/Windows), healthchecks, secrets via --secret, distroless images. Replaces Docker daemon in CI and production for security and simplicity."
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Podman Patterns (2026)
|
|
8
|
+
|
|
9
|
+
**Invoke when writing container definitions, systemd services for containers, Kubernetes manifests for single pods, or migrating from Docker daemon.**
|
|
10
|
+
|
|
11
|
+
> 2026 reality: **Rootless is the default**. No container runtime should run as root. Podman + Quadlet is the recommended path on Linux for production services. `podman play kube` replaces simple `kubectl apply` for single-pod workloads. Dockerfiles are fully compatible.
|
|
12
|
+
|
|
13
|
+
## Why Podman in 2026
|
|
14
|
+
|
|
15
|
+
| Aspect | Docker | Podman | Winner |
|
|
16
|
+
|--------|--------|--------|--------|
|
|
17
|
+
| Daemon | Required (root) | None | Podman |
|
|
18
|
+
| Root | Required for daemon | Rootless by default | Podman |
|
|
19
|
+
| Kubernetes | Separate (kind/minikube) | `podman play kube` | Podman |
|
|
20
|
+
| Systemd | Manual units | Quadlet (native) | Podman |
|
|
21
|
+
| macOS/Windows | Docker Desktop | `podman machine` | Tie |
|
|
22
|
+
| CI | Docker-in-Docker | Native rootless | Podman |
|
|
23
|
+
|
|
24
|
+
**Rule:** If the target is Linux (server or CI), prefer Podman. If you need Docker Compose syntax, use `podman compose` — it is a drop-in replacement.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 1. Rootless + Distroless (Node.js example)
|
|
29
|
+
|
|
30
|
+
```dockerfile
|
|
31
|
+
# syntax=docker/dockerfile:1.7
|
|
32
|
+
ARG NODE_VERSION=22.11.0
|
|
33
|
+
|
|
34
|
+
FROM node:${NODE_VERSION}-alpine AS builder
|
|
35
|
+
WORKDIR /app
|
|
36
|
+
COPY package*.json ./
|
|
37
|
+
RUN --mount=type=cache,target=/root/.npm \
|
|
38
|
+
npm ci --ignore-scripts
|
|
39
|
+
COPY . .
|
|
40
|
+
RUN npm run build && npm prune --omit=dev
|
|
41
|
+
|
|
42
|
+
FROM gcr.io/distroless/nodejs22-debian12:nonroot
|
|
43
|
+
WORKDIR /app
|
|
44
|
+
COPY --from=builder --chown=nonroot:nonroot /app/node_modules ./node_modules
|
|
45
|
+
COPY --from=builder --chown=nonroot:nonroot /app/dist ./dist
|
|
46
|
+
COPY --from=builder --chown=nonroot:nonroot /app/package.json ./
|
|
47
|
+
USER nonroot
|
|
48
|
+
EXPOSE 3000
|
|
49
|
+
ENV NODE_ENV=production
|
|
50
|
+
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
|
51
|
+
CMD ["/nodejs/bin/node", "-e", "fetch('http://127.0.0.1:3000/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
|
|
52
|
+
CMD ["dist/server.js"]
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Run rootless:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
podman run -d --name app \
|
|
59
|
+
--user 10001:10001 \
|
|
60
|
+
--read-only \
|
|
61
|
+
--security-opt no-new-privileges \
|
|
62
|
+
-p 3000:3000 \
|
|
63
|
+
localhost/myapp:latest
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 2. Quadlet — The 2026 Way to Run Containers as Services
|
|
69
|
+
|
|
70
|
+
Instead of writing systemd units by hand, use **Quadlet** (`.container` files).
|
|
71
|
+
|
|
72
|
+
### Example: `myapp.container`
|
|
73
|
+
|
|
74
|
+
```ini
|
|
75
|
+
[Unit]
|
|
76
|
+
Description=My Node.js App
|
|
77
|
+
After=network-online.target
|
|
78
|
+
Wants=network-online.target
|
|
79
|
+
|
|
80
|
+
[Container]
|
|
81
|
+
Image=localhost/myapp:latest
|
|
82
|
+
ContainerName=myapp
|
|
83
|
+
PublishPort=3000:3000
|
|
84
|
+
Environment=NODE_ENV=production
|
|
85
|
+
HealthCmd=/nodejs/bin/node -e "fetch('http://127.0.0.1:3000/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
|
|
86
|
+
HealthInterval=30s
|
|
87
|
+
HealthTimeout=3s
|
|
88
|
+
HealthStartPeriod=5s
|
|
89
|
+
HealthRetries=3
|
|
90
|
+
ReadOnly=true
|
|
91
|
+
User=10001
|
|
92
|
+
Group=10001
|
|
93
|
+
SecurityLabelDisable=false
|
|
94
|
+
|
|
95
|
+
[Service]
|
|
96
|
+
Restart=always
|
|
97
|
+
RestartSec=5
|
|
98
|
+
|
|
99
|
+
[Install]
|
|
100
|
+
WantedBy=multi-user.target
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Install:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
sudo cp myapp.container /etc/containers/systemd/
|
|
107
|
+
sudo systemctl daemon-reload
|
|
108
|
+
sudo systemctl start myapp.service
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Quadlet automatically creates the proper systemd service, handles restarts, and integrates with `podman`.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## 3. `podman play kube` — Single-Pod Kubernetes
|
|
116
|
+
|
|
117
|
+
For simple workloads, avoid full Kubernetes. Use a Pod manifest:
|
|
118
|
+
|
|
119
|
+
```yaml
|
|
120
|
+
apiVersion: v1
|
|
121
|
+
kind: Pod
|
|
122
|
+
metadata:
|
|
123
|
+
name: myapp
|
|
124
|
+
labels:
|
|
125
|
+
app: myapp
|
|
126
|
+
spec:
|
|
127
|
+
containers:
|
|
128
|
+
- name: app
|
|
129
|
+
image: localhost/myapp:latest
|
|
130
|
+
ports:
|
|
131
|
+
- containerPort: 3000
|
|
132
|
+
env:
|
|
133
|
+
- name: NODE_ENV
|
|
134
|
+
value: production
|
|
135
|
+
securityContext:
|
|
136
|
+
runAsNonRoot: true
|
|
137
|
+
runAsUser: 10001
|
|
138
|
+
readOnlyRootFilesystem: true
|
|
139
|
+
livenessProbe:
|
|
140
|
+
exec:
|
|
141
|
+
command:
|
|
142
|
+
- /nodejs/bin/node
|
|
143
|
+
- -e
|
|
144
|
+
- "fetch('http://127.0.0.1:3000/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
|
|
145
|
+
initialDelaySeconds: 5
|
|
146
|
+
periodSeconds: 30
|
|
147
|
+
restartPolicy: Always
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Deploy:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
podman play kube myapp.yaml
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Stop:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
podman play kube --down myapp.yaml
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
This is dramatically simpler than Helm or full K8s for single-service apps.
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## 4. Secrets (Better Than Docker Build Secrets)
|
|
167
|
+
|
|
168
|
+
Podman supports `--secret` natively:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
podman run --secret id=stripe_key,src=.env.d/local/secrets/stripe.key \
|
|
172
|
+
-e STRIPE_SECRET_KEY_FILE=/run/secrets/stripe_key \
|
|
173
|
+
localhost/myapp
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Inside the container, the secret is available at `/run/secrets/stripe_key` and is never stored in the image layer.
|
|
177
|
+
|
|
178
|
+
See `secrets-management` for the full `.env.d/` + secret file pattern.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## 5. Podman Machine (macOS / Windows)
|
|
183
|
+
|
|
184
|
+
On non-Linux hosts:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
podman machine init
|
|
188
|
+
podman machine start
|
|
189
|
+
podman machine ssh # for debugging
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Use the same Dockerfiles and Quadlet files — Podman abstracts the VM.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## 6. Migration from Docker
|
|
197
|
+
|
|
198
|
+
| Docker Command | Podman Equivalent | Notes |
|
|
199
|
+
|----------------|-------------------|-------|
|
|
200
|
+
| `docker build` | `podman build` | Same syntax |
|
|
201
|
+
| `docker compose up` | `podman compose up` | Full compatibility |
|
|
202
|
+
| `docker run --user` | `podman run --user` | Rootless is default |
|
|
203
|
+
| Manual systemd unit | Quadlet `.container` | Much simpler |
|
|
204
|
+
| `docker ps` | `podman ps` | Identical output |
|
|
205
|
+
| `docker system prune` | `podman system prune` | Same |
|
|
206
|
+
|
|
207
|
+
**Rule:** Keep using `docker-compose.yml` if you want — just run it with `podman compose`. The compose file is unchanged.
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## 7. Integration With Other Skills
|
|
212
|
+
|
|
213
|
+
- `secrets-management` — Use `.env.d/` + `--secret` mounts.
|
|
214
|
+
- `security-baseline` — Rootless + read-only + no-new-privileges is the baseline.
|
|
215
|
+
- `ci-pipelines` — Run `podman` instead of `docker` in GitHub Actions (no DinD needed).
|
|
216
|
+
- `docker-patterns` — Podman can consume the exact same Dockerfiles. The two skills are complementary.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## FORBIDDEN
|
|
221
|
+
|
|
222
|
+
| Pattern | Reason |
|
|
223
|
+
|---------|--------|
|
|
224
|
+
| Running Podman as root | Defeats the entire security model |
|
|
225
|
+
| Using `sudo podman` in scripts | Indicates misconfiguration |
|
|
226
|
+
| Storing secrets in image layers | Use `--secret` or mounted files |
|
|
227
|
+
| Running containers with `--privileged` | Never needed with rootless + proper capabilities |
|
|
228
|
+
| Ignoring healthchecks | Quadlet and `podman play kube` both support them natively |
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Pre-Deployment Checklist
|
|
233
|
+
|
|
234
|
+
- [ ] Image built with multi-stage + distroless/chiseled
|
|
235
|
+
- [ ] Non-root user (10001:10001 or higher)
|
|
236
|
+
- [ ] Read-only root filesystem
|
|
237
|
+
- [ ] Healthcheck defined
|
|
238
|
+
- [ ] Secrets passed via `--secret` (not ENV or COPY)
|
|
239
|
+
- [ ] If Linux server → Quadlet `.container` file
|
|
240
|
+
- [ ] If simple K8s workload → `podman play kube` manifest
|
|
241
|
+
- [ ] `.dockerignore` present (same as Docker)
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## See Also
|
|
246
|
+
|
|
247
|
+
- `docker-patterns` — the Docker side of the same story (compatible)
|
|
248
|
+
- `secrets-management` — how to structure `.env.d/` and secret files
|
|
249
|
+
- `security-baseline` — rootless is now table stakes
|
|
250
|
+
- `ci-pipelines` — rootless CI runners
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Memory Optimization Trigger
|
|
255
|
+
|
|
256
|
+
If this skill grows beyond ~250 lines or accumulates many near-duplicate Quadlet examples, run:
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
npx start-vibing-stacks memory optimize --dry-run
|
|
260
|
+
```
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: react-server-components
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
description: "React 19 Server Components (RSC) production patterns for 2026: Server Components as default, 'use client' only on interactive leaves, streaming with Suspense, parallel data fetching with Promise.all, serialization rules (no functions/classes across boundary), layered caching (React cache + framework unstable_cache), Server Actions for mutations. Use with Next.js 16+ App Router."
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# React Server Components (2026 Production)
|
|
8
|
+
|
|
9
|
+
**Invoke when building any React 19+ application using the App Router (Next.js 16+, Remix RSC, TanStack Start, etc.).**
|
|
10
|
+
|
|
11
|
+
> 2026 reality: Server Components are the default. 40%+ bundle size reduction is common when done correctly. The mental model is: **Server Components fetch and render on the server. Client Components add interactivity. Keep the boundary small and explicit.**
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 1. The Core Mental Model
|
|
16
|
+
|
|
17
|
+
| Component Type | Runs Where | Bundle Impact | Data Fetching | Interactivity |
|
|
18
|
+
|----------------|------------|---------------|---------------|---------------|
|
|
19
|
+
| **Server Component** (default) | Server | 0 KB to client | Direct (DB, API, FS) | None |
|
|
20
|
+
| **Client Component** (`'use client'`) | Client | Full JS shipped | Via props or API | Full (useState, useEffect, etc.) |
|
|
21
|
+
|
|
22
|
+
**Rule of thumb:**
|
|
23
|
+
- Default to Server Component.
|
|
24
|
+
- Add `'use client'` **only** when you need interactivity, browser APIs, or hooks.
|
|
25
|
+
- Keep Client Components as **small leaf components** near the user interaction.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 2. Data Fetching — No Waterfalls
|
|
30
|
+
|
|
31
|
+
**Wrong (sequential):**
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
// app/page.tsx
|
|
35
|
+
export default async function Page() {
|
|
36
|
+
const user = await getUser(); // 200ms
|
|
37
|
+
const posts = await getPosts(user); // waits for user
|
|
38
|
+
const comments = await getComments(); // waits for posts
|
|
39
|
+
return <Dashboard ... />;
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Correct (parallel):**
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
export default async function Page() {
|
|
47
|
+
const [user, posts, comments] = await Promise.all([
|
|
48
|
+
getUser(),
|
|
49
|
+
getPosts(),
|
|
50
|
+
getComments(),
|
|
51
|
+
]);
|
|
52
|
+
return <Dashboard user={user} posts={posts} comments={comments} />;
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Or use React's `cache()` for deduplication across the component tree.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 3. Passing Data Across the Server → Client Boundary
|
|
61
|
+
|
|
62
|
+
**Only plain serializable data is allowed:**
|
|
63
|
+
|
|
64
|
+
- Primitives (`string`, `number`, `boolean`, `null`)
|
|
65
|
+
- Plain objects and arrays
|
|
66
|
+
- Dates (serialized as ISO strings in Next.js)
|
|
67
|
+
- Promises (React will suspend)
|
|
68
|
+
|
|
69
|
+
**Forbidden:**
|
|
70
|
+
|
|
71
|
+
- Functions
|
|
72
|
+
- Class instances
|
|
73
|
+
- `Map`, `Set`, `Date` (raw), `Error`, `RegExp`
|
|
74
|
+
- React elements (JSX) from Server to Client
|
|
75
|
+
|
|
76
|
+
**Correct pattern:**
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
// Server Component
|
|
80
|
+
const user = await getUser();
|
|
81
|
+
const safeUser = {
|
|
82
|
+
id: user.id,
|
|
83
|
+
name: user.name,
|
|
84
|
+
createdAt: user.createdAt.toISOString(),
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
return <UserProfile user={safeUser} />; // Client Component
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 4. Streaming with Suspense
|
|
93
|
+
|
|
94
|
+
Slow data should be wrapped in `<Suspense>` so the rest of the page streams immediately.
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
import { Suspense } from 'react';
|
|
98
|
+
import { Comments } from './comments';
|
|
99
|
+
|
|
100
|
+
export default function Page() {
|
|
101
|
+
return (
|
|
102
|
+
<div>
|
|
103
|
+
<Header /> {/* streams first */}
|
|
104
|
+
<Suspense fallback={<CommentsSkeleton />}>
|
|
105
|
+
<Comments /> {/* streams when ready */}
|
|
106
|
+
</Suspense>
|
|
107
|
+
</div>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## 5. Server Actions (`'use server'`)
|
|
115
|
+
|
|
116
|
+
For mutations, use Server Actions instead of API routes.
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
// app/actions.ts
|
|
120
|
+
'use server';
|
|
121
|
+
|
|
122
|
+
export async function createPost(formData: FormData) {
|
|
123
|
+
const title = formData.get('title');
|
|
124
|
+
await db.post.create({ data: { title } });
|
|
125
|
+
revalidatePath('/posts');
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
// app/new-post.tsx (Client Component)
|
|
131
|
+
'use client';
|
|
132
|
+
|
|
133
|
+
import { createPost } from './actions';
|
|
134
|
+
|
|
135
|
+
export function NewPostForm() {
|
|
136
|
+
return (
|
|
137
|
+
<form action={createPost}>
|
|
138
|
+
<input name="title" />
|
|
139
|
+
<button type="submit">Create</button>
|
|
140
|
+
</form>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## 6. Caching Layers (Next.js 16+)
|
|
148
|
+
|
|
149
|
+
1. **React `cache()`** — per-request memoization
|
|
150
|
+
2. **`unstable_cache`** — shared across requests (with tags)
|
|
151
|
+
3. **Route segment config** — `export const revalidate = 3600`
|
|
152
|
+
4. **Dynamic functions** (`cookies()`, `headers()`) — opt out of static rendering
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## 7. Common Pitfalls in 2026
|
|
157
|
+
|
|
158
|
+
| Mistake | Fix |
|
|
159
|
+
|---------|-----|
|
|
160
|
+
| Putting `'use client'` at the root layout | Only add it to interactive leaves |
|
|
161
|
+
| Passing functions or class instances as props | Serialize on the server first |
|
|
162
|
+
| Sequential `await` in Server Components | Use `Promise.all` |
|
|
163
|
+
| Using Context at the root layout | Context only works inside Client Components |
|
|
164
|
+
| Forgetting to handle loading states with Suspense | Wrap slow sections in `<Suspense>` |
|
|
165
|
+
| Using `Date` objects directly across boundary | Convert to ISO string on the server |
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## 8. When to Use Client Components
|
|
170
|
+
|
|
171
|
+
Only when you need:
|
|
172
|
+
|
|
173
|
+
- `useState`, `useEffect`, `useReducer`, `useContext`
|
|
174
|
+
- Browser APIs (`localStorage`, `window`, `document`)
|
|
175
|
+
- Event handlers (`onClick`, `onSubmit`)
|
|
176
|
+
- Third-party libraries that require the browser (many chart libs, drag-and-drop, etc.)
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## See Also
|
|
181
|
+
|
|
182
|
+
- `nextjs-app-router` — framework-specific patterns on top of RSC
|
|
183
|
+
- `react-patterns` — general React 19 best practices
|
|
184
|
+
- Official React docs: https://react.dev/reference/rsc/server-components
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Memory Optimization Trigger
|
|
189
|
+
|
|
190
|
+
If this skill accumulates many near-duplicate examples or grows beyond ~200 lines, run:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
npx start-vibing-stacks memory optimize --dry-run
|
|
194
|
+
```
|
package/stacks/nodejs/stack.json
CHANGED
|
@@ -81,7 +81,8 @@
|
|
|
81
81
|
"icon": "⚛️",
|
|
82
82
|
"skillsDir": "react",
|
|
83
83
|
"default": true,
|
|
84
|
-
"frameworks": ["nextjs", "express", "fastify", "vanilla"]
|
|
84
|
+
"frameworks": ["nextjs", "express", "fastify", "vanilla"],
|
|
85
|
+
"skills": ["react-server-components"]
|
|
85
86
|
},
|
|
86
87
|
{
|
|
87
88
|
"id": "vue",
|
|
@@ -109,7 +110,9 @@
|
|
|
109
110
|
"bun-runtime",
|
|
110
111
|
"zod-validation",
|
|
111
112
|
"api-security-node",
|
|
112
|
-
"openapi-design"
|
|
113
|
+
"openapi-design",
|
|
114
|
+
"docker-patterns",
|
|
115
|
+
"podman-patterns"
|
|
113
116
|
],
|
|
114
117
|
"requirements": [
|
|
115
118
|
{
|
package/stacks/php/stack.json
CHANGED
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
"laravel-patterns",
|
|
34
34
|
"laravel-octane",
|
|
35
35
|
"laravel-api-architecture",
|
|
36
|
-
"api-security"
|
|
36
|
+
"api-security",
|
|
37
|
+
"laravel-octane-inertia"
|
|
37
38
|
]
|
|
38
39
|
},
|
|
39
40
|
{
|
|
@@ -44,7 +45,8 @@
|
|
|
44
45
|
"skills": [
|
|
45
46
|
"laravel-patterns",
|
|
46
47
|
"laravel-api-architecture",
|
|
47
|
-
"api-security"
|
|
48
|
+
"api-security",
|
|
49
|
+
"laravel-octane-inertia"
|
|
48
50
|
]
|
|
49
51
|
}
|
|
50
52
|
],
|
|
@@ -98,7 +100,9 @@
|
|
|
98
100
|
"api-design",
|
|
99
101
|
"api-security",
|
|
100
102
|
"laravel-api-architecture",
|
|
101
|
-
"openapi-design"
|
|
103
|
+
"openapi-design",
|
|
104
|
+
"docker-patterns",
|
|
105
|
+
"podman-patterns"
|
|
102
106
|
],
|
|
103
107
|
"requirements": [
|
|
104
108
|
{
|