@ngxtm/devkit 3.4.0 → 3.4.1
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 +2 -1
- package/rules/README.md +141 -0
- package/rules/dart/best-practices/SKILL.md +23 -0
- package/rules/dart/language/SKILL.md +52 -0
- package/rules/dart/tooling/SKILL.md +43 -0
- package/rules/dotnet/aspnet-core/SKILL.md +92 -0
- package/rules/dotnet/aspnet-core/references/REFERENCE.md +335 -0
- package/rules/dotnet/best-practices/SKILL.md +101 -0
- package/rules/dotnet/best-practices/references/REFERENCE.md +256 -0
- package/rules/dotnet/blazor/SKILL.md +146 -0
- package/rules/dotnet/blazor/references/REFERENCE.md +392 -0
- package/rules/dotnet/language/SKILL.md +82 -0
- package/rules/dotnet/language/references/REFERENCE.md +222 -0
- package/rules/dotnet/patterns.rule.md +388 -0
- package/rules/dotnet/razor-pages/SKILL.md +124 -0
- package/rules/dotnet/razor-pages/references/REFERENCE.md +321 -0
- package/rules/dotnet/security/SKILL.md +89 -0
- package/rules/dotnet/security/references/REFERENCE.md +295 -0
- package/rules/dotnet/tooling/SKILL.md +92 -0
- package/rules/dotnet/tooling/references/REFERENCE.md +300 -0
- package/rules/flutter/auto-route-navigation/SKILL.md +43 -0
- package/rules/flutter/auto-route-navigation/references/REFERENCE.md +19 -0
- package/rules/flutter/auto-route-navigation/references/router-config.md +62 -0
- package/rules/flutter/bloc-state-management/SKILL.md +64 -0
- package/rules/flutter/bloc-state-management/references/REFERENCE.md +20 -0
- package/rules/flutter/bloc-state-management/references/auth-bloc-example.md +52 -0
- package/rules/flutter/bloc-state-management/references/equatable-usage.md +56 -0
- package/rules/flutter/bloc-state-management/references/property-based-state.md +68 -0
- package/rules/flutter/bloc.rule.md +76 -0
- package/rules/flutter/cicd/SKILL.md +48 -0
- package/rules/flutter/cicd/references/advanced-workflow.md +66 -0
- package/rules/flutter/cicd/references/fastlane.md +139 -0
- package/rules/flutter/cicd/references/github-actions.md +59 -0
- package/rules/flutter/dependency-injection/SKILL.md +42 -0
- package/rules/flutter/dependency-injection/references/REFERENCE.md +15 -0
- package/rules/flutter/dependency-injection/references/modules.md +37 -0
- package/rules/flutter/error-handling/SKILL.md +32 -0
- package/rules/flutter/error-handling/references/REFERENCE.md +19 -0
- package/rules/flutter/error-handling/references/error-mapping.md +31 -0
- package/rules/flutter/feature-based-clean-architecture/SKILL.md +46 -0
- package/rules/flutter/feature-based-clean-architecture/references/REFERENCE.md +14 -0
- package/rules/flutter/feature-based-clean-architecture/references/folder-structure.md +36 -0
- package/rules/flutter/getx-navigation/SKILL.md +70 -0
- package/rules/flutter/getx-navigation/references/app-pages.md +40 -0
- package/rules/flutter/getx-navigation/references/middleware-example.md +29 -0
- package/rules/flutter/getx-state-management/SKILL.md +76 -0
- package/rules/flutter/getx-state-management/references/binding-example.md +32 -0
- package/rules/flutter/getx-state-management/references/reactive-vs-simple.md +39 -0
- package/rules/flutter/go-router-navigation/SKILL.md +57 -0
- package/rules/flutter/idiomatic-flutter/SKILL.md +20 -0
- package/rules/flutter/layer-based-clean-architecture/SKILL.md +50 -0
- package/rules/flutter/layer-based-clean-architecture/references/REFERENCE.md +60 -0
- package/rules/flutter/layer-based-clean-architecture/references/repository-mapping.md +50 -0
- package/rules/flutter/localization/SKILL.md +50 -0
- package/rules/flutter/localization/references/REFERENCE.md +48 -0
- package/rules/flutter/localization/references/sheet-loader.md +33 -0
- package/rules/flutter/navigator-v1-navigation/SKILL.md +71 -0
- package/rules/flutter/navigator-v1-navigation/references/on-generate-route.md +48 -0
- package/rules/flutter/performance/SKILL.md +24 -0
- package/rules/flutter/retrofit-networking/SKILL.md +51 -0
- package/rules/flutter/retrofit-networking/references/REFERENCE.md +19 -0
- package/rules/flutter/retrofit-networking/references/token-refresh.md +40 -0
- package/rules/flutter/riverpod-state-management/SKILL.md +53 -0
- package/rules/flutter/riverpod-state-management/references/architecture.md +124 -0
- package/rules/flutter/riverpod-state-management/references/best-practices.md +89 -0
- package/rules/flutter/riverpod-state-management/references/testing.md +73 -0
- package/rules/flutter/riverpod.rule.md +78 -0
- package/rules/flutter/security/SKILL.md +33 -0
- package/rules/flutter/security/references/REFERENCE.md +15 -0
- package/rules/flutter/security/references/network-security.md +28 -0
- package/rules/flutter/testing/SKILL.md +44 -0
- package/rules/flutter/testing/references/REFERENCE.md +21 -0
- package/rules/flutter/testing/references/bloc-testing.md +38 -0
- package/rules/flutter/testing/references/integration-testing.md +128 -0
- package/rules/flutter/testing/references/robot-pattern.md +82 -0
- package/rules/flutter/testing/references/unit-testing.md +130 -0
- package/rules/flutter/testing/references/widget-testing.md +120 -0
- package/rules/flutter/widgets/SKILL.md +37 -0
- package/rules/golang/chi-router/SKILL.md +219 -0
- package/rules/golang/chi-router/references/REFERENCE.md +13 -0
- package/rules/golang/chi-router/references/routing-patterns.md +205 -0
- package/rules/golang/cobra-cli/SKILL.md +227 -0
- package/rules/golang/cobra-cli/references/REFERENCE.md +13 -0
- package/rules/golang/cobra-cli/references/command-patterns.md +224 -0
- package/rules/golang/core/SKILL.md +210 -0
- package/rules/golang/core/references/REFERENCE.md +14 -0
- package/rules/golang/core/references/concurrency-patterns.md +114 -0
- package/rules/golang/core/references/error-handling.md +87 -0
- package/rules/golang/echo-framework/SKILL.md +215 -0
- package/rules/golang/echo-framework/references/REFERENCE.md +14 -0
- package/rules/golang/echo-framework/references/middleware-patterns.md +141 -0
- package/rules/golang/echo-framework/references/routing-patterns.md +140 -0
- package/rules/golang/ent-orm/SKILL.md +239 -0
- package/rules/golang/ent-orm/references/REFERENCE.md +13 -0
- package/rules/golang/ent-orm/references/schema-patterns.md +255 -0
- package/rules/golang/fiber-framework/SKILL.md +196 -0
- package/rules/golang/fiber-framework/references/REFERENCE.md +13 -0
- package/rules/golang/fiber-framework/references/routing-patterns.md +191 -0
- package/rules/golang/gin-framework/SKILL.md +205 -0
- package/rules/golang/gin-framework/references/REFERENCE.md +14 -0
- package/rules/golang/gin-framework/references/middleware-patterns.md +119 -0
- package/rules/golang/gorm-orm/SKILL.md +196 -0
- package/rules/golang/gorm-orm/references/REFERENCE.md +14 -0
- package/rules/golang/gorm-orm/references/model-definitions.md +167 -0
- package/rules/golang/gorm-orm/references/query-patterns.md +161 -0
- package/rules/golang/grpc/SKILL.md +231 -0
- package/rules/golang/grpc/references/REFERENCE.md +13 -0
- package/rules/golang/grpc/references/service-patterns.md +276 -0
- package/rules/golang/testify/SKILL.md +239 -0
- package/rules/golang/testify/references/REFERENCE.md +13 -0
- package/rules/golang/testify/references/assert-patterns.md +170 -0
- package/rules/golang/validator/SKILL.md +234 -0
- package/rules/golang/validator/references/REFERENCE.md +13 -0
- package/rules/golang/validator/references/validation-tags.md +211 -0
- package/rules/golang/viper-config/SKILL.md +244 -0
- package/rules/golang/viper-config/references/REFERENCE.md +13 -0
- package/rules/golang/viper-config/references/config-loading.md +181 -0
- package/rules/golang/wire-di/SKILL.md +243 -0
- package/rules/golang/wire-di/references/REFERENCE.md +13 -0
- package/rules/golang/wire-di/references/provider-patterns.md +193 -0
- package/rules/golang/zap-logging/SKILL.md +203 -0
- package/rules/golang/zap-logging/references/REFERENCE.md +13 -0
- package/rules/golang/zap-logging/references/logger-config.md +165 -0
- package/rules/java/build-gradle/SKILL.md +92 -0
- package/rules/java/build-gradle/references/REFERENCE.md +14 -0
- package/rules/java/build-gradle/references/kotlin-dsl.md +118 -0
- package/rules/java/build-gradle/references/task-configuration.md +132 -0
- package/rules/java/build-maven/SKILL.md +86 -0
- package/rules/java/build-maven/references/REFERENCE.md +14 -0
- package/rules/java/build-maven/references/dependency-management.md +111 -0
- package/rules/java/build-maven/references/lifecycle-phases.md +114 -0
- package/rules/java/graalvm-native/SKILL.md +105 -0
- package/rules/java/graalvm-native/references/REFERENCE.md +12 -0
- package/rules/java/java-collections-streams/SKILL.md +148 -0
- package/rules/java/java-collections-streams/references/REFERENCE.md +15 -0
- package/rules/java/java-collections-streams/references/collectors-patterns.md +178 -0
- package/rules/java/java-collections-streams/references/stream-pipelines.md +165 -0
- package/rules/java/java-concurrency/SKILL.md +187 -0
- package/rules/java/java-concurrency/references/REFERENCE.md +17 -0
- package/rules/java/java-concurrency/references/completable-future.md +165 -0
- package/rules/java/java-concurrency/references/executor-patterns.md +176 -0
- package/rules/java/java-concurrency/references/virtual-threads.md +190 -0
- package/rules/java/java-core-language/SKILL.md +121 -0
- package/rules/java/java-core-language/references/REFERENCE.md +15 -0
- package/rules/java/java-core-language/references/jvm-memory-model.md +160 -0
- package/rules/java/java-core-language/references/modern-java-features.md +168 -0
- package/rules/java/java-project-structure/SKILL.md +195 -0
- package/rules/java/java-project-structure/references/REFERENCE.md +15 -0
- package/rules/java/java-project-structure/references/maven-project-layout.md +199 -0
- package/rules/java/java-project-structure/references/module-system.md +159 -0
- package/rules/java/micronaut-core/SKILL.md +99 -0
- package/rules/java/micronaut-core/references/REFERENCE.md +12 -0
- package/rules/java/micronaut-reactive/SKILL.md +68 -0
- package/rules/java/micronaut-reactive/references/REFERENCE.md +12 -0
- package/rules/java/quarkus-core/SKILL.md +85 -0
- package/rules/java/quarkus-core/references/REFERENCE.md +12 -0
- package/rules/java/quarkus-reactive/SKILL.md +67 -0
- package/rules/java/quarkus-reactive/references/REFERENCE.md +12 -0
- package/rules/java/spring-batch/SKILL.md +102 -0
- package/rules/java/spring-batch/references/REFERENCE.md +12 -0
- package/rules/java/spring-boot-architecture/SKILL.md +206 -0
- package/rules/java/spring-boot-architecture/references/REFERENCE.md +15 -0
- package/rules/java/spring-boot-architecture/references/auto-configuration.md +158 -0
- package/rules/java/spring-boot-architecture/references/configuration-properties.md +202 -0
- package/rules/java/spring-boot-web/SKILL.md +217 -0
- package/rules/java/spring-boot-web/references/REFERENCE.md +17 -0
- package/rules/java/spring-cloud/SKILL.md +109 -0
- package/rules/java/spring-cloud/references/REFERENCE.md +13 -0
- package/rules/java/spring-data-jpa/SKILL.md +241 -0
- package/rules/java/spring-data-jpa/references/REFERENCE.md +16 -0
- package/rules/java/spring-security/SKILL.md +161 -0
- package/rules/java/spring-security/references/REFERENCE.md +16 -0
- package/rules/java/spring-security/references/jwt-auth-flow.md +213 -0
- package/rules/java/testing-junit-mockito/SKILL.md +135 -0
- package/rules/java/testing-junit-mockito/references/REFERENCE.md +15 -0
- package/rules/java/testing-junit-mockito/references/junit5-patterns.md +159 -0
- package/rules/java/testing-junit-mockito/references/mockito-patterns.md +148 -0
- package/rules/java/testing-junit-mockito/references/spring-boot-testing.md +152 -0
- package/rules/javascript/best-practices/SKILL.md +64 -0
- package/rules/javascript/best-practices/references/REFERENCE.md +91 -0
- package/rules/javascript/language/SKILL.md +71 -0
- package/rules/javascript/language/references/REFERENCE.md +106 -0
- package/rules/javascript/tooling/SKILL.md +60 -0
- package/rules/javascript/tooling/references/REFERENCE.md +107 -0
- package/rules/metadata.json +54 -0
- package/rules/nestjs/api-standards/SKILL.md +47 -0
- package/rules/nestjs/api-standards/references/pagination-wrapper.md +87 -0
- package/rules/nestjs/architecture/SKILL.md +68 -0
- package/rules/nestjs/architecture/references/dynamic-module.md +53 -0
- package/rules/nestjs/caching/SKILL.md +51 -0
- package/rules/nestjs/caching/references/REFERENCE.md +13 -0
- package/rules/nestjs/caching/references/cache-patterns.md +183 -0
- package/rules/nestjs/configuration/SKILL.md +41 -0
- package/rules/nestjs/configuration/references/REFERENCE.md +13 -0
- package/rules/nestjs/configuration/references/config-patterns.md +184 -0
- package/rules/nestjs/controllers-services/SKILL.md +63 -0
- package/rules/nestjs/controllers-services/references/REFERENCE.md +14 -0
- package/rules/nestjs/controllers-services/references/controller-patterns.md +119 -0
- package/rules/nestjs/controllers-services/references/service-patterns.md +129 -0
- package/rules/nestjs/database/SKILL.md +102 -0
- package/rules/nestjs/database/references/REFERENCE.md +14 -0
- package/rules/nestjs/database/references/typeorm-patterns.md +156 -0
- package/rules/nestjs/deployment/SKILL.md +36 -0
- package/rules/nestjs/deployment/references/REFERENCE.md +13 -0
- package/rules/nestjs/deployment/references/deployment-patterns.md +140 -0
- package/rules/nestjs/documentation/SKILL.md +64 -0
- package/rules/nestjs/documentation/references/REFERENCE.md +13 -0
- package/rules/nestjs/documentation/references/swagger-patterns.md +139 -0
- package/rules/nestjs/error-handling/SKILL.md +55 -0
- package/rules/nestjs/error-handling/references/REFERENCE.md +13 -0
- package/rules/nestjs/error-handling/references/exception-filters.md +152 -0
- package/rules/nestjs/file-uploads/SKILL.md +35 -0
- package/rules/nestjs/file-uploads/references/REFERENCE.md +13 -0
- package/rules/nestjs/file-uploads/references/upload-patterns.md +125 -0
- package/rules/nestjs/observability/SKILL.md +39 -0
- package/rules/nestjs/observability/references/REFERENCE.md +13 -0
- package/rules/nestjs/observability/references/logging-metrics.md +175 -0
- package/rules/nestjs/performance/SKILL.md +60 -0
- package/rules/nestjs/performance/references/REFERENCE.md +13 -0
- package/rules/nestjs/performance/references/performance-patterns.md +107 -0
- package/rules/nestjs/real-time/SKILL.md +45 -0
- package/rules/nestjs/real-time/references/REFERENCE.md +13 -0
- package/rules/nestjs/real-time/references/websocket-patterns.md +121 -0
- package/rules/nestjs/scheduling/SKILL.md +39 -0
- package/rules/nestjs/scheduling/references/REFERENCE.md +13 -0
- package/rules/nestjs/scheduling/references/scheduling-patterns.md +137 -0
- package/rules/nestjs/search/SKILL.md +41 -0
- package/rules/nestjs/search/references/REFERENCE.md +13 -0
- package/rules/nestjs/search/references/search-patterns.md +137 -0
- package/rules/nestjs/security/SKILL.md +87 -0
- package/rules/nestjs/security/references/REFERENCE.md +14 -0
- package/rules/nestjs/security/references/authentication.md +151 -0
- package/rules/nestjs/testing/SKILL.md +40 -0
- package/rules/nestjs/testing/references/REFERENCE.md +14 -0
- package/rules/nestjs/testing/references/unit-testing.md +179 -0
- package/rules/nestjs/transport/SKILL.md +45 -0
- package/rules/nestjs/transport/references/REFERENCE.md +13 -0
- package/rules/nestjs/transport/references/microservices-patterns.md +170 -0
- package/rules/nextjs/app-router/SKILL.md +46 -0
- package/rules/nextjs/app-router/references/REFERENCE.md +14 -0
- package/rules/nextjs/app-router/references/routing-patterns.md +182 -0
- package/rules/nextjs/architecture/SKILL.md +44 -0
- package/rules/nextjs/architecture/references/fsd-structure.md +77 -0
- package/rules/nextjs/authentication/SKILL.md +29 -0
- package/rules/nextjs/authentication/references/auth-implementation.md +73 -0
- package/rules/nextjs/caching/SKILL.md +66 -0
- package/rules/nextjs/caching/references/REFERENCE.md +13 -0
- package/rules/nextjs/caching/references/cache-strategies.md +168 -0
- package/rules/nextjs/data-access-layer/SKILL.md +33 -0
- package/rules/nextjs/data-access-layer/references/patterns.md +66 -0
- package/rules/nextjs/data-fetching/SKILL.md +59 -0
- package/rules/nextjs/data-fetching/references/REFERENCE.md +13 -0
- package/rules/nextjs/data-fetching/references/fetch-patterns.md +160 -0
- package/rules/nextjs/internationalization/SKILL.md +105 -0
- package/rules/nextjs/internationalization/references/REFERENCE.md +13 -0
- package/rules/nextjs/internationalization/references/i18n-patterns.md +180 -0
- package/rules/nextjs/optimization/SKILL.md +64 -0
- package/rules/nextjs/optimization/references/REFERENCE.md +13 -0
- package/rules/nextjs/optimization/references/optimization-patterns.md +190 -0
- package/rules/nextjs/rendering/SKILL.md +91 -0
- package/rules/nextjs/rendering/references/REFERENCE.md +13 -0
- package/rules/nextjs/rendering/references/rendering-modes.md +163 -0
- package/rules/nextjs/server-actions/SKILL.md +46 -0
- package/rules/nextjs/server-actions/references/REFERENCE.md +13 -0
- package/rules/nextjs/server-actions/references/action-patterns.md +188 -0
- package/rules/nextjs/server-components/SKILL.md +52 -0
- package/rules/nextjs/server-components/references/REFERENCE.md +13 -0
- package/rules/nextjs/server-components/references/component-patterns.md +175 -0
- package/rules/nextjs/state-management/SKILL.md +73 -0
- package/rules/nextjs/state-management/references/REFERENCE.md +13 -0
- package/rules/nextjs/state-management/references/state-patterns.md +218 -0
- package/rules/nextjs/styling/SKILL.md +31 -0
- package/rules/nextjs/styling/references/implementation.md +56 -0
- package/rules/react/component-patterns/SKILL.md +66 -0
- package/rules/react/component-patterns/references/REFERENCE.md +126 -0
- package/rules/react/hooks/SKILL.md +60 -0
- package/rules/react/hooks/references/REFERENCE.md +132 -0
- package/rules/react/hooks.rule.md +79 -0
- package/rules/react/performance/SKILL.md +69 -0
- package/rules/react/performance/references/REFERENCE.md +143 -0
- package/rules/react/security/SKILL.md +46 -0
- package/rules/react/security/references/REFERENCE.md +170 -0
- package/rules/react/state-management/SKILL.md +56 -0
- package/rules/react/state-management/references/REFERENCE.md +137 -0
- package/rules/react/testing/SKILL.md +45 -0
- package/rules/react/testing/references/REFERENCE.md +149 -0
- package/rules/react/tooling/SKILL.md +39 -0
- package/rules/react/typescript/SKILL.md +53 -0
- package/rules/rust/actix-web/SKILL.md +160 -0
- package/rules/rust/actix-web/references/REFERENCE.md +13 -0
- package/rules/rust/actix-web/references/handler-patterns.md +198 -0
- package/rules/rust/async-graphql/SKILL.md +228 -0
- package/rules/rust/async-graphql/references/REFERENCE.md +13 -0
- package/rules/rust/async-graphql/references/schema-patterns.md +215 -0
- package/rules/rust/axum/SKILL.md +161 -0
- package/rules/rust/axum/references/REFERENCE.md +14 -0
- package/rules/rust/axum/references/handler-patterns.md +97 -0
- package/rules/rust/bevy/SKILL.md +206 -0
- package/rules/rust/bevy/references/REFERENCE.md +13 -0
- package/rules/rust/bevy/references/ecs-patterns.md +226 -0
- package/rules/rust/clap/SKILL.md +217 -0
- package/rules/rust/clap/references/REFERENCE.md +13 -0
- package/rules/rust/clap/references/derive-patterns.md +205 -0
- package/rules/rust/core/SKILL.md +154 -0
- package/rules/rust/core/references/REFERENCE.md +14 -0
- package/rules/rust/core/references/error-handling.md +92 -0
- package/rules/rust/diesel-orm/SKILL.md +176 -0
- package/rules/rust/diesel-orm/references/REFERENCE.md +13 -0
- package/rules/rust/diesel-orm/references/schema-patterns.md +206 -0
- package/rules/rust/rocket/SKILL.md +182 -0
- package/rules/rust/rocket/references/REFERENCE.md +13 -0
- package/rules/rust/rocket/references/handler-patterns.md +209 -0
- package/rules/rust/sea-orm/SKILL.md +230 -0
- package/rules/rust/sea-orm/references/REFERENCE.md +13 -0
- package/rules/rust/sea-orm/references/entity-patterns.md +221 -0
- package/rules/rust/serde-serialization/SKILL.md +150 -0
- package/rules/rust/serde-serialization/references/REFERENCE.md +13 -0
- package/rules/rust/serde-serialization/references/serialization-patterns.md +199 -0
- package/rules/rust/sqlx-database/SKILL.md +140 -0
- package/rules/rust/sqlx-database/references/REFERENCE.md +13 -0
- package/rules/rust/sqlx-database/references/query-patterns.md +210 -0
- package/rules/rust/tauri/SKILL.md +180 -0
- package/rules/rust/tauri/references/REFERENCE.md +13 -0
- package/rules/rust/tauri/references/command-patterns.md +209 -0
- package/rules/rust/tokio-runtime/SKILL.md +167 -0
- package/rules/rust/tokio-runtime/references/REFERENCE.md +14 -0
- package/rules/rust/tokio-runtime/references/async-patterns.md +137 -0
- package/rules/rust/tokio-runtime/references/synchronization.md +152 -0
- package/rules/rust/tonic/SKILL.md +231 -0
- package/rules/rust/tonic/references/REFERENCE.md +13 -0
- package/rules/rust/tonic/references/service-patterns.md +213 -0
- package/rules/rust/tracing/SKILL.md +214 -0
- package/rules/rust/tracing/references/REFERENCE.md +13 -0
- package/rules/rust/tracing/references/instrumentation.md +187 -0
- package/rules/typescript/best-practices/SKILL.md +108 -0
- package/rules/typescript/best-practices/references/REFERENCE.md +68 -0
- package/rules/typescript/language/SKILL.md +72 -0
- package/rules/typescript/language/references/REFERENCE.md +67 -0
- package/rules/typescript/patterns.rule.md +85 -0
- package/rules/typescript/security/SKILL.md +59 -0
- package/rules/typescript/security/references/REFERENCE.md +113 -0
- package/rules/typescript/tooling/SKILL.md +52 -0
- package/rules/typescript/tooling/references/REFERENCE.md +110 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# Next.js Optimization Patterns
|
|
2
|
+
|
|
3
|
+
## Image Optimization
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import Image from 'next/image';
|
|
7
|
+
|
|
8
|
+
// Basic responsive image
|
|
9
|
+
<Image
|
|
10
|
+
src="/hero.jpg"
|
|
11
|
+
alt="Hero"
|
|
12
|
+
width={1200}
|
|
13
|
+
height={600}
|
|
14
|
+
priority // Load immediately for LCP
|
|
15
|
+
/>
|
|
16
|
+
|
|
17
|
+
// Fill container
|
|
18
|
+
<div className="relative h-64 w-full">
|
|
19
|
+
<Image
|
|
20
|
+
src="/background.jpg"
|
|
21
|
+
alt="Background"
|
|
22
|
+
fill
|
|
23
|
+
sizes="100vw"
|
|
24
|
+
style={{ objectFit: 'cover' }}
|
|
25
|
+
/>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
// Responsive with sizes
|
|
29
|
+
<Image
|
|
30
|
+
src="/product.jpg"
|
|
31
|
+
alt="Product"
|
|
32
|
+
width={800}
|
|
33
|
+
height={600}
|
|
34
|
+
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
|
35
|
+
/>
|
|
36
|
+
|
|
37
|
+
// Blur placeholder
|
|
38
|
+
<Image
|
|
39
|
+
src="/photo.jpg"
|
|
40
|
+
alt="Photo"
|
|
41
|
+
width={400}
|
|
42
|
+
height={300}
|
|
43
|
+
placeholder="blur"
|
|
44
|
+
blurDataURL="data:image/jpeg;base64,..."
|
|
45
|
+
/>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Font Optimization
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// app/layout.tsx
|
|
52
|
+
import { Inter, Roboto_Mono } from 'next/font/google';
|
|
53
|
+
|
|
54
|
+
const inter = Inter({
|
|
55
|
+
subsets: ['latin'],
|
|
56
|
+
display: 'swap',
|
|
57
|
+
variable: '--font-inter',
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const robotoMono = Roboto_Mono({
|
|
61
|
+
subsets: ['latin'],
|
|
62
|
+
display: 'swap',
|
|
63
|
+
variable: '--font-roboto-mono',
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
export default function RootLayout({ children }) {
|
|
67
|
+
return (
|
|
68
|
+
<html lang="en" className={`${inter.variable} ${robotoMono.variable}`}>
|
|
69
|
+
<body className={inter.className}>{children}</body>
|
|
70
|
+
</html>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Local fonts
|
|
75
|
+
import localFont from 'next/font/local';
|
|
76
|
+
|
|
77
|
+
const myFont = localFont({
|
|
78
|
+
src: './fonts/MyFont.woff2',
|
|
79
|
+
display: 'swap',
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Script Optimization
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
import Script from 'next/script';
|
|
87
|
+
|
|
88
|
+
// After page interactive (default)
|
|
89
|
+
<Script src="https://example.com/analytics.js" />
|
|
90
|
+
|
|
91
|
+
// Before page hydration
|
|
92
|
+
<Script src="https://polyfill.io/v3/polyfill.min.js" strategy="beforeInteractive" />
|
|
93
|
+
|
|
94
|
+
// After page load
|
|
95
|
+
<Script src="https://example.com/chat-widget.js" strategy="lazyOnload" />
|
|
96
|
+
|
|
97
|
+
// With onLoad callback
|
|
98
|
+
<Script
|
|
99
|
+
src="https://maps.googleapis.com/maps/api/js"
|
|
100
|
+
onLoad={() => console.log('Google Maps loaded')}
|
|
101
|
+
/>
|
|
102
|
+
|
|
103
|
+
// Inline script
|
|
104
|
+
<Script id="schema-org" type="application/ld+json">
|
|
105
|
+
{JSON.stringify(structuredData)}
|
|
106
|
+
</Script>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Dynamic Imports
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import dynamic from 'next/dynamic';
|
|
113
|
+
|
|
114
|
+
// Lazy load component
|
|
115
|
+
const DynamicChart = dynamic(() => import('../components/Chart'), {
|
|
116
|
+
loading: () => <p>Loading chart...</p>,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Disable SSR for client-only components
|
|
120
|
+
const DynamicMap = dynamic(() => import('../components/Map'), {
|
|
121
|
+
ssr: false,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Named export
|
|
125
|
+
const DynamicModal = dynamic(
|
|
126
|
+
() => import('../components/Modal').then((mod) => mod.Modal)
|
|
127
|
+
);
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Bundle Analysis
|
|
131
|
+
|
|
132
|
+
```javascript
|
|
133
|
+
// next.config.js
|
|
134
|
+
const withBundleAnalyzer = require('@next/bundle-analyzer')({
|
|
135
|
+
enabled: process.env.ANALYZE === 'true',
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
module.exports = withBundleAnalyzer({
|
|
139
|
+
// config
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Run: ANALYZE=true npm run build
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Metadata Optimization
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
// app/layout.tsx
|
|
149
|
+
import { Metadata } from 'next';
|
|
150
|
+
|
|
151
|
+
export const metadata: Metadata = {
|
|
152
|
+
title: {
|
|
153
|
+
template: '%s | My App',
|
|
154
|
+
default: 'My App',
|
|
155
|
+
},
|
|
156
|
+
description: 'My app description',
|
|
157
|
+
openGraph: {
|
|
158
|
+
images: ['/og-image.jpg'],
|
|
159
|
+
},
|
|
160
|
+
robots: {
|
|
161
|
+
index: true,
|
|
162
|
+
follow: true,
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// Dynamic metadata
|
|
167
|
+
export async function generateMetadata({ params }): Promise<Metadata> {
|
|
168
|
+
const product = await getProduct(params.id);
|
|
169
|
+
return {
|
|
170
|
+
title: product.name,
|
|
171
|
+
description: product.description,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Route Segment Config
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
// Static generation with revalidation
|
|
180
|
+
export const revalidate = 3600; // Revalidate every hour
|
|
181
|
+
|
|
182
|
+
// Force dynamic rendering
|
|
183
|
+
export const dynamic = 'force-dynamic';
|
|
184
|
+
|
|
185
|
+
// Force static rendering
|
|
186
|
+
export const dynamic = 'force-static';
|
|
187
|
+
|
|
188
|
+
// Runtime selection
|
|
189
|
+
export const runtime = 'edge'; // or 'nodejs'
|
|
190
|
+
```
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Next.js Rendering Strategies
|
|
3
|
+
description: SSG, SSR, ISR, Streaming, and Partial Prerendering (PPR).
|
|
4
|
+
metadata:
|
|
5
|
+
labels: [nextjs, rendering, isr, ssr, ssg]
|
|
6
|
+
triggers:
|
|
7
|
+
files: ['**/page.tsx', '**/layout.tsx']
|
|
8
|
+
keywords: [generateStaticParams, dynamic, dynamicParams, PPR, streaming]
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Rendering Strategies (App Router)
|
|
12
|
+
|
|
13
|
+
## **Priority: P0 (CRITICAL)**
|
|
14
|
+
|
|
15
|
+
Understanding how Next.js renders content determines performance and cost.
|
|
16
|
+
|
|
17
|
+
## Strategy Selection Matrix (Scaling & Cost)
|
|
18
|
+
|
|
19
|
+
| Strategy | Ideal For | Data Freshness | Performance (TTFB) | Scaling Risk |
|
|
20
|
+
| :------- | :--------------------- | :------------------- | :-------------------------- | :----------------------------------- |
|
|
21
|
+
| **SSG** | Marketing, Docs, Blogs | Build Time | **Instant** (CDN) | **None** |
|
|
22
|
+
| **ISR** | E-commerce, CMS | Periodic (e.g., 60s) | **Instant** (CDN) | **Low** (Background Rebuilds) |
|
|
23
|
+
| **SSR** | Dashboards, Auth Gates | Real-Time (Request) | **Slow** (Waits for Server) | **Critical** (1 Request = 1 Compute) |
|
|
24
|
+
| **PPR** | Personalized Apps | Hybrid | **Instant** (Shell) | **Medium** (Streaming Holes) |
|
|
25
|
+
|
|
26
|
+
## Scaling Patterns
|
|
27
|
+
|
|
28
|
+
### 1. The "Static Shell" Pattern (Preferred)
|
|
29
|
+
|
|
30
|
+
- **Goal**: Make most of the page **Static** to hit the CDN cache.
|
|
31
|
+
- **Pattern**:
|
|
32
|
+
- Render the generic layout (Logo, Footer, Navigation) as **Static**.
|
|
33
|
+
- Wrap personalized/slow components (User Profile, Cart Count, Recommendations) in `<Suspense>`.
|
|
34
|
+
- **Result**: **TTFB (Time to First Byte)** is near-instant (~50ms) because the shell is cached. User perception is fast, even if DB is slow.
|
|
35
|
+
|
|
36
|
+
### 2. Avoiding "SSR Waterfalls"
|
|
37
|
+
|
|
38
|
+
- **Problem**: In naive SSR, the server waits for _every_ fetch to finish before sending _any_ HTML.
|
|
39
|
+
- _Scenario_: DB Call (200ms) + Auth Check (100ms) + 3rd Party API (500ms) = Blank screen for 800ms.
|
|
40
|
+
- **Solution**:
|
|
41
|
+
- Move slow fetches **down** the component tree into Suspense boundaries.
|
|
42
|
+
- Do not `await` everything in the root `page.tsx`.
|
|
43
|
+
|
|
44
|
+
## 1. Static Rendering (SSG) - **Default**
|
|
45
|
+
|
|
46
|
+
- **Behavior**: Routes are rendered at **build time**.
|
|
47
|
+
- **Usage**: Marketing pages, Blogs, Documentation.
|
|
48
|
+
- **Dynamic Routes**: Use `generateStaticParams` to generate static pages for dynamic paths (e.g., `/blog/[slug]`).
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
export async function generateStaticParams() {
|
|
52
|
+
const posts = await getPosts();
|
|
53
|
+
return posts.map((post) => ({ slug: post.slug }));
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## 2. Dynamic Rendering (SSR)
|
|
58
|
+
|
|
59
|
+
- **Behavior**: Routes are rendered at **request time**.
|
|
60
|
+
- **Triggers**:
|
|
61
|
+
- Using Dynamic Functions: `cookies()`, `headers()`, `searchParams`.
|
|
62
|
+
- Using Dynamic Fetch: `fetch(..., { cache: 'no-store' })`.
|
|
63
|
+
- Explicit Config: `export const dynamic = 'force-dynamic'`.
|
|
64
|
+
|
|
65
|
+
## 3. Streaming (Suspense)
|
|
66
|
+
|
|
67
|
+
- **Problem**: SSR blocks the entire page until all data is ready.
|
|
68
|
+
- **Solution**: Wrap slow components in `<Suspense>`. Next.js instantly sends the initial HTML (static shell) and streams the slow content later.
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
<Suspense fallback={<Skeleton />}>
|
|
72
|
+
<SlowDashboard />
|
|
73
|
+
</Suspense>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## 4. Incremental Static Regeneration (ISR)
|
|
77
|
+
|
|
78
|
+
- **Behavior**: Update static content after build time without rebuilding the entire site.
|
|
79
|
+
- **Time-based**: `export const revalidate = 3600;` (in layout/page) or `{ next: { revalidate: 3600 } }` (in fetch).
|
|
80
|
+
- **On-Demand**: `revalidatePath('/posts')` via Server Actions or Webhooks.
|
|
81
|
+
|
|
82
|
+
## 5. Partial Prerendering (PPR) - _Experimental_
|
|
83
|
+
|
|
84
|
+
- **Concept**: Combines Static shell + Dynamic holes.
|
|
85
|
+
- **Config**: `export const experimental_ppr = true`.
|
|
86
|
+
- **Behavior**: The build generates a static shell for the route (served instantly from Edge), and dynamic parts stream in.
|
|
87
|
+
|
|
88
|
+
## Runtime Configuration
|
|
89
|
+
|
|
90
|
+
- **Node.js (Default)**: Full Node.js API support.
|
|
91
|
+
- **Edge**: `export const runtime = 'edge'`. Limited API, starts instantly, lower cost.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Next.js Rendering References
|
|
2
|
+
|
|
3
|
+
## References
|
|
4
|
+
|
|
5
|
+
- [**Rendering Modes**](rendering-modes.md) - Static, Dynamic, Streaming
|
|
6
|
+
|
|
7
|
+
## Quick Checks
|
|
8
|
+
|
|
9
|
+
- [ ] Static by default for performance
|
|
10
|
+
- [ ] Dynamic when data changes per request
|
|
11
|
+
- [ ] Streaming for improved TTFB
|
|
12
|
+
- [ ] PPR for best of both worlds
|
|
13
|
+
- [ ] Understand rendering boundaries
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Next.js Rendering Modes
|
|
2
|
+
|
|
3
|
+
## Static Rendering (Default)
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
// Rendered at build time
|
|
7
|
+
async function BlogPage() {
|
|
8
|
+
const posts = await getPosts(); // Cached
|
|
9
|
+
return <PostList posts={posts} />;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Generate static pages for dynamic routes
|
|
13
|
+
export async function generateStaticParams() {
|
|
14
|
+
const posts = await getPosts();
|
|
15
|
+
return posts.map((post) => ({ slug: post.slug }));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// With revalidation (ISR)
|
|
19
|
+
export const revalidate = 3600; // Regenerate every hour
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Dynamic Rendering
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
// Force dynamic rendering
|
|
26
|
+
export const dynamic = 'force-dynamic';
|
|
27
|
+
|
|
28
|
+
// Automatic dynamic - using dynamic functions
|
|
29
|
+
import { cookies, headers } from 'next/headers';
|
|
30
|
+
|
|
31
|
+
async function Page() {
|
|
32
|
+
const cookieStore = await cookies();
|
|
33
|
+
const token = cookieStore.get('token');
|
|
34
|
+
// Now renders dynamically
|
|
35
|
+
return <Dashboard token={token} />;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Dynamic with searchParams
|
|
39
|
+
async function SearchPage({
|
|
40
|
+
searchParams
|
|
41
|
+
}: {
|
|
42
|
+
searchParams: Promise<{ q: string }>
|
|
43
|
+
}) {
|
|
44
|
+
const { q } = await searchParams;
|
|
45
|
+
const results = await search(q);
|
|
46
|
+
return <Results data={results} />;
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Streaming
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
import { Suspense } from 'react';
|
|
54
|
+
|
|
55
|
+
// Progressive rendering with streaming
|
|
56
|
+
async function Page() {
|
|
57
|
+
return (
|
|
58
|
+
<div>
|
|
59
|
+
{/* Renders immediately */}
|
|
60
|
+
<Header />
|
|
61
|
+
|
|
62
|
+
{/* Streams when ready */}
|
|
63
|
+
<Suspense fallback={<RecommendationsSkeleton />}>
|
|
64
|
+
<Recommendations />
|
|
65
|
+
</Suspense>
|
|
66
|
+
|
|
67
|
+
<Suspense fallback={<ReviewsSkeleton />}>
|
|
68
|
+
<Reviews />
|
|
69
|
+
</Suspense>
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// loading.tsx provides route-level streaming
|
|
75
|
+
// app/dashboard/loading.tsx
|
|
76
|
+
export default function Loading() {
|
|
77
|
+
return <DashboardSkeleton />;
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Partial Prerendering (PPR)
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
// next.config.js
|
|
85
|
+
module.exports = {
|
|
86
|
+
experimental: {
|
|
87
|
+
ppr: true,
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Static shell with dynamic holes
|
|
92
|
+
async function Page() {
|
|
93
|
+
return (
|
|
94
|
+
<div>
|
|
95
|
+
{/* Static - prerendered */}
|
|
96
|
+
<Header />
|
|
97
|
+
<Hero />
|
|
98
|
+
|
|
99
|
+
{/* Dynamic - streamed */}
|
|
100
|
+
<Suspense fallback={<CartSkeleton />}>
|
|
101
|
+
<Cart /> {/* Uses cookies */}
|
|
102
|
+
</Suspense>
|
|
103
|
+
|
|
104
|
+
{/* Static */}
|
|
105
|
+
<Footer />
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Edge Runtime
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
// Run at the edge for lower latency
|
|
115
|
+
export const runtime = 'edge';
|
|
116
|
+
|
|
117
|
+
async function Page() {
|
|
118
|
+
// Limited Node.js APIs
|
|
119
|
+
// Fast cold starts
|
|
120
|
+
return <Content />;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Middleware always runs on edge
|
|
124
|
+
// middleware.ts
|
|
125
|
+
import { NextResponse } from 'next/server';
|
|
126
|
+
|
|
127
|
+
export function middleware(request: Request) {
|
|
128
|
+
return NextResponse.next();
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Rendering Decision Tree
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
Is data static?
|
|
136
|
+
├── Yes → Static Rendering (SSG)
|
|
137
|
+
│ └── Need periodic updates? → ISR (revalidate)
|
|
138
|
+
└── No → Dynamic Rendering
|
|
139
|
+
├── Per-request data (cookies, headers) → Server
|
|
140
|
+
├── Real-time data → Client + SWR/React Query
|
|
141
|
+
└── Mix of static + dynamic → PPR + Suspense
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Client-Side Rendering
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
'use client';
|
|
148
|
+
|
|
149
|
+
import useSWR from 'swr';
|
|
150
|
+
|
|
151
|
+
// Real-time or frequently changing data
|
|
152
|
+
function StockPrice({ symbol }: { symbol: string }) {
|
|
153
|
+
const { data, error, isLoading } = useSWR(
|
|
154
|
+
`/api/stocks/${symbol}`,
|
|
155
|
+
fetcher,
|
|
156
|
+
{ refreshInterval: 1000 }
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
if (isLoading) return <Skeleton />;
|
|
160
|
+
if (error) return <Error />;
|
|
161
|
+
return <Price value={data.price} />;
|
|
162
|
+
}
|
|
163
|
+
```
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Next.js Server Actions
|
|
3
|
+
description: Mutations, Form handling, and RPC-style calls.
|
|
4
|
+
metadata:
|
|
5
|
+
labels: [nextjs, actions, mutations]
|
|
6
|
+
triggers:
|
|
7
|
+
files: ['**/actions.ts', '**/*.tsx']
|
|
8
|
+
keywords: [use server, Server Action, revalidatePath, useFormStatus]
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Server Actions
|
|
12
|
+
|
|
13
|
+
## **Priority: P1 (HIGH)**
|
|
14
|
+
|
|
15
|
+
Handle form submissions and mutations without creating API endpoints.
|
|
16
|
+
|
|
17
|
+
## Implementation
|
|
18
|
+
|
|
19
|
+
- **Directive**: Add `'use server'` at the top of an async function.
|
|
20
|
+
- **Usage**: Pass to `action` prop of `<form>` or invoke from event handlers.
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
// actions.ts
|
|
24
|
+
'use server';
|
|
25
|
+
export async function createPost(formData: FormData) {
|
|
26
|
+
const title = formData.get('title');
|
|
27
|
+
await db.post.create({ title });
|
|
28
|
+
revalidatePath('/posts'); // Refresh UI
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Client Invocation
|
|
33
|
+
|
|
34
|
+
- **Form**: `<form action={createPost}>` (Progressive enhancements work without JS).
|
|
35
|
+
- **Event Handler**: `onClick={() => createPost(data)}`.
|
|
36
|
+
- **Pending State**: Use `useFormStatus` hook (must be inside a component rendered within the form).
|
|
37
|
+
|
|
38
|
+
## Validation & Error Handling
|
|
39
|
+
|
|
40
|
+
- **Zod**: Always validate `FormData` or arguments on the server.
|
|
41
|
+
- **Return Values**: Return serializable objects `{ success: boolean, error?: string }` to handle feedback on Client.
|
|
42
|
+
|
|
43
|
+
## Security
|
|
44
|
+
|
|
45
|
+
- **Authentication**: Check `auth()` (e.g., NextAuth) session inside every Server Action.
|
|
46
|
+
- **Closure**: Be careful with closures in Server Actions defined inside Components (they capture context encrypted). Prefer defining actions in separate files (`actions.ts`).
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Next.js Server Actions References
|
|
2
|
+
|
|
3
|
+
## References
|
|
4
|
+
|
|
5
|
+
- [**Action Patterns**](action-patterns.md) - Forms, mutations, revalidation
|
|
6
|
+
|
|
7
|
+
## Quick Checks
|
|
8
|
+
|
|
9
|
+
- [ ] Use 'use server' directive
|
|
10
|
+
- [ ] Validate inputs on server
|
|
11
|
+
- [ ] Use useActionState for forms
|
|
12
|
+
- [ ] Revalidate after mutations
|
|
13
|
+
- [ ] Handle errors appropriately
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Next.js Server Action Patterns
|
|
2
|
+
|
|
3
|
+
## Basic Actions
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
// app/actions.ts
|
|
7
|
+
'use server';
|
|
8
|
+
|
|
9
|
+
import { revalidatePath } from 'next/cache';
|
|
10
|
+
|
|
11
|
+
export async function createPost(formData: FormData) {
|
|
12
|
+
const title = formData.get('title') as string;
|
|
13
|
+
const content = formData.get('content') as string;
|
|
14
|
+
|
|
15
|
+
await db.post.create({ data: { title, content } });
|
|
16
|
+
|
|
17
|
+
revalidatePath('/posts');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// In component
|
|
21
|
+
import { createPost } from './actions';
|
|
22
|
+
|
|
23
|
+
function CreatePostForm() {
|
|
24
|
+
return (
|
|
25
|
+
<form action={createPost}>
|
|
26
|
+
<input name="title" required />
|
|
27
|
+
<textarea name="content" required />
|
|
28
|
+
<button type="submit">Create</button>
|
|
29
|
+
</form>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## With useActionState
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
'use client';
|
|
38
|
+
|
|
39
|
+
import { useActionState } from 'react';
|
|
40
|
+
import { createPost } from './actions';
|
|
41
|
+
|
|
42
|
+
function CreatePostForm() {
|
|
43
|
+
const [state, action, pending] = useActionState(createPost, null);
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<form action={action}>
|
|
47
|
+
<input name="title" required />
|
|
48
|
+
<textarea name="content" required />
|
|
49
|
+
{state?.error && <p className="error">{state.error}</p>}
|
|
50
|
+
<button type="submit" disabled={pending}>
|
|
51
|
+
{pending ? 'Creating...' : 'Create Post'}
|
|
52
|
+
</button>
|
|
53
|
+
</form>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Action with state
|
|
58
|
+
'use server';
|
|
59
|
+
|
|
60
|
+
export async function createPost(prevState: any, formData: FormData) {
|
|
61
|
+
const title = formData.get('title') as string;
|
|
62
|
+
|
|
63
|
+
if (!title || title.length < 3) {
|
|
64
|
+
return { error: 'Title must be at least 3 characters' };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
await db.post.create({ data: { title } });
|
|
69
|
+
revalidatePath('/posts');
|
|
70
|
+
return { success: true };
|
|
71
|
+
} catch (e) {
|
|
72
|
+
return { error: 'Failed to create post' };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Validation with Zod
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
'use server';
|
|
81
|
+
|
|
82
|
+
import { z } from 'zod';
|
|
83
|
+
|
|
84
|
+
const CreatePostSchema = z.object({
|
|
85
|
+
title: z.string().min(3).max(100),
|
|
86
|
+
content: z.string().min(10),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
export async function createPost(prevState: any, formData: FormData) {
|
|
90
|
+
const validatedFields = CreatePostSchema.safeParse({
|
|
91
|
+
title: formData.get('title'),
|
|
92
|
+
content: formData.get('content'),
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
if (!validatedFields.success) {
|
|
96
|
+
return {
|
|
97
|
+
errors: validatedFields.error.flatten().fieldErrors,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const { title, content } = validatedFields.data;
|
|
102
|
+
|
|
103
|
+
await db.post.create({ data: { title, content } });
|
|
104
|
+
revalidatePath('/posts');
|
|
105
|
+
redirect('/posts');
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Programmatic Invocation
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
'use client';
|
|
113
|
+
|
|
114
|
+
import { deletePost } from './actions';
|
|
115
|
+
|
|
116
|
+
function DeleteButton({ id }: { id: string }) {
|
|
117
|
+
async function handleDelete() {
|
|
118
|
+
if (confirm('Delete this post?')) {
|
|
119
|
+
await deletePost(id);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return <button onClick={handleDelete}>Delete</button>;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Action
|
|
127
|
+
'use server';
|
|
128
|
+
|
|
129
|
+
export async function deletePost(id: string) {
|
|
130
|
+
await db.post.delete({ where: { id } });
|
|
131
|
+
revalidatePath('/posts');
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## With useOptimistic
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
'use client';
|
|
139
|
+
|
|
140
|
+
import { useOptimistic } from 'react';
|
|
141
|
+
import { likePost } from './actions';
|
|
142
|
+
|
|
143
|
+
function LikeButton({ postId, likes }: { postId: string; likes: number }) {
|
|
144
|
+
const [optimisticLikes, addOptimisticLike] = useOptimistic(
|
|
145
|
+
likes,
|
|
146
|
+
(state) => state + 1
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
async function handleLike() {
|
|
150
|
+
addOptimisticLike(null);
|
|
151
|
+
await likePost(postId);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<button onClick={handleLike}>
|
|
156
|
+
❤️ {optimisticLikes}
|
|
157
|
+
</button>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## File Uploads
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
'use server';
|
|
166
|
+
|
|
167
|
+
export async function uploadFile(formData: FormData) {
|
|
168
|
+
const file = formData.get('file') as File;
|
|
169
|
+
|
|
170
|
+
if (!file || file.size === 0) {
|
|
171
|
+
return { error: 'No file provided' };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const bytes = await file.arrayBuffer();
|
|
175
|
+
const buffer = Buffer.from(bytes);
|
|
176
|
+
|
|
177
|
+
const path = `/uploads/${file.name}`;
|
|
178
|
+
await writeFile(path, buffer);
|
|
179
|
+
|
|
180
|
+
return { url: path };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Form
|
|
184
|
+
<form action={uploadFile}>
|
|
185
|
+
<input type="file" name="file" required />
|
|
186
|
+
<button type="submit">Upload</button>
|
|
187
|
+
</form>
|
|
188
|
+
```
|