@0xsown/vibe-code-fe 1.0.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/bin/index.js +181 -0
- package/package.json +32 -0
- package/skills/claude-md-improver/SKILL.md +179 -0
- package/skills/claude-md-improver/references/quality-criteria.md +109 -0
- package/skills/claude-md-improver/references/templates.md +253 -0
- package/skills/claude-md-improver/references/update-guidelines.md +150 -0
- package/skills/find-skills/SKILL.md +133 -0
- package/skills/frontend-design/LICENSE.txt +177 -0
- package/skills/frontend-design/SKILL.md +42 -0
- package/skills/next-best-practices/SKILL.md +153 -0
- package/skills/next-best-practices/async-patterns.md +87 -0
- package/skills/next-best-practices/bundling.md +180 -0
- package/skills/next-best-practices/data-patterns.md +297 -0
- package/skills/next-best-practices/debug-tricks.md +105 -0
- package/skills/next-best-practices/directives.md +73 -0
- package/skills/next-best-practices/error-handling.md +227 -0
- package/skills/next-best-practices/file-conventions.md +140 -0
- package/skills/next-best-practices/font.md +245 -0
- package/skills/next-best-practices/functions.md +108 -0
- package/skills/next-best-practices/hydration-error.md +91 -0
- package/skills/next-best-practices/image.md +173 -0
- package/skills/next-best-practices/metadata.md +301 -0
- package/skills/next-best-practices/parallel-routes.md +287 -0
- package/skills/next-best-practices/route-handlers.md +146 -0
- package/skills/next-best-practices/rsc-boundaries.md +159 -0
- package/skills/next-best-practices/runtime-selection.md +39 -0
- package/skills/next-best-practices/scripts.md +141 -0
- package/skills/next-best-practices/self-hosting.md +371 -0
- package/skills/next-best-practices/suspense-boundaries.md +67 -0
- package/skills/next-cache-components/SKILL.md +411 -0
- package/skills/shadcn-ui/README.md +248 -0
- package/skills/shadcn-ui/SKILL.md +326 -0
- package/skills/shadcn-ui/examples/auth-layout.tsx +177 -0
- package/skills/shadcn-ui/examples/data-table.tsx +313 -0
- package/skills/shadcn-ui/examples/form-pattern.tsx +177 -0
- package/skills/shadcn-ui/resources/component-catalog.md +481 -0
- package/skills/shadcn-ui/resources/customization-guide.md +516 -0
- package/skills/shadcn-ui/resources/migration-guide.md +463 -0
- package/skills/shadcn-ui/resources/setup-guide.md +412 -0
- package/skills/shadcn-ui/scripts/verify-setup.sh +134 -0
- package/skills/supabase-postgres-best-practices/AGENTS.md +68 -0
- package/skills/supabase-postgres-best-practices/CLAUDE.md +68 -0
- package/skills/supabase-postgres-best-practices/README.md +116 -0
- package/skills/supabase-postgres-best-practices/SKILL.md +64 -0
- package/skills/supabase-postgres-best-practices/references/advanced-full-text-search.md +55 -0
- package/skills/supabase-postgres-best-practices/references/advanced-jsonb-indexing.md +49 -0
- package/skills/supabase-postgres-best-practices/references/conn-idle-timeout.md +46 -0
- package/skills/supabase-postgres-best-practices/references/conn-limits.md +44 -0
- package/skills/supabase-postgres-best-practices/references/conn-pooling.md +41 -0
- package/skills/supabase-postgres-best-practices/references/conn-prepared-statements.md +46 -0
- package/skills/supabase-postgres-best-practices/references/data-batch-inserts.md +54 -0
- package/skills/supabase-postgres-best-practices/references/data-n-plus-one.md +53 -0
- package/skills/supabase-postgres-best-practices/references/data-pagination.md +50 -0
- package/skills/supabase-postgres-best-practices/references/data-upsert.md +50 -0
- package/skills/supabase-postgres-best-practices/references/lock-advisory.md +56 -0
- package/skills/supabase-postgres-best-practices/references/lock-deadlock-prevention.md +68 -0
- package/skills/supabase-postgres-best-practices/references/lock-short-transactions.md +50 -0
- package/skills/supabase-postgres-best-practices/references/lock-skip-locked.md +54 -0
- package/skills/supabase-postgres-best-practices/references/monitor-explain-analyze.md +45 -0
- package/skills/supabase-postgres-best-practices/references/monitor-pg-stat-statements.md +55 -0
- package/skills/supabase-postgres-best-practices/references/monitor-vacuum-analyze.md +55 -0
- package/skills/supabase-postgres-best-practices/references/query-composite-indexes.md +44 -0
- package/skills/supabase-postgres-best-practices/references/query-covering-indexes.md +40 -0
- package/skills/supabase-postgres-best-practices/references/query-index-types.md +48 -0
- package/skills/supabase-postgres-best-practices/references/query-missing-indexes.md +43 -0
- package/skills/supabase-postgres-best-practices/references/query-partial-indexes.md +45 -0
- package/skills/supabase-postgres-best-practices/references/schema-constraints.md +80 -0
- package/skills/supabase-postgres-best-practices/references/schema-data-types.md +46 -0
- package/skills/supabase-postgres-best-practices/references/schema-foreign-key-indexes.md +59 -0
- package/skills/supabase-postgres-best-practices/references/schema-lowercase-identifiers.md +55 -0
- package/skills/supabase-postgres-best-practices/references/schema-partitioning.md +55 -0
- package/skills/supabase-postgres-best-practices/references/schema-primary-keys.md +61 -0
- package/skills/supabase-postgres-best-practices/references/security-privileges.md +54 -0
- package/skills/supabase-postgres-best-practices/references/security-rls-basics.md +50 -0
- package/skills/supabase-postgres-best-practices/references/security-rls-performance.md +57 -0
- package/skills/tailwind-design-system/SKILL.md +874 -0
- package/skills/vercel-composition-patterns/AGENTS.md +946 -0
- package/skills/vercel-composition-patterns/README.md +60 -0
- package/skills/vercel-composition-patterns/SKILL.md +89 -0
- package/skills/vercel-composition-patterns/rules/architecture-avoid-boolean-props.md +100 -0
- package/skills/vercel-composition-patterns/rules/architecture-compound-components.md +112 -0
- package/skills/vercel-composition-patterns/rules/patterns-children-over-render-props.md +87 -0
- package/skills/vercel-composition-patterns/rules/patterns-explicit-variants.md +100 -0
- package/skills/vercel-composition-patterns/rules/react19-no-forwardref.md +42 -0
- package/skills/vercel-composition-patterns/rules/state-context-interface.md +191 -0
- package/skills/vercel-composition-patterns/rules/state-decouple-implementation.md +113 -0
- package/skills/vercel-composition-patterns/rules/state-lift-state.md +125 -0
- package/skills/vercel-react-best-practices/AGENTS.md +2934 -0
- package/skills/vercel-react-best-practices/README.md +123 -0
- package/skills/vercel-react-best-practices/SKILL.md +136 -0
- package/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/skills/vercel-react-best-practices/rules/advanced-init-once.md +42 -0
- package/skills/vercel-react-best-practices/rules/advanced-use-latest.md +39 -0
- package/skills/vercel-react-best-practices/rules/async-api-routes.md +38 -0
- package/skills/vercel-react-best-practices/rules/async-defer-await.md +80 -0
- package/skills/vercel-react-best-practices/rules/async-dependencies.md +51 -0
- package/skills/vercel-react-best-practices/rules/async-parallel.md +28 -0
- package/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/skills/vercel-react-best-practices/rules/bundle-conditional.md +31 -0
- package/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/skills/vercel-react-best-practices/rules/bundle-preload.md +50 -0
- package/skills/vercel-react-best-practices/rules/client-event-listeners.md +74 -0
- package/skills/vercel-react-best-practices/rules/client-localstorage-schema.md +71 -0
- package/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/skills/vercel-react-best-practices/rules/client-swr-dedup.md +56 -0
- package/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +107 -0
- package/skills/vercel-react-best-practices/rules/js-cache-function-results.md +80 -0
- package/skills/vercel-react-best-practices/rules/js-cache-property-access.md +28 -0
- package/skills/vercel-react-best-practices/rules/js-cache-storage.md +70 -0
- package/skills/vercel-react-best-practices/rules/js-combine-iterations.md +32 -0
- package/skills/vercel-react-best-practices/rules/js-early-exit.md +50 -0
- package/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/skills/vercel-react-best-practices/rules/js-index-maps.md +37 -0
- package/skills/vercel-react-best-practices/rules/js-length-check-first.md +49 -0
- package/skills/vercel-react-best-practices/rules/js-min-max-loop.md +82 -0
- package/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/skills/vercel-react-best-practices/rules/rendering-activity.md +26 -0
- package/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md +30 -0
- package/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md +75 -0
- package/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/skills/vercel-react-best-practices/rules/rerender-dependencies.md +45 -0
- package/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
- package/skills/vercel-react-best-practices/rules/rerender-derived-state.md +29 -0
- package/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md +38 -0
- package/skills/vercel-react-best-practices/rules/rerender-memo.md +44 -0
- package/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
- package/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
- package/skills/vercel-react-best-practices/rules/rerender-transitions.md +40 -0
- package/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
- package/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/skills/vercel-react-best-practices/rules/server-auth-actions.md +96 -0
- package/skills/vercel-react-best-practices/rules/server-cache-lru.md +41 -0
- package/skills/vercel-react-best-practices/rules/server-cache-react.md +76 -0
- package/skills/vercel-react-best-practices/rules/server-dedup-props.md +65 -0
- package/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +83 -0
- package/skills/vercel-react-best-practices/rules/server-serialization.md +38 -0
- package/skills/vercel-react-native-skills/AGENTS.md +2897 -0
- package/skills/vercel-react-native-skills/README.md +165 -0
- package/skills/vercel-react-native-skills/SKILL.md +121 -0
- package/skills/vercel-react-native-skills/rules/animation-derived-value.md +53 -0
- package/skills/vercel-react-native-skills/rules/animation-gesture-detector-press.md +95 -0
- package/skills/vercel-react-native-skills/rules/animation-gpu-properties.md +65 -0
- package/skills/vercel-react-native-skills/rules/design-system-compound-components.md +66 -0
- package/skills/vercel-react-native-skills/rules/fonts-config-plugin.md +71 -0
- package/skills/vercel-react-native-skills/rules/imports-design-system-folder.md +68 -0
- package/skills/vercel-react-native-skills/rules/js-hoist-intl.md +61 -0
- package/skills/vercel-react-native-skills/rules/list-performance-callbacks.md +44 -0
- package/skills/vercel-react-native-skills/rules/list-performance-function-references.md +132 -0
- package/skills/vercel-react-native-skills/rules/list-performance-images.md +53 -0
- package/skills/vercel-react-native-skills/rules/list-performance-inline-objects.md +97 -0
- package/skills/vercel-react-native-skills/rules/list-performance-item-expensive.md +94 -0
- package/skills/vercel-react-native-skills/rules/list-performance-item-memo.md +82 -0
- package/skills/vercel-react-native-skills/rules/list-performance-item-types.md +104 -0
- package/skills/vercel-react-native-skills/rules/list-performance-virtualize.md +67 -0
- package/skills/vercel-react-native-skills/rules/monorepo-native-deps-in-app.md +46 -0
- package/skills/vercel-react-native-skills/rules/monorepo-single-dependency-versions.md +63 -0
- package/skills/vercel-react-native-skills/rules/navigation-native-navigators.md +188 -0
- package/skills/vercel-react-native-skills/rules/react-compiler-destructure-functions.md +50 -0
- package/skills/vercel-react-native-skills/rules/react-compiler-reanimated-shared-values.md +48 -0
- package/skills/vercel-react-native-skills/rules/react-state-dispatcher.md +91 -0
- package/skills/vercel-react-native-skills/rules/react-state-fallback.md +56 -0
- package/skills/vercel-react-native-skills/rules/react-state-minimize.md +65 -0
- package/skills/vercel-react-native-skills/rules/rendering-no-falsy-and.md +74 -0
- package/skills/vercel-react-native-skills/rules/rendering-text-in-text-component.md +36 -0
- package/skills/vercel-react-native-skills/rules/scroll-position-no-state.md +82 -0
- package/skills/vercel-react-native-skills/rules/state-ground-truth.md +80 -0
- package/skills/vercel-react-native-skills/rules/ui-expo-image.md +66 -0
- package/skills/vercel-react-native-skills/rules/ui-image-gallery.md +104 -0
- package/skills/vercel-react-native-skills/rules/ui-measure-views.md +78 -0
- package/skills/vercel-react-native-skills/rules/ui-menus.md +174 -0
- package/skills/vercel-react-native-skills/rules/ui-native-modals.md +77 -0
- package/skills/vercel-react-native-skills/rules/ui-pressable.md +61 -0
- package/skills/vercel-react-native-skills/rules/ui-safe-area-scroll.md +65 -0
- package/skills/vercel-react-native-skills/rules/ui-scrollview-content-inset.md +45 -0
- package/skills/vercel-react-native-skills/rules/ui-styling.md +87 -0
- package/skills/web-design-guidelines/SKILL.md +39 -0
- package/templates/AGENTS.md +31 -0
- package/templates/CLAUDE.md +31 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Scripts
|
|
2
|
+
|
|
3
|
+
Loading third-party scripts in Next.js.
|
|
4
|
+
|
|
5
|
+
## Use next/script
|
|
6
|
+
|
|
7
|
+
Always use `next/script` instead of native `<script>` tags for better performance.
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
// Bad: Native script tag
|
|
11
|
+
<script src="https://example.com/script.js"></script>
|
|
12
|
+
|
|
13
|
+
// Good: Next.js Script component
|
|
14
|
+
import Script from 'next/script'
|
|
15
|
+
|
|
16
|
+
<Script src="https://example.com/script.js" />
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Inline Scripts Need ID
|
|
20
|
+
|
|
21
|
+
Inline scripts require an `id` attribute for Next.js to track them.
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
// Bad: Missing id
|
|
25
|
+
<Script dangerouslySetInnerHTML={{ __html: 'console.log("hi")' }} />
|
|
26
|
+
|
|
27
|
+
// Good: Has id
|
|
28
|
+
<Script id="my-script" dangerouslySetInnerHTML={{ __html: 'console.log("hi")' }} />
|
|
29
|
+
|
|
30
|
+
// Good: Inline with id
|
|
31
|
+
<Script id="show-banner">
|
|
32
|
+
{`document.getElementById('banner').classList.remove('hidden')`}
|
|
33
|
+
</Script>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Don't Put Script in Head
|
|
37
|
+
|
|
38
|
+
`next/script` should not be placed inside `next/head`. It handles its own positioning.
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
// Bad: Script inside Head
|
|
42
|
+
import Head from 'next/head'
|
|
43
|
+
import Script from 'next/script'
|
|
44
|
+
|
|
45
|
+
<Head>
|
|
46
|
+
<Script src="/analytics.js" />
|
|
47
|
+
</Head>
|
|
48
|
+
|
|
49
|
+
// Good: Script outside Head
|
|
50
|
+
<Head>
|
|
51
|
+
<title>Page</title>
|
|
52
|
+
</Head>
|
|
53
|
+
<Script src="/analytics.js" />
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Loading Strategies
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
// afterInteractive (default) - Load after page is interactive
|
|
60
|
+
<Script src="/analytics.js" strategy="afterInteractive" />
|
|
61
|
+
|
|
62
|
+
// lazyOnload - Load during idle time
|
|
63
|
+
<Script src="/widget.js" strategy="lazyOnload" />
|
|
64
|
+
|
|
65
|
+
// beforeInteractive - Load before page is interactive (use sparingly)
|
|
66
|
+
// Only works in app/layout.tsx or pages/_document.js
|
|
67
|
+
<Script src="/critical.js" strategy="beforeInteractive" />
|
|
68
|
+
|
|
69
|
+
// worker - Load in web worker (experimental)
|
|
70
|
+
<Script src="/heavy.js" strategy="worker" />
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Google Analytics
|
|
74
|
+
|
|
75
|
+
Use `@next/third-parties` instead of inline GA scripts.
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
// Bad: Inline GA script
|
|
79
|
+
<Script src="https://www.googletagmanager.com/gtag/js?id=G-XXXXX" />
|
|
80
|
+
<Script id="ga-init">
|
|
81
|
+
{`window.dataLayer = window.dataLayer || [];
|
|
82
|
+
function gtag(){dataLayer.push(arguments);}
|
|
83
|
+
gtag('js', new Date());
|
|
84
|
+
gtag('config', 'G-XXXXX');`}
|
|
85
|
+
</Script>
|
|
86
|
+
|
|
87
|
+
// Good: Next.js component
|
|
88
|
+
import { GoogleAnalytics } from '@next/third-parties/google'
|
|
89
|
+
|
|
90
|
+
export default function Layout({ children }) {
|
|
91
|
+
return (
|
|
92
|
+
<html>
|
|
93
|
+
<body>{children}</body>
|
|
94
|
+
<GoogleAnalytics gaId="G-XXXXX" />
|
|
95
|
+
</html>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Google Tag Manager
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
import { GoogleTagManager } from '@next/third-parties/google'
|
|
104
|
+
|
|
105
|
+
export default function Layout({ children }) {
|
|
106
|
+
return (
|
|
107
|
+
<html>
|
|
108
|
+
<GoogleTagManager gtmId="GTM-XXXXX" />
|
|
109
|
+
<body>{children}</body>
|
|
110
|
+
</html>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Other Third-Party Scripts
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
// YouTube embed
|
|
119
|
+
import { YouTubeEmbed } from '@next/third-parties/google'
|
|
120
|
+
|
|
121
|
+
<YouTubeEmbed videoid="dQw4w9WgXcQ" />
|
|
122
|
+
|
|
123
|
+
// Google Maps
|
|
124
|
+
import { GoogleMapsEmbed } from '@next/third-parties/google'
|
|
125
|
+
|
|
126
|
+
<GoogleMapsEmbed
|
|
127
|
+
apiKey="YOUR_API_KEY"
|
|
128
|
+
mode="place"
|
|
129
|
+
q="Brooklyn+Bridge,New+York,NY"
|
|
130
|
+
/>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Quick Reference
|
|
134
|
+
|
|
135
|
+
| Pattern | Issue | Fix |
|
|
136
|
+
|---------|-------|-----|
|
|
137
|
+
| `<script src="...">` | No optimization | Use `next/script` |
|
|
138
|
+
| `<Script>` without id | Can't track inline scripts | Add `id` attribute |
|
|
139
|
+
| `<Script>` inside `<Head>` | Wrong placement | Move outside Head |
|
|
140
|
+
| Inline GA/GTM scripts | No optimization | Use `@next/third-parties` |
|
|
141
|
+
| `strategy="beforeInteractive"` outside layout | Won't work | Only use in root layout |
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
# Self-Hosting Next.js
|
|
2
|
+
|
|
3
|
+
Deploy Next.js outside of Vercel with confidence.
|
|
4
|
+
|
|
5
|
+
## Quick Start: Standalone Output
|
|
6
|
+
|
|
7
|
+
For Docker or any containerized deployment, use standalone output:
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
// next.config.js
|
|
11
|
+
module.exports = {
|
|
12
|
+
output: 'standalone',
|
|
13
|
+
};
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
This creates a minimal `standalone` folder with only production dependencies:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
.next/
|
|
20
|
+
├── standalone/
|
|
21
|
+
│ ├── server.js # Entry point
|
|
22
|
+
│ ├── node_modules/ # Only production deps
|
|
23
|
+
│ └── .next/ # Build output
|
|
24
|
+
└── static/ # Must be copied separately
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Docker Deployment
|
|
28
|
+
|
|
29
|
+
### Dockerfile
|
|
30
|
+
|
|
31
|
+
```dockerfile
|
|
32
|
+
FROM node:20-alpine AS base
|
|
33
|
+
|
|
34
|
+
# Install dependencies
|
|
35
|
+
FROM base AS deps
|
|
36
|
+
WORKDIR /app
|
|
37
|
+
COPY package.json package-lock.json* ./
|
|
38
|
+
RUN npm ci
|
|
39
|
+
|
|
40
|
+
# Build
|
|
41
|
+
FROM base AS builder
|
|
42
|
+
WORKDIR /app
|
|
43
|
+
COPY --from=deps /app/node_modules ./node_modules
|
|
44
|
+
COPY . .
|
|
45
|
+
RUN npm run build
|
|
46
|
+
|
|
47
|
+
# Production
|
|
48
|
+
FROM base AS runner
|
|
49
|
+
WORKDIR /app
|
|
50
|
+
|
|
51
|
+
ENV NODE_ENV=production
|
|
52
|
+
|
|
53
|
+
# Create non-root user
|
|
54
|
+
RUN addgroup --system --gid 1001 nodejs
|
|
55
|
+
RUN adduser --system --uid 1001 nextjs
|
|
56
|
+
|
|
57
|
+
# Copy standalone output
|
|
58
|
+
COPY --from=builder /app/.next/standalone ./
|
|
59
|
+
COPY --from=builder /app/.next/static ./.next/static
|
|
60
|
+
COPY --from=builder /app/public ./public
|
|
61
|
+
|
|
62
|
+
USER nextjs
|
|
63
|
+
|
|
64
|
+
EXPOSE 3000
|
|
65
|
+
ENV PORT=3000
|
|
66
|
+
ENV HOSTNAME="0.0.0.0"
|
|
67
|
+
|
|
68
|
+
CMD ["node", "server.js"]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Docker Compose
|
|
72
|
+
|
|
73
|
+
```yaml
|
|
74
|
+
version: '3.8'
|
|
75
|
+
|
|
76
|
+
services:
|
|
77
|
+
web:
|
|
78
|
+
build: .
|
|
79
|
+
ports:
|
|
80
|
+
- "3000:3000"
|
|
81
|
+
environment:
|
|
82
|
+
- NODE_ENV=production
|
|
83
|
+
restart: unless-stopped
|
|
84
|
+
healthcheck:
|
|
85
|
+
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/api/health"]
|
|
86
|
+
interval: 30s
|
|
87
|
+
timeout: 10s
|
|
88
|
+
retries: 3
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## PM2 Deployment
|
|
92
|
+
|
|
93
|
+
For traditional server deployments:
|
|
94
|
+
|
|
95
|
+
```js
|
|
96
|
+
// ecosystem.config.js
|
|
97
|
+
module.exports = {
|
|
98
|
+
apps: [{
|
|
99
|
+
name: 'nextjs',
|
|
100
|
+
script: '.next/standalone/server.js',
|
|
101
|
+
instances: 'max',
|
|
102
|
+
exec_mode: 'cluster',
|
|
103
|
+
env: {
|
|
104
|
+
NODE_ENV: 'production',
|
|
105
|
+
PORT: 3000,
|
|
106
|
+
},
|
|
107
|
+
}],
|
|
108
|
+
};
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
npm run build
|
|
113
|
+
pm2 start ecosystem.config.js
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## ISR and Cache Handlers
|
|
117
|
+
|
|
118
|
+
### The Problem
|
|
119
|
+
|
|
120
|
+
ISR (Incremental Static Regeneration) uses filesystem caching by default. This **breaks with multiple instances**:
|
|
121
|
+
|
|
122
|
+
- Instance A regenerates page → saves to its local disk
|
|
123
|
+
- Instance B serves stale page → doesn't see Instance A's cache
|
|
124
|
+
- Load balancer sends users to random instances → inconsistent content
|
|
125
|
+
|
|
126
|
+
### Solution: Custom Cache Handler
|
|
127
|
+
|
|
128
|
+
Next.js 14+ supports custom cache handlers for shared storage:
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
// next.config.js
|
|
132
|
+
module.exports = {
|
|
133
|
+
cacheHandler: require.resolve('./cache-handler.js'),
|
|
134
|
+
cacheMaxMemorySize: 0, // Disable in-memory cache
|
|
135
|
+
};
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
#### Redis Cache Handler Example
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
// cache-handler.js
|
|
142
|
+
const Redis = require('ioredis');
|
|
143
|
+
|
|
144
|
+
const redis = new Redis(process.env.REDIS_URL);
|
|
145
|
+
const CACHE_PREFIX = 'nextjs:';
|
|
146
|
+
|
|
147
|
+
module.exports = class CacheHandler {
|
|
148
|
+
constructor(options) {
|
|
149
|
+
this.options = options;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async get(key) {
|
|
153
|
+
const data = await redis.get(CACHE_PREFIX + key);
|
|
154
|
+
if (!data) return null;
|
|
155
|
+
|
|
156
|
+
const parsed = JSON.parse(data);
|
|
157
|
+
return {
|
|
158
|
+
value: parsed.value,
|
|
159
|
+
lastModified: parsed.lastModified,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async set(key, data, ctx) {
|
|
164
|
+
const cacheData = {
|
|
165
|
+
value: data,
|
|
166
|
+
lastModified: Date.now(),
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// Set TTL based on revalidate option
|
|
170
|
+
if (ctx?.revalidate) {
|
|
171
|
+
await redis.setex(
|
|
172
|
+
CACHE_PREFIX + key,
|
|
173
|
+
ctx.revalidate,
|
|
174
|
+
JSON.stringify(cacheData)
|
|
175
|
+
);
|
|
176
|
+
} else {
|
|
177
|
+
await redis.set(CACHE_PREFIX + key, JSON.stringify(cacheData));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async revalidateTag(tags) {
|
|
182
|
+
// Implement tag-based invalidation
|
|
183
|
+
// This requires tracking which keys have which tags
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
#### S3 Cache Handler Example
|
|
189
|
+
|
|
190
|
+
```js
|
|
191
|
+
// cache-handler.js
|
|
192
|
+
const { S3Client, GetObjectCommand, PutObjectCommand } = require('@aws-sdk/client-s3');
|
|
193
|
+
|
|
194
|
+
const s3 = new S3Client({ region: process.env.AWS_REGION });
|
|
195
|
+
const BUCKET = process.env.CACHE_BUCKET;
|
|
196
|
+
|
|
197
|
+
module.exports = class CacheHandler {
|
|
198
|
+
async get(key) {
|
|
199
|
+
try {
|
|
200
|
+
const response = await s3.send(new GetObjectCommand({
|
|
201
|
+
Bucket: BUCKET,
|
|
202
|
+
Key: `cache/${key}`,
|
|
203
|
+
}));
|
|
204
|
+
const body = await response.Body.transformToString();
|
|
205
|
+
return JSON.parse(body);
|
|
206
|
+
} catch (err) {
|
|
207
|
+
if (err.name === 'NoSuchKey') return null;
|
|
208
|
+
throw err;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async set(key, data, ctx) {
|
|
213
|
+
await s3.send(new PutObjectCommand({
|
|
214
|
+
Bucket: BUCKET,
|
|
215
|
+
Key: `cache/${key}`,
|
|
216
|
+
Body: JSON.stringify({
|
|
217
|
+
value: data,
|
|
218
|
+
lastModified: Date.now(),
|
|
219
|
+
}),
|
|
220
|
+
ContentType: 'application/json',
|
|
221
|
+
}));
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## What Works vs What Needs Setup
|
|
227
|
+
|
|
228
|
+
| Feature | Single Instance | Multi-Instance | Notes |
|
|
229
|
+
|---------|----------------|----------------|-------|
|
|
230
|
+
| SSR | Yes | Yes | No special setup |
|
|
231
|
+
| SSG | Yes | Yes | Built at deploy time |
|
|
232
|
+
| ISR | Yes | Needs cache handler | Filesystem cache breaks |
|
|
233
|
+
| Image Optimization | Yes | Yes | CPU-intensive, consider CDN |
|
|
234
|
+
| Middleware | Yes | Yes | Runs on Node.js |
|
|
235
|
+
| Edge Runtime | Limited | Limited | Some features Node-only |
|
|
236
|
+
| `revalidatePath/Tag` | Yes | Needs cache handler | Must share cache |
|
|
237
|
+
| `next/font` | Yes | Yes | Fonts bundled at build |
|
|
238
|
+
| Draft Mode | Yes | Yes | Cookie-based |
|
|
239
|
+
|
|
240
|
+
## Image Optimization
|
|
241
|
+
|
|
242
|
+
Next.js Image Optimization works out of the box but is CPU-intensive.
|
|
243
|
+
|
|
244
|
+
### Option 1: Built-in (Simple)
|
|
245
|
+
|
|
246
|
+
Works automatically, but consider:
|
|
247
|
+
- Set `deviceSizes` and `imageSizes` in config to limit variants
|
|
248
|
+
- Use `minimumCacheTTL` to reduce regeneration
|
|
249
|
+
|
|
250
|
+
```js
|
|
251
|
+
// next.config.js
|
|
252
|
+
module.exports = {
|
|
253
|
+
images: {
|
|
254
|
+
minimumCacheTTL: 60 * 60 * 24, // 24 hours
|
|
255
|
+
deviceSizes: [640, 750, 1080, 1920], // Limit sizes
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Option 2: External Loader (Recommended for Scale)
|
|
261
|
+
|
|
262
|
+
Offload to Cloudinary, Imgix, or similar:
|
|
263
|
+
|
|
264
|
+
```js
|
|
265
|
+
// next.config.js
|
|
266
|
+
module.exports = {
|
|
267
|
+
images: {
|
|
268
|
+
loader: 'custom',
|
|
269
|
+
loaderFile: './lib/image-loader.js',
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
```js
|
|
275
|
+
// lib/image-loader.js
|
|
276
|
+
export default function cloudinaryLoader({ src, width, quality }) {
|
|
277
|
+
const params = ['f_auto', 'c_limit', `w_${width}`, `q_${quality || 'auto'}`];
|
|
278
|
+
return `https://res.cloudinary.com/demo/image/upload/${params.join(',')}${src}`;
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Environment Variables
|
|
283
|
+
|
|
284
|
+
### Build-time vs Runtime
|
|
285
|
+
|
|
286
|
+
```js
|
|
287
|
+
// Available at build time only (baked into bundle)
|
|
288
|
+
NEXT_PUBLIC_API_URL=https://api.example.com
|
|
289
|
+
|
|
290
|
+
// Available at runtime (server-side only)
|
|
291
|
+
DATABASE_URL=postgresql://...
|
|
292
|
+
API_SECRET=...
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Runtime Configuration
|
|
296
|
+
|
|
297
|
+
For truly dynamic config, don't use `NEXT_PUBLIC_*`. Instead:
|
|
298
|
+
|
|
299
|
+
```tsx
|
|
300
|
+
// app/api/config/route.ts
|
|
301
|
+
export async function GET() {
|
|
302
|
+
return Response.json({
|
|
303
|
+
apiUrl: process.env.API_URL,
|
|
304
|
+
features: process.env.FEATURES?.split(','),
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## OpenNext: Serverless Without Vercel
|
|
310
|
+
|
|
311
|
+
[OpenNext](https://open-next.js.org/) adapts Next.js for AWS Lambda, Cloudflare Workers, etc.
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
npx create-sst@latest
|
|
315
|
+
# or
|
|
316
|
+
npx @opennextjs/aws build
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Supports:
|
|
320
|
+
- AWS Lambda + CloudFront
|
|
321
|
+
- Cloudflare Workers
|
|
322
|
+
- Netlify Functions
|
|
323
|
+
- Deno Deploy
|
|
324
|
+
|
|
325
|
+
## Health Check Endpoint
|
|
326
|
+
|
|
327
|
+
Always include a health check for load balancers:
|
|
328
|
+
|
|
329
|
+
```tsx
|
|
330
|
+
// app/api/health/route.ts
|
|
331
|
+
export async function GET() {
|
|
332
|
+
try {
|
|
333
|
+
// Optional: check database connection
|
|
334
|
+
// await db.$queryRaw`SELECT 1`;
|
|
335
|
+
|
|
336
|
+
return Response.json({ status: 'healthy' }, { status: 200 });
|
|
337
|
+
} catch (error) {
|
|
338
|
+
return Response.json({ status: 'unhealthy' }, { status: 503 });
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## Pre-Deployment Checklist
|
|
344
|
+
|
|
345
|
+
1. **Build locally first**: `npm run build` - catch errors before deploy
|
|
346
|
+
2. **Test standalone output**: `node .next/standalone/server.js`
|
|
347
|
+
3. **Set `output: 'standalone'`** for Docker
|
|
348
|
+
4. **Configure cache handler** for multi-instance ISR
|
|
349
|
+
5. **Set `HOSTNAME="0.0.0.0"`** for containers
|
|
350
|
+
6. **Copy `public/` and `.next/static/`** - not included in standalone
|
|
351
|
+
7. **Add health check endpoint**
|
|
352
|
+
8. **Test ISR revalidation** after deployment
|
|
353
|
+
9. **Monitor memory usage** - Node.js defaults may need tuning
|
|
354
|
+
|
|
355
|
+
## Testing Cache Handler
|
|
356
|
+
|
|
357
|
+
**Critical**: Test your cache handler on every Next.js upgrade:
|
|
358
|
+
|
|
359
|
+
```bash
|
|
360
|
+
# Start multiple instances
|
|
361
|
+
PORT=3001 node .next/standalone/server.js &
|
|
362
|
+
PORT=3002 node .next/standalone/server.js &
|
|
363
|
+
|
|
364
|
+
# Trigger ISR revalidation
|
|
365
|
+
curl http://localhost:3001/api/revalidate?path=/posts
|
|
366
|
+
|
|
367
|
+
# Verify both instances see the update
|
|
368
|
+
curl http://localhost:3001/posts
|
|
369
|
+
curl http://localhost:3002/posts
|
|
370
|
+
# Should return identical content
|
|
371
|
+
```
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Suspense Boundaries
|
|
2
|
+
|
|
3
|
+
Client hooks that cause CSR bailout without Suspense boundaries.
|
|
4
|
+
|
|
5
|
+
## useSearchParams
|
|
6
|
+
|
|
7
|
+
Always requires Suspense boundary in static routes. Without it, the entire page becomes client-side rendered.
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
// Bad: Entire page becomes CSR
|
|
11
|
+
'use client'
|
|
12
|
+
|
|
13
|
+
import { useSearchParams } from 'next/navigation'
|
|
14
|
+
|
|
15
|
+
export default function SearchBar() {
|
|
16
|
+
const searchParams = useSearchParams()
|
|
17
|
+
return <div>Query: {searchParams.get('q')}</div>
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
// Good: Wrap in Suspense
|
|
23
|
+
import { Suspense } from 'react'
|
|
24
|
+
import SearchBar from './search-bar'
|
|
25
|
+
|
|
26
|
+
export default function Page() {
|
|
27
|
+
return (
|
|
28
|
+
<Suspense fallback={<div>Loading...</div>}>
|
|
29
|
+
<SearchBar />
|
|
30
|
+
</Suspense>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## usePathname
|
|
36
|
+
|
|
37
|
+
Requires Suspense boundary when route has dynamic parameters.
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
// In dynamic route [slug]
|
|
41
|
+
// Bad: No Suspense
|
|
42
|
+
'use client'
|
|
43
|
+
import { usePathname } from 'next/navigation'
|
|
44
|
+
|
|
45
|
+
export function Breadcrumb() {
|
|
46
|
+
const pathname = usePathname()
|
|
47
|
+
return <nav>{pathname}</nav>
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
// Good: Wrap in Suspense
|
|
53
|
+
<Suspense fallback={<BreadcrumbSkeleton />}>
|
|
54
|
+
<Breadcrumb />
|
|
55
|
+
</Suspense>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
If you use `generateStaticParams`, Suspense is optional.
|
|
59
|
+
|
|
60
|
+
## Quick Reference
|
|
61
|
+
|
|
62
|
+
| Hook | Suspense Required |
|
|
63
|
+
|------|-------------------|
|
|
64
|
+
| `useSearchParams()` | Yes |
|
|
65
|
+
| `usePathname()` | Yes (dynamic routes) |
|
|
66
|
+
| `useParams()` | No |
|
|
67
|
+
| `useRouter()` | No |
|