### Performing Basic Database Queries
Source: https://nexty.dev/docs/guide/auth-database/database
Demonstrates various basic querying techniques including fetching a single record by ID, retrieving lists with pagination, sorting, and filtering with a keyword search. It also shows how to get the total count for pagination purposes.
```typescript
import { eq, ilike, or, desc, count } from 'drizzle-orm';
// Single record
const one = await db.select().from(user).where(eq(user.id, userId)).limit(1);
// List + pagination + sorting + search
const pageIndex = 0, pageSize = 20, keyword = 'john';
const cond = keyword
? or(ilike(user.email, `%${keyword}%`), ilike(user.name, `%${keyword}%`))
: undefined;
const users = await db
.select()
.from(user)
.where(cond)
.orderBy(desc(user.createdAt))
.offset(pageIndex * pageSize)
.limit(pageSize);
// Total count (for pagination)
const totalCount = (await db.select({ value: count() }).from(user).where(cond))[0].value;
```
--------------------------------
### Create Frontend Display Pages for Guide Module (TypeScript/JSX)
Source: https://nexty.dev/docs/guide/cms
This snippet demonstrates how to set up frontend pages for a new 'guide' content module. It includes initializing a CMS module instance, fetching posts and tags, and rendering a PostList component. It also shows how to handle individual guide page display, including view count statistics and related posts.
```typescript
// Create CMS module instance (in lib/cms/index.ts)
export const guideCms = createCmsModule('guide');
// Use in page
const { posts: localPosts } = await guideCms.getLocalList(locale);
const initialServerPostsResult = await listPublishedPostsAction({
pageIndex: 0,
pageSize: SERVER_POST_PAGE_SIZE,
postType: "guide", // Modify here
locale: locale,
});
// Tags
const tagsResult = await listTagsAction({ postType: "guide" });
// PostList component
// guide/[slug]/page.tsx
// Use guideCms to get content
const { post, errorCode } = await guideCms.getBySlug(slug, locale);
// View count statistics
const viewCountConfig = POST_CONFIGS.guide.viewCount;
if (viewCountConfig.enabled) {
if (viewCountConfig.mode === "unique") {
await incrementUniqueViewCountAction({ slug, postType: "guide", locale });
} else {
await incrementViewCountAction({ slug, postType: "guide", locale });
}
}
// Related articles
```
--------------------------------
### Executing Relational Queries with Joins
Source: https://nexty.dev/docs/guide/auth-database/database
Illustrates how to perform relational queries using a left join to combine data from multiple tables. This example selects specific columns from 'orders' and 'pricingPlans' tables, joining them on their respective IDs.
```typescript
import { eq } from 'drizzle-orm';
const rows = await db
.select({
orderId: orders.id,
planInterval: pricingPlans.recurringInterval,
amount: orders.amountTotal,
})
.from(orders)
.leftJoin(pricingPlans, eq(orders.planId, pricingPlans.id));
```
--------------------------------
### Start Application with Node.js
Source: https://nexty.dev/docs/start-project/dokploy
This command initiates the application server using Node.js. It's a fundamental step for running the application.
```dockerfile
CMD ["node", "server.js"]
```
--------------------------------
### Install Dokploy using cURL
Source: https://nexty.dev/docs/start-project/dokploy
This command installs Dokploy on your server by downloading and executing an installation script from dokploy.com. Ensure you have SSH access to your server before running this command.
```bash
curl -sSL https://dokploy.com/install.sh | sh
```
--------------------------------
### Install and Update Supabase CLI
Source: https://nexty.dev/docs/integration/supabase
Commands to install and update the Supabase Command Line Interface (CLI) on macOS, Linux, and Windows systems.
```shell
# mac OS or Linux
brew install supabase/tap/supabase
# Windows
scoop bucket add supabase https://github.com/supabase/scoop-bucket.git
scoop install supabase
```
```shell
# mac OS or Linux
brew upgrade supabase
# Windows
scoop update supabase
```
--------------------------------
### Start Development Server
Source: https://nexty.dev/docs/guide/upgrade
Launches the local development server to allow for testing of the upgraded features. This command is used for local verification before deploying the changes.
```bash
npm run dev
```
--------------------------------
### Update Sitemap for Guide Module (TypeScript)
Source: https://nexty.dev/docs/guide/cms
This code demonstrates how to update the `sitemap.ts` file to include routes for the new 'guide' module. It adds entries for the main guide listing page and dynamically generates routes for individual guide detail pages based on published posts.
```typescript
// Add guide list page
{
url: `${siteUrl}/${locale}/guide`,
lastModified: new Date(),
changeFrequency: 'daily',
priority: 0.8,
}
// Add guide detail pages
const guideResult = await listPublishedPostsAction({
locale: locale,
pageSize: 1000,
visibility: "public",
postType: "guide",
});
if (guideResult.success && guideResult.data?.posts) {
guideResult.data.posts.forEach((post) => {
sitemap.push({
url: `${siteUrl}/${locale}/guide/${post.slug}`,
lastModified: post.updatedAt ? new Date(post.updatedAt) : new Date(),
changeFrequency: 'weekly',
priority: 0.7,
});
});
}
```
--------------------------------
### Initialize and Seed Database with npm
Source: https://nexty.dev/docs/integration/database
Commands to initialize the database schema and import example pricing plan seed data using npm scripts. These commands are essential for setting up your database with initial data.
```bash
# Initialize database
npm run db:migrate
# Import example pricing plan seed data
npm run db:seed
```
--------------------------------
### Monthly Subscription Benefits Configuration
Source: https://nexty.dev/docs/guide/payment/prices-card
Example JSON structure for configuring benefits for a monthly subscription plan, granting credits upon subscription and renewal.
```json
{
"monthlyCredits": 50
}
```
--------------------------------
### Extending CMS Configuration for New Modules (TypeScript)
Source: https://nexty.dev/docs/guide/cms
Shows how to add configuration for a new content module, like 'guide', in `components/cms/post-config.ts`. This includes defining schemas, image paths, tag enablement, and routing for the new module.
```typescript
export const POST_CONFIGS: Record = {
blog: {
// ... existing configuration
},
glossary: {
// ... existing configuration
},
// New guide configuration
guide: {
postType: "guide",
schema: basePostSchema,
actionSchema: postActionSchema,
imagePath: GUIDE_IMAGE_PATH, // Needs to be defined in config/common.ts
enableTags: true,
// localDirectory: 'guides', // If you need to support local MDX files
viewCount: {
enabled: true, // Enable view count statistics
mode: 'unique', // Use unique IP statistics
showInUI: true,
},
showCoverInList: true,
routes: {
list: "/dashboard/guides",
create: "/dashboard/guides/new",
edit: (id: string) => `/dashboard/guides/${id}`,
},
},
};
```
--------------------------------
### Importing Database Client and Schema
Source: https://nexty.dev/docs/guide/auth-database/database
Imports the database client 'db' and schema definitions from '@/lib/db' and '@/lib/db/schema' respectively. This is the foundational step for any database interaction.
```typescript
import { db } from '@/lib/db';
import { user, orders, pricingPlans, usage, creditLogs } from '@/lib/db/schema';
```
--------------------------------
### Email Magic Link and Signup Email Templates
Source: https://nexty.dev/docs/integration/supabase
Example HTML templates for Supabase Auth's email Magic Link and signup confirmation emails. These templates can be customized, but the `href` attribute in the confirmation link must match the provided structure for cross-browser compatibility.
```html
If you didn't request this, you can safely ignore this email.
The Nexty.dev Team
```
--------------------------------
### Run Better Auth Database Migration (CLI)
Source: https://nexty.dev/docs/guide/auth-database/rate-limiting
This command uses the Better Auth CLI to run database migrations. This is a necessary step after configuring database storage to set up the required database tables.
```bash
pnpm dlx @better-auth/cli migrate
```
--------------------------------
### One-time Payment Benefits Configuration
Source: https://nexty.dev/docs/guide/payment/prices-card
Example JSON structure for configuring benefits for a one-time payment plan, specifically granting a fixed number of credits upon purchase.
```json
{
"oneTimeCredits": 100
}
```
--------------------------------
### Initialize and Link Supabase Database
Source: https://nexty.dev/docs/integration/supabase
Commands to log in to Supabase, link your project to the database, and update the database schema with initial data. This process requires Supabase API keys and the database password.
```shell
pnpm db:login
pnpm db:link
pnpm db:update
```
--------------------------------
### Install Project Dependencies with PNPM
Source: https://nexty.dev/docs/guide/upgrade
Installs all necessary project dependencies using the PNPM package manager. This step is essential after merging new code to ensure all required libraries and packages are present.
```bash
pnpm install
```
--------------------------------
### Stricter Daily Rate Limits for Free Tier Abuse Prevention (TypeScript)
Source: https://nexty.dev/docs/guide/auth-database/rate-limiting
Implements stricter daily rate limits for authentication endpoints to prevent free tier abuse. This example extends the default configuration by setting longer windows (24 hours) and lower request caps for magic link, OTP send, and OTP verification. It also includes optional Upstash Redis integration for persistent storage.
```typescript
rateLimit: {
enabled: true,
window: 60,
max: 100,
customRules: {
// Strict email sending limits
"/sign-in/magic-link": {
window: 60 * 60 * 24, // 24 hours
max: 5, // Only 5 magic links per IP per day
},
"/email-otp/send-verification-otp": {
window: 60 * 60 * 24, // 24 hours
max: 10, // Only 10 OTP codes per IP per day
},
// Keep verification attempts reasonable
"/sign-in/email-otp": {
window: 60 * 60 * 24, // 24 hours
max: 20, // 20 attempts per 24 hours
}
},
...(redis && {
customStorage: {
get: async (key: string) => {
const data = await redis!.get<{ key: string; count: number; lastRequest: number }>(key);
return data || undefined;
},
set: async (key: string, value: { key: string; count: number; lastRequest: number }) => {
await redis!.set(key, value, { ex: 60 * 60 * 24 }); // Auto-expire after 24 hours
},
},
}),
}
```
--------------------------------
### Relaxed Rate Limits for Internal Tools (TypeScript)
Source: https://nexty.dev/docs/guide/auth-database/rate-limiting
Configures relaxed rate limits suitable for internal tools or low-risk applications. This example increases the global default request limit and raises the maximum requests for specific endpoints, allowing for more lenient usage patterns.
```typescript
rateLimit: {
enabled: true,
window: 60,
max: 200, // Increase global default
customRules: {
"/sign-in/magic-link": { window: 60, max: 10 },
"/email-otp/send-verification-otp": { window: 60, max: 10 },
"/sign-in/email-otp": { window: 60, max: 20 },
},
}
```
--------------------------------
### Updating Records
Source: https://nexty.dev/docs/guide/auth-database/database
Shows how to update existing records in the 'user' table. This example sets the 'banned' status to true, provides a reason, and nullifies the ban expiration date for a specific user identified by their ID.
```typescript
await db.update(user)
.set({ banned: true, banReason: 'By admin', banExpires: null })
.where(eq(user.id, userId));
```
--------------------------------
### HTML Metadata Example
Source: https://nexty.dev/docs/guide/metadata-config
This snippet shows a typical HTML `` section with various metadata tags, including title, description, Open Graph, canonical, and hreflang. These tags are essential for search engine optimization and social media sharing.
```html
NEXTY.DEV - Build and ship your SaaS faster
```
--------------------------------
### Numeric Type Conversion for Stripe Amounts (TypeScript)
Source: https://nexty.dev/docs/guide/auth-database/database
Provides a concise example of handling currency amounts, specifically for Stripe integration. It shows the necessary conversion from cents (as typically provided by Stripe) to a displayable or storable format by dividing by 100 and converting to a string.
```typescript
const amountStr = (invoice.amount_paid / 100).toString();
```
--------------------------------
### Updating Post Editor Client for New Content Module (TypeScript)
Source: https://nexty.dev/docs/guide/cms
Demonstrates the necessary changes in `guides/new/page.tsx` and `guides/[postId]/edit/page.tsx` to configure the `PostEditorClient` for the 'guide' post type. This involves setting the `postType` prop and potentially the `mode` and `postId`.
```typescript
```
--------------------------------
### Performing Upsert Operations
Source: https://nexty.dev/docs/guide/auth-database/database
Provides examples of Upsert operations, which are useful for synchronizing data or handling idempotent writes. It covers updating an existing subscription if a 'stripeSubscriptionId' exists, or inserting it if it's new, and accumulating usage credits based on 'userId'.
```typescript
// Subscription upsert
const { stripeSubscriptionId, ...updateData } = subscriptionData;
await db.insert(subscriptions)
.values(subscriptionData)
.onConflictDoUpdate({
target: subscriptions.stripeSubscriptionId,
set: updateData,
});
// usage table upsert by userId (accumulate/overwrite)
await db.insert(usage)
.values({ userId, oneTimeCreditsBalance: add })
.onConflictDoUpdate({
target: usage.userId,
set: { oneTimeCreditsBalance: sql`${usage.oneTimeCreditsBalance} + ${add}` },
});
```
--------------------------------
### Configure GitHub OAuth for Development
Source: https://nexty.dev/docs/integration/auth
Guides the configuration of GitHub OAuth for a development environment. It involves setting the Homepage URL and Authorization callback URL in GitHub's OAuth Apps settings.
```bash
# Homepage URL: http://localhost:3000
# Authorization callback URL: http://localhost:3000/api/auth/callback/github
```
--------------------------------
### Get Session and Protect APIs (Server-side)
Source: https://nexty.dev/docs/guide/auth-database/auth
Demonstrates how to retrieve the current user session on the server-side using `getSession()`. This is essential for accessing user information and protecting API routes or server actions from unauthorized access. It expects a session object containing user details.
```typescript
import { getSession } from "@/lib/auth/server";
export async function SomeServerActions(req: Request) {
const session = await getSession()
const user = session?.user;
if (!user) return actionResponse.unauthorized();
// session.user: { id, email, role, ... }
return actionResponse.success({});
}
```
--------------------------------
### Get Session and Login Status (Client-side)
Source: https://nexty.dev/docs/guide/auth-database/auth
Demonstrates how to fetch the user's session data and check their login status from a React client component using the `authClient.useSession()` hook. This hook provides reactive state for session data and loading status.
```typescript
import { authClient } from "@/lib/auth/auth-client";
// React client component
const { data: session, isPending } = authClient.useSession();
```
--------------------------------
### Update Supabase Database with Migrations
Source: https://nexty.dev/docs/integration/supabase
Command to apply all pending SQL migration files in the `supabase/migrations` directory to the database. This command should be run after creating or modifying migration files.
```shell
pnpm db:update
```
--------------------------------
### Export New CMS Module in lib/cms/index.ts (TypeScript)
Source: https://nexty.dev/docs/guide/cms
This snippet shows how to export a newly created CMS module, in this case, 'guide', from the central `lib/cms/index.ts` file. This makes the module's functionalities available throughout the application.
```typescript
// Add at the end of the file
export const guideCms = createCmsModule('guide');
```
--------------------------------
### Get User Benefits (TypeScript)
Source: https://nexty.dev/docs/guide/payment/credit-system
Retrieves a user's complete benefits information, including credit balances (subscription and one-time), and subscription details. It queries the database for usage data, processes yearly subscription allocations, fetches subscription information, and constructs a consolidated benefits object. Requires a `userId` as input.
```typescript
export async function getUserBenefits(userId: string): Promise {
// 1. Query credit balance
const usage = await db
.select({
subscriptionCreditsBalance: usageSchema.subscriptionCreditsBalance,
oneTimeCreditsBalance: usageSchema.oneTimeCreditsBalance,
balanceJsonb: usageSchema.balanceJsonb,
})
.from(usageSchema)
.where(eq(usageSchema.userId, userId));
// 2. Process yearly subscription monthly allocation
if (usage[0]) {
await processYearlySubscriptionCatchUp(userId);
}
// 3. Query subscription information
const subscription = await fetchSubscriptionData(userId);
// 4. Build benefits object
return {
activePlanId: subscription?.status === 'active' ? subscription.planId : null,
subscriptionStatus: subscription?.status,
currentPeriodEnd: subscription?.currentPeriodEnd,
nextCreditDate: usage[0]?.balanceJsonb?.yearlyAllocationDetails?.nextCreditDate,
totalAvailableCredits: (usage[0]?.subscriptionCreditsBalance || 0) + (usage[0]?.oneTimeCreditsBalance || 0),
subscriptionCreditsBalance: usage[0]?.subscriptionCreditsBalance || 0,
oneTimeCreditsBalance: usage[0]?.oneTimeCreditsBalance || 0,
};
}
```
--------------------------------
### Create New Supabase Database Migration
Source: https://nexty.dev/docs/integration/supabase
Command to generate a new migration file in the `supabase/migrations` directory for designing or modifying database tables. The `` should describe the changes being made.
```shell
pnpm db:new-migration
## eg: pnpm db:new-migration add_image_jobs_table
```
--------------------------------
### Extending Database Schema for New Post Types (TypeScript)
Source: https://nexty.dev/docs/guide/cms
Demonstrates how to extend the PostgreSQL enumeration for post types in `lib/db/schema.ts`. This involves adding a new type, such as 'guide', to the existing `post_type` enum.
```typescript
export const postTypeEnum = pgEnum('post_type', [
'blog',
'glossary',
'guide', // New addition
])
```
--------------------------------
### Defining Page Path
Source: https://nexty.dev/docs/guide/metadata-config
Shows how to set the `path` property, which must start with '/'. This path is used in conjunction with the locale to generate canonical URLs and hreflang links, helping to avoid duplicate content issues and improve international SEO.
```javascript
path: "/about"
```
--------------------------------
### Implement SSG for Locale-Only Dynamic Pages
Source: https://nexty.dev/docs/guide/i18n
Provides an example of implementing Static Site Generation (SSG) for pages where the only dynamic parameter is the locale. It shows how to extract the locale from `params` and use `generateStaticParams` to pre-render pages for all supported locales. This approach is suitable for pages like `/privacy-policy` or `/zh/privacy-policy`.
```typescript
export default async function Page({ params }: { params: Params }) {
// ✅ Extract locale from params for SSG compatibility
// ❌ Don't use getLocale() as it prevents static generation
const { locale } = await params;
// ... other code ...
return (
// ... page content ...
);
}
export async function generateStaticParams() {
return LOCALES.map((locale) => ({
locale,
}));
}
```
--------------------------------
### Page-level Guard for Login (Server Component)
Source: https://nexty.dev/docs/guide/auth-database/auth
Illustrates how to implement a page-level access control using the `AuthGuard` component. This ensures that the content within the `AuthGuard` is only visible to logged-in users, providing a seamless way to protect routes.
```typescript
// Component: components/auth/AuthGuard.tsx already encapsulated
import { AuthGuard } from "@/components/auth/AuthGuard";
export default async function LoginRequiredLayout() {
return (
{/* Content visible only to logged-in users */}
);
}
```
--------------------------------
### Performing Aggregate Statistics
Source: https://nexty.dev/docs/guide/auth-database/database
Shows how to use aggregate functions like COUNT and SUM with filtering, and how to perform daily aggregation using date truncation. This is useful for generating reports and analyzing data trends.
```typescript
import { sql, and, gte, lt, eq, count } from 'drizzle-orm';
const start = new Date('2025-01-01');
const end = new Date('2025-02-01');
// Aggregation with FILTER
const stats = await db
.select({
oneTimeCount: sql`COUNT(*) FILTER (WHERE ${orders.orderType} = 'one_time_purchase')`.mapWith(Number),
monthlyRevenue: sql`COALESCE(SUM(${orders.amountTotal}) FILTER (WHERE ${pricingPlans.recurringInterval} = 'month'), 0)`.mapWith(Number),
})
.from(orders)
.leftJoin(pricingPlans, eq(orders.planId, pricingPlans.id))
.where(and(gte(orders.createdAt, start), lt(orders.createdAt, end)));
// Daily aggregation (date_trunc)
const dt = sql`date_trunc('day', ${orders.createdAt})`;
const daily = await db
.select({ date: dt, count: count(orders.id) })
.from(orders)
.where(and(gte(orders.createdAt, start), lt(orders.createdAt, end)))
.groupBy(dt);
```
--------------------------------
### Sign Out (Client-side)
Source: https://nexty.dev/docs/guide/auth-database/auth
Shows how to implement the sign-out functionality on the client-side using `authClient.signOut()`. It includes an option to refresh the router after a successful sign-out, ensuring the UI is updated accordingly.
```typescript
await authClient.signOut({
fetchOptions: {
onSuccess: () => router.refresh(),
},
});
```
--------------------------------
### Page-level Guard for Admin Role (Server Component)
Source: https://nexty.dev/docs/guide/auth-database/auth
Shows how to use the `AuthGuard` component to restrict access to specific pages or sections for users with the 'admin' role. This is a declarative way to enforce role-based access control at the component level.
```typescript
// Component: components/auth/AuthGuard.tsx already encapsulated
import { AuthGuard } from "@/components/auth/AuthGuard";
export default async function AdminLayout() {
return (
{/* Content visible only to admins */}
);
}
```
--------------------------------
### Inserting Data and Returning Primary Key
Source: https://nexty.dev/docs/guide/auth-database/database
Demonstrates how to insert a new record into the 'orders' table and retrieve the newly generated primary key. It uses 'InferInsertModel' for type safety and returns the 'id' of the inserted order.
```typescript
import { InferInsertModel, eq, count } from 'drizzle-orm';
type NewOrder = InferInsertModel;
const orderData: NewOrder = {
userId,
provider: 'stripe',
providerOrderId: 'pi_123',
status: 'succeeded',
orderType: 'one_time_purchase',
amountTotal: '9.90', // Numeric type recommended to use string storage
currency: 'usd',
};
const inserted = await db.insert(orders).values(orderData).returning({ id: orders.id });
const orderId = inserted[0]?.id;
```
--------------------------------
### Get Last Used Login Method (Client-side)
Source: https://nexty.dev/docs/guide/auth-database/auth
Provides a utility function `authClient.getLastUsedLoginMethod()` to retrieve the user's most recent login method. This can be used to optimize the login flow by pre-selecting or suggesting the last used method.
```typescript
const lastLogin = authClient.getLastUsedLoginMethod(); // "google" | "github" | "email" | undefined
```
--------------------------------
### Updating Dashboard List Page for New Content Module (TypeScript)
Source: https://nexty.dev/docs/guide/cms
Shows modifications needed in `guides/page.tsx` to adapt the post listing functionality for the 'guide' post type. This includes updating the `postType` parameter in `listPostsAction` and the `PostDataTable` configuration.
```typescript
// Change postType to "guide"
const result = await listPostsAction({
pageIndex: 0,
pageSize: PAGE_SIZE,
postType: "guide", // Modify here
});
// Update configuration
```
--------------------------------
### Social Login (Google/GitHub) (Client-side)
Source: https://nexty.dev/docs/guide/auth-database/auth
Provides a client-side function `authClient.signIn.social()` to initiate login via social providers like Google or GitHub. It allows specifying callback URLs for success and error scenarios, and includes optional callbacks for request, success, and error events.
```typescript
await authClient.signIn.social(
{
provider: "google", // or "github"
callbackURL: window.location.origin, // where to redirect after login
errorCallbackURL: "/redirect-error",
},
{
onRequest: () => {/* loading UI */},
onSuccess: (ctx) => {/* logged in */},
onError: (ctx) => {/* error handling */},
}
);
```
--------------------------------
### Configure Plausible Analytics Environment Variables in Dockerfile
Source: https://nexty.dev/docs/start-project/dokploy
This section sets environment variables for Plausible Analytics, an open-source, privacy-focused analytics tool. `PLAUSIBLE_API_KEY` and `PLAUSIBLE_URL` are used to configure the connection and data reporting to the Plausible instance. The variables are sourced from the build environment. Dependencies include a Plausible Analytics setup.
```dockerfile
# PLAUSIBLE
ENV PLAUSIBLE_API_KEY=${PLAUSIBLE_API_KEY}
ENV PLAUSIBLE_URL=${PLAUSIBLE_URL}
```
--------------------------------
### Stripe CLI Webhook Testing (Shell)
Source: https://nexty.dev/docs/guide/payment/webhook-handling
Configures and tests Stripe webhooks locally using the Stripe Command Line Interface. It involves installing the CLI, logging in, forwarding webhooks to a local server, and triggering test events.
```shell
# Install Stripe CLI
brew install stripe/stripe-cli/stripe
# Login
stripe login
# Forward webhooks to local
stripe listen --forward-to localhost:3000/api/stripe/webhook
# Trigger test event
stripe trigger checkout.session.completed
```
--------------------------------
### Set up Node.js Runtime Environment in Dockerfile
Source: https://nexty.dev/docs/start-project/dokploy
This section defines the Docker image for the runtime stage, using `node:20-alpine`. It sets the working directory to `/app` and configures the Node.js environment for production. It also creates a system user and group (`nodejs` and `nextjs`) for secure execution and copies the necessary built artifacts from the builder stage. Dependencies include the `node:20-alpine` Docker image.
```dockerfile
# ============================================
# Runtime Stage
# ============================================
FROM node:20-alpine AS runner
WORKDIR /app
# ============================================
# No build arguments or environment variables needed here
# All runtime secrets will be injected by the container runtime (dokploy)
# Next.js standalone mode reads environment variables at runtime
# ============================================
# Set production environment
ENV NODE_ENV=production
# Disable Next.js telemetry
ENV NEXT_TELEMETRY_DISABLED=1
# Create system user group and user (for secure execution)
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# Copy necessary files from build stage
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
```
--------------------------------
### Local Webhook Testing with ngrok (Shell)
Source: https://nexty.dev/docs/guide/payment/webhook-handling
Exposes a local development server to the public internet using ngrok, enabling testing of webhooks from external services like Creem. After starting ngrok, the provided URL is used to configure the webhook in the Creem dashboard.
```shell
ngrok http 3000
# Use the URL provided by ngrok to configure the webhook
```
--------------------------------
### Configure Database Storage for Better Auth (TypeScript)
Source: https://nexty.dev/docs/guide/auth-database/rate-limiting
This snippet demonstrates how to configure database storage for Better Auth's rate limiting. It specifies that database storage should be enabled and provides a model name for the database table. Note that database storage requires manual implementation and scheduled cleanup of expired records.
```typescript
rateLimit: {
enabled: true,
storage: "database",
modelName: "rateLimit", // Table name
// ...
}
```
--------------------------------
### Create Pricing Plan via API
Source: https://nexty.dev/docs/guide/payment/prices-card
Uses the `createPricingPlanAction` function to create a new pricing plan. Requires plan details including pricing, localization, and features. Returns the result of the creation operation.
```javascript
import { createPricingPlanAction } from '@/actions/prices/admin';
const result = await createPricingPlanAction({
planData: {
environment: 'live',
cardTitle: 'Pro Plan',
provider: 'stripe',
stripePriceId: 'price_xxx',
paymentType: 'recurring',
recurringInterval: 'month',
price: '29.00',
currency: 'USD',
displayPrice: '$29',
priceSuffix: 'month',
isActive: true,
isHighlighted: false,
displayOrder: 1,
langJsonb: {
en: {
cardTitle: 'Pro Plan',
cardDescription: 'Best for professionals',
displayPrice: '$29',
priceSuffix: 'month',
buttonText: 'Get Started',
features: [
{ description: 'Feature 1', included: true }
]
}
},
benefitsJsonb: {
monthlyCredits: 100
},
features: [
{ description: 'Unlimited projects', included: true }
]
},
locale: 'en'
});
```
--------------------------------
### Get Client User Benefits (TypeScript)
Source: https://nexty.dev/docs/guide/payment/credit-system
Provides a client-side function to fetch user benefits. It first obtains the current user's session and ID, then calls `getUserBenefits` to retrieve the data. Returns an `ActionResult` containing the benefits or an error message if the user is unauthorized or an error occurs during fetching.
```typescript
export async function getClientUserBenefits(): Promise> {
const session = await getSession();
const user = session?.user;
if (!user) return actionResponse.unauthorized();
try {
const benefits = await getUserBenefits(user.id);
return actionResponse.success(benefits);
} catch (error: any) {
return actionResponse.error(error.message || 'Failed to fetch user benefits.');
}
}
```
--------------------------------
### Adding Image Path Configuration for New Modules (TypeScript)
Source: https://nexty.dev/docs/guide/cms
Illustrates adding a new image path constant, `GUIDE_IMAGE_PATH`, in `config/common.ts` and integrating it into the `R2_CATEGORIES` array for resource selection.
```typescript
export const GUIDE_IMAGE_PATH = "guide-images";
export const R2_CATEGORIES: R2Category[] = [
{ name: "All", prefix: "" },
{ name: "Admin Uploads", prefix: `${ADMIN_UPLOAD_IMAGE_PATH}/` },
{ name: "Blogs Images", prefix: `${BLOGS_IMAGE_PATH}/` },
{ name: "Glossary Images", prefix: `${GLOSSARY_IMAGE_PATH}/` },
{ name: "Guide Images", prefix: `${GUIDE_IMAGE_PATH}/` }, // New addition
];
```
--------------------------------
### Google One Tap Login (Client-side)
Source: https://nexty.dev/docs/guide/auth-database/auth
Details the client-side implementation of Google One Tap, a passwordless sign-in experience. It uses `authClient.oneTap()` and allows for configuration of success and error handlers, as well as a notification handler for prompt events.
```typescript
import { authClient } from "@/lib/auth/auth-client";
await authClient.oneTap({
fetchOptions: {
onSuccess: () => window.location.reload(),
onError: (ctx) => console.error(ctx.error),
},
onPromptNotification: (note) => console.log(note),
});
```
--------------------------------
### Environment Variables for Supabase and Google Auth
Source: https://nexty.dev/docs/integration/supabase
This snippet shows the necessary environment variables to configure Supabase authentication and Google OAuth, including the Supabase URL, anonymous and service role keys, and the Google Client ID. These are essential for connecting your application to Supabase and enabling Google One Tap sign-in.
```dotenv
# .env.local or .env
NEXT_PUBLIC_SUPABASE_URL=""
NEXT_PUBLIC_SUPABASE_ANON_KEY=""
SUPABASE_SERVICE_ROLE_KEY=""
NEXT_PUBLIC_GOOGLE_CLIENT_ID=""
```
--------------------------------
### Resolve Supabase DB Update Error on Windows
Source: https://nexty.dev/docs/integration/supabase
A workaround for the 'Invalid project ref format' error encountered on Windows when running `pnpm db:update`. This involves modifying the `db:update` script in `package.json` to use a semicolon instead of `&&` for command chaining.
```json
{
"scripts": {
"db:update": "npm run db:push ; npm run db:gen-types"
}
}
```
--------------------------------
### Configure Upstash Redis for Better Auth Storage (TypeScript)
Source: https://nexty.dev/docs/guide/auth-database/rate-limiting
This snippet shows how to configure Upstash Redis as a custom storage solution for Better Auth. It leverages the Upstash Redis client to get and set data with automatic expiration (TTL). This is recommended for production and serverless environments.
```typescript
...(redis && {
customStorage: {
get: async (key: string) => {
const data = await redis!.get<{ key: string; count: number; lastRequest: number }>(key);
return data || undefined;
},
set: async (key: string, value: { key: string; count: number; lastRequest: number }) => {
await redis!.set(key, value, { ex: 120 }); // Auto-expire after 120 seconds
},
},
}),
```
--------------------------------
### Allocate Yearly Subscription Credits
Source: https://nexty.dev/docs/guide/payment/credit-system
Initializes and allocates credits for yearly subscriptions using a monthly allocation approach. It calculates the next credit date and sets up the initial balance and details for yearly allocation, logging the initial grant.
```typescript
if (isYearlyInterval(plan.recurringInterval) && benefits?.totalMonths && benefits?.monthlyCredits) {
await db.transaction(async (tx) => {
const startDate = new Date(currentPeriodStart);
const nextCreditDate = new Date(
startDate.getFullYear(),
startDate.getMonth() + 1,
startDate.getDate()
);
const yearlyDetails = {
yearlyAllocationDetails: {
remainingMonths: benefits.totalMonths - 1,
nextCreditDate: nextCreditDate.toISOString(),
monthlyCredits: benefits.monthlyCredits,
lastAllocatedMonth: `${startDate.getFullYear()}-${(startDate.getMonth() + 1).toString().padStart(2, '0')}`,
relatedOrderId: orderId,
}
};
// Initialize yearly subscription, immediately allocate first month's credits
const updatedUsage = await tx
.insert(usageSchema)
.values({
userId,
subscriptionCreditsBalance: benefits.monthlyCredits,
balanceJsonb: yearlyDetails,
})
.onConflictDoUpdate({
target: usageSchema.userId,
set: {
subscriptionCreditsBalance: benefits.monthlyCredits,
balanceJsonb: sql`coalesce(${usageSchema.balanceJsonb}, '{}'::jsonb) - 'yearlyAllocationDetails' || ${JSON.stringify(yearlyDetails)}::jsonb`,
}
})
.returning({
oneTimeBalanceAfter: usageSchema.oneTimeCreditsBalance,
subscriptionBalanceAfter: usageSchema.subscriptionCreditsBalance,
});
// Record credit log
await tx.insert(creditLogsSchema).values({
userId,
amount: benefits.monthlyCredits,
oneTimeBalanceAfter: updatedUsage[0].oneTimeBalanceAfter,
subscriptionBalanceAfter: updatedUsage[0].subscriptionBalanceAfter,
type: 'subscription_grant',
notes: 'Yearly plan initial credits granted',
relatedOrderId: orderId,
});
});
}
```
--------------------------------
### JSONB Field Operations: Merge, Overwrite, and Delete Keys (TypeScript)
Source: https://nexty.dev/docs/guide/auth-database/database
Illustrates how to manipulate JSONB fields within a database, covering scenarios like updating monthly and yearly quota details. The examples show how to overwrite specific keys, merge new data while removing old keys to prevent data remnants, and delete individual keys from a JSONB object.
```typescript
// Overwrite monthlyAllocationDetails field
const details = { monthlyAllocationDetails: { monthlyCredits: 1000 } };
await db.insert(usage)
.values({ userId, subscriptionCreditsBalance: 1000, balanceJsonb: details })
.onConflictDoUpdate({
target: usage.userId,
set: {
subscriptionCreditsBalance: 1000,
balanceJsonb: sql`coalesce(${usage.balanceJsonb}, '{}'::jsonb) - 'monthlyAllocationDetails' || ${JSON.stringify(details)}::jsonb`,
},
});
// Delete a specific key (e.g., cancel yearly allocation info)
await db.update(usage).set({
balanceJsonb: sql`coalesce(${usage.balanceJsonb}, '{}'::jsonb) - 'yearlyAllocationDetails'`,
}).where(eq(usage.userId, userId));
```
--------------------------------
### GitHub Actions Workflow for Docker Image Build and Push
Source: https://nexty.dev/docs/start-project/dokploy
This workflow automates the process of building a Docker image for the Nexty.dev project and pushing it to a container registry. It checks out the repository, logs into the registry, extracts metadata for tagging, sets up Docker Buildx, and then builds and pushes the image. It utilizes environment variables and secrets for registry authentication and build arguments.
```yaml
name: Create and publish a Docker image
on:
push:
branches: ["main"]
workflow_dispatch:
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push-image:
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
packages: write
attestations: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Log in to the Container registry
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push Docker image
id: push
timeout-minutes: 25
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
# ⚠️
# It's recommended to only include environment variables needed for the build
# Environment variables starting with NEXT_PUBLIC_ use vars
# Other environment variables use secrets
build-args: |
NEXT_PUBLIC_SITE_URL=${{ vars.NEXT_PUBLIC_SITE_URL }}
NEXT_PUBLIC_PRICING_PATH=${{ vars.NEXT_PUBLIC_PRICING_PATH }}
NEXT_PUBLIC_OPTIMIZED_IMAGES=${{ vars.NEXT_PUBLIC_OPTIMIZED_IMAGES }}
NEXT_PUBLIC_LOGIN_MODE=${{ vars.NEXT_PUBLIC_LOGIN_MODE }}
NEXT_PUBLIC_GITHUB_CLIENT_ID=${{ vars.NEXT_PUBLIC_GITHUB_CLIENT_ID }}
NEXT_PUBLIC_GOOGLE_CLIENT_ID=${{ vars.NEXT_PUBLIC_GOOGLE_CLIENT_ID }}
NEXT_PUBLIC_TURNSTILE_SITE_KEY=${{ vars.NEXT_PUBLIC_TURNSTILE_SITE_KEY }}
NEXT_PUBLIC_ENABLE_STRIPE=${{ vars.NEXT_PUBLIC_ENABLE_STRIPE }}
NEXT_PUBLIC_DEFAULT_CURRENCY=${{ vars.NEXT_PUBLIC_DEFAULT_CURRENCY }}
NEXT_PUBLIC_GOOGLE_ID=${{ vars.NEXT_PUBLIC_GOOGLE_ID }}
NEXT_PUBLIC_PLAUSIBLE_DOMAIN=${{ vars.NEXT_PUBLIC_PLAUSIBLE_DOMAIN }}
NEXT_PUBLIC_PLAUSIBLE_SRC=${{ vars.NEXT_PUBLIC_PLAUSIBLE_SRC }}
NEXT_PUBLIC_DISCORD_INVITE_URL=${{ vars.NEXT_PUBLIC_DISCORD_INVITE_URL }}
NEXT_PUBLIC_AUTO_FILL_AI_PROVIDER=${{ vars.NEXT_PUBLIC_AUTO_FILL_AI_PROVIDER }}
NEXT_PUBLIC_AUTO_FILL_AI_MODEL_ID=${{ vars.NEXT_PUBLIC_AUTO_FILL_AI_MODEL_ID }}
NEXT_PUBLIC_DAILY_AI_AUTO_FILL_LIMIT=${{ vars.NEXT_PUBLIC_DAILY_AI_AUTO_FILL_LIMIT }}
NEXT_PUBLIC_DAILY_SUBMIT_LIMIT=${{ vars.NEXT_PUBLIC_DAILY_SUBMIT_LIMIT }}
NEXT_PUBLIC_DAILY_IMAGE_UPLOAD_LIMIT=${{ vars.NEXT_PUBLIC_DAILY_IMAGE_UPLOAD_LIMIT }}
R2_PUBLIC_URL=${{ secrets.R2_PUBLIC_URL }}
R2_ACCOUNT_ID=${{ secrets.R2_ACCOUNT_ID }}
R2_ACCESS_KEY_ID=${{ secrets.R2_ACCESS_KEY_ID }}
R2_SECRET_ACCESS_KEY=${{ secrets.R2_SECRET_ACCESS_KEY }}
R2_BUCKET_NAME=${{ secrets.R2_BUCKET_NAME }}
DATABASE_URL=${{ secrets.DATABASE_URL }}
BETTER_AUTH_SECRET=${{ secrets.BETTER_AUTH_SECRET }}
BETTER_AUTH_GITHUB_CLIENT_SECRET=${{ secrets.BETTER_AUTH_GITHUB_CLIENT_SECRET }}
GOOGLE_CLIENT_SECRET=${{ secrets.GOOGLE_CLIENT_SECRET }}
STRIPE_SECRET_KEY=${{ secrets.STRIPE_SECRET_KEY }}
STRIPE_PUBLISHABLE_KEY=${{ secrets.STRIPE_PUBLISHABLE_KEY }}
STRIPE_WEBHOOK_SECRET=${{ secrets.STRIPE_WEBHOOK_SECRET }}
STRIPE_CUSTOMER_PORTAL_URL=${{ secrets.STRIPE_CUSTOMER_PORTAL_URL }}
```
--------------------------------
### Blog Article Multilingual Metadata (TypeScript)
Source: https://nexty.dev/docs/guide/metadata-config
Generates metadata for a blog article, including title, description, featured image, and multilingual support. It checks for available localized versions of the article and passes them via `availableLocales`. Handles 404 for non-existent posts.
```typescript
export async function generateMetadata({
params,
}: MetadataProps): Promise {
const { locale, slug } = await params
const { post } = await getPostBySlug(slug, locale)
if (!post) {
return constructMetadata({
title: "404",
description: "Page not found",
noIndex: true, // Don't index 404 pages
locale: locale as Locale,
path: `/blogs/${slug}`,
})
}
// Detect which languages have this article
const availableLocales: string[] = []
for (const checkLocale of LOCALES) {
const { post: localePost } = await getPostBySlug(slug, checkLocale)
if (localePost) {
availableLocales.push(checkLocale)
}
}
return constructMetadata({
title: post.title,
description: post.description,
images: post.featuredImageUrl ? [post.featuredImageUrl] : [],
locale: locale as Locale,
path: `/blogs/${post.slug.replace(/^\/, '')}`,
availableLocales: availableLocales.length > 0 ? availableLocales : undefined,
})
}
```
--------------------------------
### Create Creem Customer Portal Session
Source: https://nexty.dev/docs/guide/payment/orders-subscriptions
Creates a customer portal link for a Creem subscription. It first retrieves the user's Creem customer ID from the local database based on the logged-in user's ID. Then, it calls a helper function to generate the portal link and redirects the user to it. Requires database access and a function to create Creem portal links.
```typescript
export async function createCreemPortalSession(): Promise {
const session = await getSession();
const user = session?.user;
if (!user) {
redirect('/login');
}
// 1. Get user's Creem subscription
const subscriptionResults = await db
.select({
customerId: subscriptionsSchema.customerId,
})
.from(subscriptionsSchema)
.where(
and(
eq(subscriptionsSchema.userId, user.id),
eq(subscriptionsSchema.provider, 'creem')
)
)
.orderBy(desc(subscriptionsSchema.createdAt))
.limit(1);
const subscription = subscriptionResults[0];
if (!subscription) {
throw new Error('Creem subscription not found');
}
// 2. Create portal link
const portalUrl = await createCreemCustomerPortalLink(subscription.customerId);
if (!portalUrl) {
throw new Error('Failed to create Creem portal link');
}
// 3. Redirect to portal
redirect(portalUrl);
}
```