Scaffold a new Next.js application with Effect-TS integration, including shadcn/ui, authentication, database, email, and telemetry services.
Scaffold a new Next.js application with Effect-TS integration, including shadcn/ui (baseui style), authentication, database, email, and telemetry services.
Use this skill when:
npm install -g pnpm)DATABASE_URL (PostgreSQL connection string, e.g., Neon)RESEND_API_KEY (for email)SENTRY_DSN (for error tracking)NEXT_PUBLIC_POSTHOG_KEY (for analytics)# Create the Next.js app with recommended settings
pnpm create next-app@latest <project-name> \
--typescript \
--tailwind \
--eslint \
--app \
--src-dir \
--import-alias "@/*" \
--turbopack
After creation, cd into the project directory.
# Core Effect ecosystem
pnpm add effect @effect/sql-pg @effect/sql-drizzle @effect/opentelemetry @effect/platform-node
# Database (Drizzle ORM)
pnpm add drizzle-orm @neondatabase/serverless @paralleldrive/cuid2
pnpm add -D drizzle-kit
# Authentication (better-auth)
pnpm add better-auth
# Email (Resend)
pnpm add resend
# Telemetry (Sentry + PostHog + OpenTelemetry)
pnpm add @sentry/nextjs @sentry/opentelemetry @opentelemetry/sdk-logs posthog-js
# UI (shadcn/ui prerequisites)
pnpm add class-variance-authority clsx tailwind-merge lucide-react sonner
Initialize shadcn/ui with the preferred configuration:
pnpm dlx shadcn@latest init
When prompted, select:
Important: We prefer Tailwind CSS with the baseui style for shadcn components. This provides a clean, minimal aesthetic that's easy to customize.
Add commonly used components:
pnpm dlx shadcn@latest add button input label card toast
Add more components as needed throughout development:
# Example: adding more components later
pnpm dlx shadcn@latest add dialog dropdown-menu avatar
mkdir -p src/lib/services/auth
mkdir -p src/lib/services/db
mkdir -p src/lib/services/email
mkdir -p src/lib/services/telemetry
mkdir -p src/lib/next-effect
mkdir -p src/lib/utils
mkdir -p src/lib/core/errors
mkdir -p src/app/api/auth/[...all]
mkdir -p src/app/\(auth\)/login
Create each file in sequence. The agent should use the detailed reference documents for complete implementations.
Read: references/db.md for complete implementation
Create these files:
src/lib/services/db/schema.ts - Drizzle table definitionssrc/lib/services/db/live-layer.ts - Effect service layersrc/lib/services/db/types.ts - Effect Schema validators (optional)drizzle.config.ts - Drizzle Kit configurationRead: references/email.md for complete implementation
Create these files:
src/lib/services/email/index.ts - Resend Effect servicesrc/lib/schemas/email.ts - Email validation schema (optional)Read: references/auth.md for complete implementation
Create these files:
src/lib/services/auth/index.ts - BetterAuth Effect servicesrc/lib/services/auth/auth-client.ts - Client-side authsrc/lib/services/auth/get-session.ts - Simple session gettersrc/lib/services/auth/get-session-effect.ts - Effect-wrapped guardssrc/app/api/auth/[...all]/route.ts - Auth API routeNote: Auth depends on Email service for OTP delivery.
Read: references/telemetry.md for complete implementation
Create these files:
src/instrumentation.ts - Server-side Sentry initsrc/instrumentation-client.ts - Client-side PostHog initsrc/lib/services/telemetry/live-layer.ts - OpenTelemetry layersrc/lib/services/telemetry/report-error.ts - Error reportingsrc/lib/services/telemetry/report-warning.ts - Warning reportingRead: references/next-effect.md for complete implementation
Create these files:
src/lib/next-effect/index.ts - Redirect handling for EffectCreate these files:
src/lib/utils/db-retry-policy.ts - Database retry policiessrc/lib/core/errors/index.ts - Common tagged errorsCreate src/lib/layers.ts:
import { Layer } from "effect";
import { DbLayer } from "./services/db/live-layer";
import { BetterAuth, AuthDbLive } from "./services/auth";
import { EmailLive } from "./services/email";
import { TelemetryLayer } from "./services/telemetry/live-layer";
// Auth layer with dependencies
export const AuthLayer = Layer.provide(
BetterAuth.Default,
Layer.merge(AuthDbLive, EmailLive),
);
// Combined app layer
export const AppLayer = Layer.mergeAll(AuthLayer, DbLayer, TelemetryLayer);
Create .env.local:
# Database
DATABASE_URL=postgresql://user:pass@host:5432/dbname?sslmode=require
# Email
RESEND_API_KEY=re_xxxxxxxxxxxx
# Auth
NEXT_PUBLIC_APP_URL=http://localhost:3000
# Telemetry
SENTRY_DSN=https://[email protected]/xxxx
NEXT_PUBLIC_POSTHOG_KEY=phc_xxxxxxxxxxxx
Create eslint.config.mjs:
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";
const eslintConfig = defineConfig([
...nextVitals,
...nextTs,
// Override default ignores of eslint-config-next.
globalIgnores([
// Default ignores of eslint-config-next:
".next/**",
"out/**",
"build/**",
"next-env.d.ts",
]),
]);
export default eslintConfig;
Create .prettierrc.json:
{
"trailingComma": "none",
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"printWidth": 100,
"arrowParens": "avoid"
}
Add PostHog reverse proxy and Sentry:
import type { NextConfig } from "next";
import { withSentryConfig } from "@sentry/nextjs";
const nextConfig: NextConfig = {
async rewrites() {
return [
{
source: "/ph/static/:path*",
destination: "https://us-assets.i.posthog.com/static/:path*",
},
{
source: "/ph/:path*",
destination: "https://us.i.posthog.com/:path*",
},
];
},
};
export default withSentryConfig(nextConfig);
Update package.json:
{
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"lint": "next lint",
"tsc": "tsc --noEmit",
"db:generate": "drizzle-kit generate",
"db:push": "drizzle-kit push",
"db:studio": "drizzle-kit studio"
}
}
# Push schema to database
pnpm db:push
# Optionally open Drizzle Studio to verify
pnpm db:studio
Create src/app/(auth)/login/page.tsx - use patterns from references/next-effect.md
Create src/app/(dashboard)/page.tsx - use patterns from references/next-effect.md
# Type check
pnpm tsc
# Run development server
pnpm dev
Visit http://localhost:3000 to verify the app loads.
TelemetryLayer (standalone)
|
v
EmailLive (standalone)
|
v
AuthLayer (depends on EmailLive + AuthDbLive)
|
v
DbLayer (standalone, separate from AuthDbLive)
|
v
AppLayer (combines all)
For dependency resolution, create files in this order:
src/lib/core/errors/index.ts - Tagged errorssrc/lib/utils/db-retry-policy.ts - Retry policiessrc/lib/services/db/schema.ts - Database schemasrc/lib/services/db/live-layer.ts - Database servicesrc/lib/services/email/index.ts - Email servicesrc/lib/services/auth/index.ts - Auth servicesrc/lib/services/auth/auth-client.ts - Auth clientsrc/lib/services/auth/get-session.ts - Session gettersrc/lib/services/auth/get-session-effect.ts - Session guardssrc/lib/services/telemetry/live-layer.ts - Telemetry layersrc/lib/services/telemetry/report-error.ts - Error reportingsrc/lib/services/telemetry/report-warning.ts - Warning reportingsrc/lib/next-effect/index.ts - Next.js Effect bridgesrc/lib/layers.ts - Layer compositionsrc/instrumentation.ts - Sentry server initsrc/instrumentation-client.ts - PostHog client initsrc/app/api/auth/[...all]/route.ts - Auth APIdrizzle.config.ts - Drizzle configurationEnsure all Effect packages are installed:
pnpm add effect @effect/sql-pg @effect/sql-drizzle @effect/opentelemetry @effect/platform-node
DATABASE_URL is set correctlyEnsure you're using NextEffect.redirect() instead of Next.js redirect() inside Effect pipelines.
SENTRY_DSN and NEXT_PUBLIC_POSTHOG_KEY are setinstrumentation.ts is in src/ (not src/app/)For detailed implementation of each service, see:
references/db.md - Database patternsreferences/email.md - Email patternsreferences/auth.md - Authentication patternsreferences/telemetry.md - Observability patternsreferences/next-effect.md - Next.js integration patternsEach reference document contains: