Package Exports
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (saas-forge) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
Project layout and technology stack
- Monorepo toolchain: Turborepo + pnpm workspaces.
- Root scripts delegate to Turbo:
build,dev,lint. - Workspaces are defined in
pnpm-workspace.yaml(apps/*,packages/*).
- Root scripts delegate to Turbo:
- Primary app:
apps/web– Next.js 15 (App Router) SaaS frontend.- Uses React 19,
@tanstack/react-query,@trpc/*,better-auth, and a shared UI library. - TRPC API is exposed via
app/api/trpc/[trpc]/route.tsand consumed via React Query. - Caching and rate-limiting rely on environment configuration (Upstash Redis, external webhooks, etc.).
- Uses React 19,
- Shared packages (under
packages/):@workspace/ui: Design system and shared React components (shadcn/ui-based, MDX and Notion renderers, various visual effects). Next.js is configured to transpile this package (apps/web/next.config.mjs).@workspace/auth: Better Auth configuration and client helpers. Orchestrates Prisma-based persistence, social login providers, email flows, and user metadata.@workspace/database: Prisma-based Postgres client and schema. Exposes a singleton Prisma client viasrc/client.tsand includes Prisma schema + migration helpers.@workspace/email: Email delivery utilities using Resend + React Email templates for verification, password reset, support, and contacts.@workspace/cms: Notion data access layer and transformation utilities (wrapped and re-exported as@workspace/cms/notion/...). Used by the web app to fetch landing and documentation content.@workspace/observability: Centralized logging via Winston + Logtail. Used primarily by the CMS / Notion layer.@workspace/eslint-config,@workspace/typescript-config: Shared ESLint and TypeScript configs.
- CLI:
scripts/cli.jsis exposed as thesaas-forgebinary when this repo is installed as a package. It clones this repo into a target directory, copiesapps/web/.env.exampleto.env, optionally patchesNEXT_PUBLIC_THEME, then runspnpm install.
High-level architecture and data flow
Next.js app (apps/web)
- Routing and layouts:
- Uses the App Router under
apps/web/app. - Authentication-related pages live under
app/(auth)/...and integrate with the Better Auth client from@workspace/auth. - Marketing / legal / documentation pages live under
app/landing,app/landing/doc/[docId], andapp/landing/legal/..., with UI blocks inapps/web/blocksandapps/web/components/landing.
- Uses the App Router under
- TRPC layer (
apps/web/trpc):init.tsdefinescreateTRPCContext,createTRPCRouter, andbaseProcedure. Currently the context only exposes a stubbeduserId; if you add auth-aware logic, extend this context first.routers/_app.tsaggregates feature routers:landingRouter– reads landing page content from Notion via the CMS package.documentationRouter– reads documentation structure and blocks from Notion via the CMS package, with optional Redis caching.supportRouter– proxies support, newsletter subscription, and chatbot messages to external n8n webhooks.
app/api/trpc/[trpc]/route.tswires theappRouterto Next.js API routes usingfetchRequestHandlerand the shared context.trpc/query-client.tsconfigures a shared React Query client with custom dehydration rules.trpc/client.tsxestablishes theTRPCReactProviderthat binds the TRPC client and React Query for client-side use.
- State and caching:
- TRPC queries for landing/docs use a combination of Notion reads and Upstash Redis caching.
apps/web/server/redis.tsexports a configuredRedisclient usingUPSTASH_REDIS_REST_URLandUPSTASH_REDIS_REST_TOKEN.
- Middleware and access control (
apps/web/middleware.ts):- Uses
better-authcookie cache viagetCookieCacheto determine session state. - Defines
publicRoutes,authRoutes, and anapiAuthPrefix:- Public routes bypass auth checks.
- Auth routes redirect logged-in users to
/. - Non-public, non-auth routes require a session and redirect unauthenticated users to
/landing.
- Also configures CORS headers for a specific list of allowed origins; matched routes are defined in the exported
config.matcher.
- Uses
CMS / Notion integration (@workspace/cms)
- Purpose: Provides a stable abstraction over the Notion API, handling pagination, filtering, sorting, and transformation of raw Notion responses into application-friendly objects.
- Key pieces:
src/notion/database/queryDatabase.ts:queryDatabasewraps@notionhq/clientqueries and logs requests + responses via the sharedloggerfrom@workspace/observability/winston-logger.queryNotionDatabaseandqueryAllNotionDatabaseimplement filtered and fully-paginated queries, mapping results throughmodifyResult.- Filter/sort construction utilities (
constructFilterBody,constructSortBody,modifyFilter,modifySort) convert high-level filter descriptors into Notion query payloads.
src/notion/page/createPage.ts:createPageissues raw Notion page creation requests with logging.createNotionPagetranslates a higher-levelpropertiesarray into Notion-compatible properties viamodifyProperty, then returns a normalized result viamodifyResult.
- Utility modules under
src/notion/utilsperform the low-level shape transformation between Notion’s nested JSON and your flatter domain structures (e.g., mapping rich text, relations, IDs, etc.). - Tests in
packages/cms/testing/index.test.tsvalidate the Notion abstraction using Vitest with a mocked@notionhq/client.
- Usage from the app:
apps/web/lib/functions/fetchLandingPageDataFromNotion.tscallsqueryAllNotionDatabasewith specific filters and database IDs to assemble strongly typed domain models for:- Navbar, hero, feature, testimonial, pricing, FAQ, and footer sections.
- Legal pages (cancellation/refund, privacy, terms, contact) that reuse common landing-page properties.
apps/web/lib/functions/fetchDocumentationFromNotion.tsusesqueryAllNotionDatabaseto locate the landing page and documentation entries, then returns aDocumentationPropsstructure.- The TRPC routers (
landingProcedures.ts,docProcedures.ts) wrap these functions with Redis-backed caching and input validation.
Authentication, database, and email
- Database (
@workspace/database):- Prisma schema is in
packages/database/prisma/schema.prismatargeting Postgres with theuser_schemaschema. - The generated Prisma client is output to
packages/database/src/generated/prisma, and a singletonPrismaClientinstance is exported frompackages/database/src/client.ts. - This client is used by the auth package to persist users, accounts, sessions, and related data.
- Prisma schema is in
- Authentication (
@workspace/auth):src/better-auth/auth.tsconfigures Better Auth with:- Prisma adapter backed by
@workspace/database/client. - Plugins: OpenAPI and admin (with impersonation and session settings).
- Session configuration (TTL, update age, cookie cache).
- Additional user fields (e.g.,
creditsUsed,creditsTotal). - Social providers (GitHub, Google, LinkedIn) configured via
AUTH_*environment variables. - Email/password flows with verification and password reset, delegating to email utilities.
- Email verification logic that checks whether a user signed up via social login and skips verification emails in that case.
- Prisma adapter backed by
src/better-auth/auth-client.tsdefines acreateAuthClientinstance (authClient) with an optionalbaseURLand exposessignIn,signOut, anduseSessionhelpers for the frontend.
- Email (
@workspace/email):src/resend/index.tswraps the Resend SDK to send:- Verification emails (using the
EmailVerificationReact Email template). - Reset password emails (using the
ResetPasswordtemplate). - Simple support emails and newsletter contact creation.
- Verification emails (using the
- Templates under
src/templatesdefine Tailwind-styled React Email components. - Auth flows in
@workspace/authcall these helpers directly, so any changes to email layout or sender/recipient behaviour should be made here.
Observability
- Logger (
@workspace/observability):src/winston-logger.tsconfigures a Winston logger with:- Console transport.
- Logtail transport (using
BETTERSTACK_TELEMETRY_SOURCE_TOKENandBETTERSTACK_TELEMETRY_INGESTING_HOST).
- Formats logs with timestamp + colorization and is used throughout the CMS / Notion layer for database and page operations.
UI / Design system (@workspace/ui)
- Centralized component library for the monorepo:
src/components/shadcn/*: shadcn/ui primitives.src/components/mdx/*: MDX-specific components and helpers (e.g., headings, lists, table of contents, code blocks).src/components/notion/*: Renderers for Notion blocks and rich text.src/components/aceternity/*andsrc/components/misc/*: Visual/animated components shared across the app.
- The README describes how to add new shadcn components so that they land in
packages/ui/src/componentsand can be imported from@workspace/ui.
Commands and workflows
All commands below are intended to be run from the repository root unless otherwise noted.
Installation and setup
- Install dependencies (respecting the configured package manager and Node engine):
pnpm install
- Environment configuration:
- Copy
apps/web/.env.exampletoapps/web/.envand fill in the required environment variables (Notion API credentials, Redis, auth providers, Resend, n8n webhook URLs, etc.). - The
saas-forgeCLI (scripts/cli.js) performs this copy and can optionally setNEXT_PUBLIC_THEMEwhen used from a fresh clone.
- Copy
Running the app
- Start all dev tasks via Turbo:
pnpm dev
- Run only the web app dev server:
pnpm --filter web dev
- Build the entire workspace:
pnpm build
- Build only the web app:
pnpm --filter web build
Linting, formatting, and type-checking
- Lint all workspaces:
pnpm lint
- Lint only the web app:
pnpm --filter web lint
- Auto-fix lint issues in the web app:
pnpm --filter web lint:fix
- Format TypeScript/TSX/Markdown using Prettier:
pnpm format
- Type-check the Next.js app:
pnpm --filter web typecheck
Testing
Currently, the main automated tests live in the CMS / Notion integration.
- Run all CMS tests:
pnpm --filter @workspace/cms test
- Run a single CMS test file (Vitest passes additional args through to the test runner):
pnpm --filter @workspace/cms test -- testing/index.test.ts
Database and Prisma utilities
From the repo root, using pnpm filters:
- Generate Prisma client for the database package:
pnpm --filter @workspace/database postgres:generate
- Apply local dev migrations:
pnpm --filter @workspace/database postgres:migrate
- Reset the dev database (drops data):
pnpm --filter @workspace/database postgres:reset
Notion-driven content
- The landing page, documentation, and legal content are all driven from Notion databases whose IDs and filters are specified via environment variables (e.g.,
LANDING_DATABASE_ID,HERO_DATABASE_ID,DOCUMENTATION_DATABASE_ID, etc.). - The recommended way to change content structure is:
- Update Notion databases and properties.
- Adjust the query and mapping logic in
apps/web/lib/functions/fetchLandingPageDataFromNotion.tsand/orapps/web/lib/functions/fetchDocumentationFromNotion.ts. - If necessary, extend the type definitions in
apps/web/lib/ts-types/*and corresponding UI components inapps/web/blocksand@workspace/ui.
Adding UI components via shadcn
- As noted in
README.md, to add a new shadcn component into the shared UI package:- From the repo root, run (example for
button):pnpm dlx shadcn@latest add button -c apps/web
- The generated component will be placed in
packages/ui/src/components, and you can then import it in the app as:import { Button } from "@workspace/ui/components/button".
- From the repo root, run (example for
How future Warp agents should approach changes
- For backend/data changes:
- Prefer implementing Notion or DB-related logic in the shared packages (
@workspace/cms,@workspace/database,@workspace/auth,@workspace/email) instead of directly inapps/web. - Expose new behaviour via TRPC routers under
apps/web/trpc/routers, then consume them from React components using the TRPC React/Query hooks.
- Prefer implementing Notion or DB-related logic in the shared packages (
- For frontend/UX changes:
- Reuse and extend components in
@workspace/uiwhere possible so they can serve multiple apps if more are added to the monorepo. - Keep marketing/landing-specific composition in
apps/web/blocksandapps/web/components/landing, and avoid coupling them directly to low-level Notion logic.
- Reuse and extend components in
- For auth and middleware:
- Changes to session rules, route protection, or CORS should primarily happen in
apps/web/middleware.tsand@workspace/auth/src/better-auth/auth.ts. - If you need per-request user context inside TRPC, extend
createTRPCContextinapps/web/trpc/init.tsand thread that through procedures instead of reading cookies or headers directly from routers.
- Changes to session rules, route protection, or CORS should primarily happen in