file: ./content/docs/authentication.mdx meta: { "title": "Authentication", "description": "Learn how authentication works in Dirstarter, including BetterAuth setup, providers, user management, and security" } Dirstarter uses [BetterAuth](https://better-auth.com) for authentication, providing a flexible and secure system with multiple authentication methods and role-based access control. ## Core Features * Magic Link authentication with [Resend integration](/docs/integrations/email) * Social login providers (Google, GitHub) * Role-based access control (admin/user) * Session management * User management interface ## Setup The following environment variables are required: ```dotenv title=".env" BETTER_AUTH_SECRET=your_secret_key // Random string BETTER_AUTH_URL=http://localhost:3000 ``` > You can generate a random string for the secret key using `openssl rand -base64 32`. For every OAuth provider you want to use, you need to set the `clientId` and `clientSecret` provider variables. ```dotenv title=".env" # Google OAuth AUTH_GOOGLE_ID=your_google_client_id AUTH_GOOGLE_SECRET=your_google_client_secret ``` Follow the [BetterAuth documentation](https://www.better-auth.com/docs/authentication/google) for more information where to find the clientId and clientSecret. ### Basic Configuration Authentication is configured in `lib/auth.ts`: ```ts title="lib/auth.ts" export const auth = betterAuth({ database: prismaAdapter(db, { provider: "postgresql", }), socialProviders: { google: { clientId: env.AUTH_GOOGLE_ID, clientSecret: env.AUTH_GOOGLE_SECRET, }, }, account: { accountLinking: { enabled: true, }, }, plugins: [ magicLink({ sendMagicLink: async ({ email, url }) => { // Email sending logic }, }), admin(), // Add other plugins here ], }) ``` For a full list of available configuration options, plugins and providers see the [BetterAuth documentation](https://better-auth.com/docs/). ## Authentication Methods By default, Dirstarter includes 2 authentication methods: Magic Link and Google Login. BetterAuth supports a wide range of authentication methods, including more social providers, email/password, passkeys and more. ### Magic Link Magic Link authentication allows users to sign in without a password. When a user enters their email, they receive a secure link that automatically logs them in. ```tsx title="components/web/auth/login-form.tsx" const handleSignIn = ({ email }: z.infer) => { signIn.magicLink({ email, callbackURL, fetchOptions: { onSuccess: () => { router.push(`/auth/verify?email=${email}`) }, onError: ({ error }) => { toast.error(error.message) }, }, }) } ``` ### Social Login Social login is supported through OAuth providers. Currently configured for Google: ```tsx title="components/web/auth/login-button.tsx" const handleSignIn = () => { signIn.social({ provider, callbackURL, fetchOptions: { onRequest: () => { setIsPending(true) }, onError: ({ error }) => { toast.error(error.message) }, }, }) } ``` ## User Roles Dirstarter implements two user roles: * `admin`: Full access to all features and user management * `user`: Standard user access ## User Management Admins have access to various user management actions: * Edit user details * Change user roles * Ban/unban users * Revoke user sessions * Delete users ![User Management](/docs/user-management.webp) ## Protecting Routes and Actions ### Route Protection Use middleware to protect routes: ```ts title="middleware.ts" export const config = { matcher: ["/admin/:path*", "/auth/:path*"], } ``` Middleware auth check should not be the only protection for your routes. You should also protect routes in the route handler. Protecting a page is done by checking the session in the page. ```tsx title="app/admin/page.tsx" import { auth } from "~/lib/auth" import { redirect } from "next/navigation" import { headers } from "next/headers" export default async function AdminPage({ children }: { children: React.ReactNode }) { const session = await auth.api.getSession({ headers: await headers() }) if (session?.user.role !== "admin") { redirect("/auth/login") } return
{children}
} ``` ### Action Protection There are 2 predefined server actions procedures that can be used to protect actions: * `userProcedure`: Protects actions that require a user to be authenticated * `adminProcedure`: Protects actions that require a user to be an admin ```ts title="lib/safe-actions.ts" export const userProcedure = createProcedure().handler(async () => { const session = await auth.api.getSession({ headers: await headers() }) if (!session?.user) { throw new Error("User not authenticated") } return { user: session.user } }) export const adminProcedure = createProcedure(userProcedure).handler(async ({ ctx }) => { if (ctx.user.role !== "admin") { throw new Error("User not authorized") } return { user: ctx.user } }) ``` You can also create your own procedures and protect them with the same logic. For more information on how to create and use procedures, see the [Zod Safe Actions documentation](https://zsa.vercel.app). To use the procedures in your server actions, create them with the `createServerAction` function from ZSA: ```tsx title="actions/create-tool.ts" export const createTool = userProcedure .createServerAction() .input(z.object({ name: z.string(), description: z.string(), })) .handler(async ({ input, ctx: { user } }) => { // Create a new tool const tool = await db.tool.create({ data: { name: input.name, description: input.description, ownerId: user.id, // You can be certain that the user is authenticated }, }) return tool }) ``` ## Client-Side Usage ### Session Management ```tsx title="components/auth-button.tsx" 'use client' import { useSession, signIn, signOut } from '~/lib/auth-client' export function AuthButton() { const { data: session, isPending } = useSession() if (isPending) { return
Loading...
} if (session) { return ( ) } return ( ) } ``` ### Admin Checks ```tsx const { data: session } = useSession() if (session?.user.role === 'admin') { // Show admin UI } ``` ## Email Templates Magic link emails use React Email for beautiful, responsive templates: ```tsx title="emails/login-link.tsx" const EmailLoginLink = ({ url, ...props }: EmailProps) => { return ( Welcome to {config.site.name}! Please click the magic link below to sign in to your account. Sign in to {config.site.name} or copy and paste this URL into your browser: {url} ) } ``` Always implement proper authorization checks on both the client and server to secure your application. file: ./content/docs/automation.mdx meta: { "title": "Automation", "description": "Learn how Dirstarter uses AI and third-party services for automating directory content generation" } Dirstarter uses AI and third-party services for automating directory content generation. ## How It Works We use a set of tools to automate the content generation. This **speeds up the process** and **ensures a consistent output**. Here's a step-by-step guide on how the automation works: A tool with URL is added to the directory Admin reviews the tool and schedules it for publishing [Jina AI](https://jina.ai) scrapes the website and generates a structured JSON object [Google Gemini](https://gemini.google.com) generates structured data and categorises the content Screenshot of the website is taken using [Screenshot API](/docs/integrations/media) Favicon is automatically extracted from the website The generated content is saved to your database ## Setup In order to use the automation, you need to set up the following: ### Jina AI We use [Jina AI](https://jina.ai/reader) to scrape the website and generate a structured JSON object. They provide a very generous free tier without an API key, but if you run out of credits, you can create a API key to get 1,000,000 free tokens. ```dotenv title=".env" JINA_API_KEY=your_api_key ``` ### Google Gemini Create a [Google Gemini](https://aistudio.google.com/app/apikey) account and add the following environment variable: > We chose Gemini as a default model because it's the most cost-effective option. If you need more powerful model, you can use [Claude](https://claude.ai) or any other LLM supported by [Vercel AI SDK](https://sdk.vercel.ai). ```dotenv title=".env" GEMINI_API_KEY=your_api_key ``` ## Customizing Generation You can customize the generation by: 1. Modifying the system prompt in the `/api/ai/generate-content` function 2. Adjusting the schema to include additional fields 3. Changing the temperature parameter (lower for more consistent results) file: ./content/docs/blog.mdx meta: { "title": "Blog and Static Pages", "description": "Learn how to use Content Collections for managing blog posts and static MDX content in your directory website." } Dirstarter uses [Content Collections](https://content-collections.dev/) for managing blog posts and static MDX content. This system provides a type-safe way to handle markdown content with frontmatter validation and MDX support. ## Configuration The core configuration is defined in `content-collections.ts`: ```ts title="content-collections.ts" import { defineCollection, defineConfig } from "@content-collections/core" import { type Options, compileMDX } from "@content-collections/mdx" import rehypeAutolinkHeadings from "rehype-autolink-headings" import rehypeSlug from "rehype-slug" const mdxOptions: Options = { rehypePlugins: [rehypeSlug, rehypeAutolinkHeadings], } const posts = defineCollection({ name: "posts", directory: "content/posts", include: "**/*.md", schema: z => ({ title: z.string(), description: z.string(), image: z.string().optional(), publishedAt: z.string(), updatedAt: z.string().optional(), author: z .object({ name: z.string(), image: z.string(), twitterHandle: z.string(), }) .optional(), }), transform: async (data, context) => { const content = await compileMDX(context, data, mdxOptions) return { ...data, content } }, }) export default defineConfig({ collections: [posts], }) ``` ## Blog Post Structure Blog posts are stored in `content/posts/` directory as MDX files. Each post requires specific frontmatter fields: ```markdown title="content/posts/example.md" --- title: "The Rise of Edge Computing in Modern Web Development" description: "Explore how edge computing is revolutionizing web development..." image: "/content/example.webp" publishedAt: 2025-03-29 author: name: John Doe image: "/authors/john-doe.webp" twitterHandle: "john-doe" --- ``` ## Adding New Content Collections To add a new content collection for static MDX content: Define a new collection in `content-collections.ts`: ```ts title="content-collections.ts" const docs = defineCollection({ name: "docs", directory: "content/docs", include: "**/*.md", schema: z => ({ title: z.string(), description: z.string(), category: z.string(), }), transform: async (data, context) => { const content = await compileMDX(context, data, mdxOptions) return { ...data, content } }, }) export default defineConfig({ collections: [posts, docs], }) ``` Create corresponding directory structure: ``` content/ ├── posts/ # Blog posts └── docs/ # Documentation pages ``` Access the content in your components: ```ts title="app/blog/page.tsx" import { allDocs } from "content-collections" export default function DocsPage() { return (
{allDocs.map(doc => (

{doc.title}

{doc.description}

{/* Render doc.content */}
))}
) } ```
## Related Resources * [Content Collections Documentation](https://content-collections.dev/) * [MDX Documentation](https://mdxjs.com/) * [Zod Schema Validation](https://zod.dev/) file: ./content/docs/content.mdx meta: { "title": "Content Management", "description": "Learn about the content management system and workflow in Dirstarter" } Dirstarter provides a robust content management system that handles both user submissions and admin-created content. This guide explains the complete content workflow and available features. ## Content Workflow ### User Submissions Users can submit new tools through the submission form. Each submission includes: * Tool details (name, description, URL) * Submitter details (name, email) ![Submission Form](/docs/content-submit.webp) ### Admin Review Process When a tool is submitted: * It appears in the admin dashboard under "Draft" * If marked as expedited, admins receive immediate notifications * Admins can review and accept or delete submissions ![Admin Review](/docs/content-edit.webp) ### Content Scheduling Once approved, admins can: * Schedule the content for future publication * Set publication date and time * Add final touches to the content * Preview how it will appear on the site * Notify the submitter by email with a publication date and a link to skip the queue. ![Content Scheduling](/docs/content-schedule.webp) ### Automated Processing The system is capable of automatically processing submissions. Once a draft is created, you can generate a high-quality content and media for it. * High-quality AI generated content based on the scraped content * Screenshots generated using [ScreenshotOne](https://kulp.in/screenshotone) * Optimized favicons via Google Favicon API ![Automated Processing](/docs/content-automation.webp) ### Automated Publication At the scheduled time: * A [cron job](/docs/cron-jobs) automatically publishes the content * Content becomes immediately visible on the site * [Sitemap](/docs/seo#sitemap-generation) is updated and cache is invalidated * [Email](/docs/integrations/email) is sent to the submitter with a link to the published content. ![Automated Publication](/docs/content-email.webp) ## Manual Content Management Admins can also create and manage content directly through the admin panel. You can **structure everything hierarchically**, make certain items stand out as **featured**, and create clear relationships between different pieces of content. Our admin interface is packed with **sophisticated data tables** that make managing content feel like a walk in the park. You can **filter and sort** your way through content, take **quick actions** right from the table, and handle **bulk operations** without breaking a sweat. Even with tons of content, you'll stay in control with our **intuitive design** that makes everything just work. ![Data Tables](/docs/content-management.webp) file: ./content/docs/cron-jobs.mdx meta: { "title": "Cron Jobs", "description": "Learn how to use cron jobs in Dirstarter to automate tasks" } Dirstarter recommends hosting your project on Vercel for the best experience. We provide a **pre-configured cron job** for publishing tools on schedule. [Vercel offers](https://vercel.com/docs/cron-jobs) up to 2 cron jobs running in the Hobby plan (free tier) and 40 jobs per month in the Pro plan. It is by far the easiest way to set up automated publishing on schedule, but you can also use any other cron job service you want. A pretty popular, free service for cron jobs is [cron-job.org](https://cron-job.org/). ## Publish Tools Job The publish tools job is a cron job that publishes scheduled content. It is configured to be triggered every day at midnight. ```json title="vercel.json" { "crons": [ { "path": "/api/cron/publish-tools", "schedule": "0 0 * * *" // Every day at midnight } ] } ``` > You can use [crontab guru](https://crontab.guru/) to generate a desired cron job schedule. file: ./content/docs/deployment.mdx meta: { "title": "Deployment", "description": "Learn how to deploy your Dirstarter project to Vercel" } Dirstarter is built with Next.js, which means you can deploy it to any platform that supports Next.js applications. Since it is a framework built by Vercel, you will get the best developer experience and performance when deploying to [Vercel](https://vercel.com). This guide will walk you through the deployment process for Vercel. We'll be adding more deployment guides for other platforms in the future. ## Prerequisites Before deploying your Dirstarter project, make sure you have: 1. A Git repository with your project 2. An account on [Vercel](https://vercel.com) 3. Your project's [environment variables](/docs/environment-setup) configured for production ## Deployment Steps Push your code to a Git repository (GitHub, GitLab, or Bitbucket) Go to [Vercel Dashboard](https://vercel.com/dashboard) and click "Add New Project" Import your Git repository ![Vercel Import](/docs/vercel-welcome.webp) Configure your project settings: * Framework Preset: Next.js * Build Command: `npm run build` (or `yarn build` if using Yarn) * Output Directory: `.next` * Install Command: `npm install` (or `yarn install`) > You can leave the default settings if you're not sure what to do. Add your [environment variables](/docs/environment-setup): ```dotenv DATABASE_URL="your_database_url" BETTER_AUTH_SECRET="your_secret" BETTER_AUTH_URL="your_vercel_url" ... ``` Click "Deploy" and your project will be deployed. ## Automatic Deployments Vercel automatically deploys your application when you: * Push to the main branch * Create a new pull request * Push to a branch with a preview deployment enabled Every pull request gets a unique **preview URL**, making it easy to test changes before merging to production. ## Custom Domain 1. Go to your project settings in Vercel 2. Navigate to the "Domains" section 3. Add your custom domain 4. Follow the DNS configuration instructions file: ./content/docs/environment-setup.mdx meta: { "title": "Environment Setup", "description": "Configure environment variables for your Dirstarter application", "icon": "LockOpen" } Dirstarter requires several environment variables to function properly. This guide explains how to set them up. ## Setup Create a `.env` file in the root directory of your project. You can copy the example file as a starting point: ```bash title="terminal" cp .env.example .env ``` Never commit your `.env` file to version control. It contains sensitive information that should be kept private. ## Environment Variables ### Core Configuration | Variable | Description | | ------------------------ | --------------------------------------------------------------------------- | | `NEXT_PUBLIC_SITE_URL` | The public URL of your site (e.g., `http://localhost:3000` for development) | | `NEXT_PUBLIC_SITE_EMAIL` | The contact email for your site | ### Cron Jobs Learn how to set up cron jobs in the [Cron Jobs](/docs/cron-jobs) guide. | Variable | Description | | ------------- | ---------------------------------------- | | `CRON_SECRET` | A random string used to secure cron jobs | ### Database Learn how to set up a database in the [Database Setup](/docs/database/prisma) guide. | Variable | Description | | -------------- | ---------------------------------- | | `DATABASE_URL` | PostgreSQL database connection URL | ### Authentication Learn how to set up authentication in the [Authentication](/docs/authentication) guide. | Variable | Description | | -------------------- | ----------------------------------------------------- | | `BETTER_AUTH_SECRET` | A random string used to hash tokens and sign cookies | | `BETTER_AUTH_URL` | The URL of your site (same as `NEXT_PUBLIC_SITE_URL`) | | `AUTH_GOOGLE_ID` | Google OAuth client ID | | `AUTH_GOOGLE_SECRET` | Google OAuth client secret | ### Redis Learn how to set up Redis in the [Rate Limiting](/docs/integrations/rate-limiting) guide. | Variable | Description | | ----------------------------- | ---------------- | | `REDIS_REST_URL` (optional) | Redis REST URL | | `REDIS_REST_TOKEN` (optional) | Redis REST token | ### Email Service Learn how to set up an Resend email service in the [Email](/docs/integrations/email) guide. | Variable | Description | | --------------------- | ---------------------------------------------- | | `RESEND_API_KEY` | API key for Resend email service | | `RESEND_SENDER_EMAIL` | Default sender email address | | `RESEND_AUDIENCE_ID` | Audience ID for Resend marketing email service | ### Storage Learn how to set up a S3 storage service in the [Storage](/docs/integrations/storage) guide. | Variable | Description | | ---------------------- | ----------------------------- | | `S3_BUCKET` | Name of your S3 bucket | | `S3_REGION` | AWS region for your S3 bucket | | `S3_ACCESS_KEY` | AWS access key ID | | `S3_SECRET_ACCESS_KEY` | AWS secret access key | ### Analytics Learn how to set up an analytics service in the [Analytics](/docs/integrations/analytics) guide. | Variable | Description | | ------------------------------ | ---------------------- | | `NEXT_PUBLIC_POSTHOG_HOST` | PostHog instance URL | | `NEXT_PUBLIC_POSTHOG_API_KEY` | PostHog API key | | `NEXT_PUBLIC_PLAUSIBLE_DOMAIN` | Your Plausible domain | | `NEXT_PUBLIC_PLAUSIBLE_URL` | Plausible instance URL | | `PLAUSIBLE_API_KEY` | Plausible API key | ### AI & Web Scraping Learn how to set up an AI and web scraping service in the [Automation](/docs/automation) guide or use the [Media](/docs/integrations/media) guide. | Variable | Description | | ------------------------------ | ------------------------------------- | | `GOOGLE_GENERATIVE_AI_API_KEY` | Google Gemini API key for AI features | | `JINA_API_KEY` (optional) | Jina API key for web scraping | | `SCREENSHOTONE_ACCESS_KEY` | ScreenshotOne API key | ### Payments Learn how to set up a payment service in the [Payments](/docs/integrations/payments) guide. | Variable | Description | | ----------------------- | ----------------------------- | | `STRIPE_SECRET_KEY` | Stripe secret key | | `STRIPE_WEBHOOK_SECRET` | Stripe webhook signing secret | ## Type-Safe Validation Dirstarter uses the T3 Env package for type-safe environment variable validation. The validation is configured in `env.ts` and ensures that: * All required environment variables are present * Variables have the correct type (string, number, etc.) * URLs are properly formatted * Email addresses are valid * No empty strings are allowed (they're treated as undefined) To skip validation during builds (useful for Docker), set `SKIP_ENV_VALIDATION` flag: ```package-install title="Terminal" npm run build SKIP_ENV_VALIDATION=1 ``` ## Environment-Specific Variables ### Development Environment For development, you can use local services or development credentials: ```dotenv title=".env" NEXT_PUBLIC_SITE_URL="http://localhost:3000" BETTER_AUTH_URL="http://localhost:3000" ``` ### Production Environment For production, use secure, production-ready services and ensure all required variables are set with proper values. ## Verifying Your Environment To verify that your environment variables are correctly set up, run: ```package-install title="Terminal" npm run dev ``` If everything is configured correctly, your application should start without any environment-related errors. ## What's Next? Now that your environment is set up, learn how to create your first directory entry in the [First Steps](/docs/first-steps) guide. file: ./content/docs/first-steps.mdx meta: { "title": "First Steps", "description": "Populate your directory with demo data and add your own tools", "icon": "WandSparkles" } Now that you have Dirstarter installed and configured, let's start with populating your directory with some tools. ## Seeding the Database Dirstarter comes with a default seed data that you can use to jumpstart your directory. This is the easiest way to quickly see what Dirstarter can do and how it works. ```package-install title="Terminal" npm run db:seed ``` That's it! You should now have some tools already created and ready to go. If for some reason demo content is not to your liking, you can can [modify the seed data](/docs/database/prisma#customizing-seed-data) or [import your own from a CSV file](/docs/database/prisma#seeding-from-csv). ## Adding New Tools Demo content is great, but you'll probably want to add your own tools to your directory. Let's walk through the process of adding a new tool to your directory. Start your development server if it's not already running: ```package-install title="Terminal" npm run dev ``` Navigate to `http://localhost:3000/admin` in your browser Log in with the admin credentials: * If you're using the default seed data, use `admin@dirstarter.com` * If you're starting fresh, make sure to add new user in your Postgres database with `admin` role and use it to login > **In development mode, the email is not sent anywhere.** Instead, the content is logged in the console so you can copy the login link and paste it in your browser. Once you login, go to the "Tools" section and click "Add New Tool" Fill in the information and click "Save" to create the entry ## Configuration Dirstarter is an opinionated directory starter, but it's designed to be easily customized. The configuration is split into multiple files for better organization and maintainability. The main configuration is exported from `config/index.ts` and combines all sub-configurations: * `config/ads.ts`: **Ads Configuration** – Configure advertising spots and settings * `config/links.ts`: **Links Configuration** – Define navigation and footer links * `config/metadata.ts`: **Metadata Configuration** – SEO and meta information * `config/search.ts`: **Search Configuration** – Search functionality settings * `config/claims.ts`: **Claims Configuration** – User claim settings * `config/submissions.ts`: **Submissions Configuration** – Form submission settings ## Next Steps Now that you've created your first entry and customized your directory, feel free to explore the documentation at your own pace. Don't hesitate to dive into the codebase and adapt it to your specific needs. file: ./content/docs/getting-started.mdx meta: { "title": "Getting Started", "description": "Learn how to set up and run your directory website in minutes", "icon": "Rocket" } This guide will walk you through setting up Dirstarter. We will go through the process of cloning the project, installing dependencies, setting up your database and running the local development server. ## Prerequisites Before you begin, make sure you have the following installed: * **Node.js 18.x** or later (we recommend using Node.js 20.x or later) * **Git** (version control system) * **A package manager** (npm, yarn, pnpm, or bun) ## Quick Installation ### Create a database Dirstarter requires a Postgres database to work. Before creating your project, make sure to have created a new database and have the connection string ready. The Postgres connection string will look something like this: ```bash postgresql://user:password@host:port/database ``` We've covered some options for hosting your Postgres database in the [database hosting guide](/docs/database/hosting). ### Set up your project There are many ways to download the source code of Dirstarter. We'll cover some of the most common ones below. **Choose the one that best suits your needs.** ### Clone the repository \[!toc] Another great option is to clone the code directly from the Dirstarter's [GitHub repository](https://github.com/dirstarter/dirstarter). ```bash title="Terminal" git clone https://github.com/dirstarter/dirstarter.git your-project-name cd your-project-name ``` Since the repository is read-only, you'll need your own repo to push your changes to. Once you have it, you can replace the remote origin with your own repository. ```bash title="Terminal" git remote set-url origin https://github.com/your-username/your-repo.git ``` ### Download the ZIP file \[!toc] You can also download the ZIP file from the [GitHub repository](https://github.com/dirstarter/dirstarter) and extract it to your desired location. You can then initialize a new Git repository using `git init` and push your changes to it. ```bash title="Terminal" git init git add . git commit -m "Initial commit" git push -u origin main ``` ### Add upstream remote (optional) If you want to be able to pull updates from the Dirstarter repository, you can add the upstream remote. ```bash title="Terminal" git remote add upstream https://github.com/dirstarter/dirstarter.git ``` This will allow for an [easier update process](/docs/codebase/updates) from the Dirstarter repository. ### Set up environment variables Dirstarter uses environment variables to configure the project. You can set them in the `.env` file. First, copy the `.env.example` file to `.env` to have a starting point. ```bash title="Terminal" cp .env.example .env ``` Then, open the `.env` file and set the variables to your desired values. You can find more information about the environment variables in the [Environment Setup](/docs/environment-setup) page. ### Install dependencies Install the dependencies by running the following command: ```package-install title="Terminal" npm install ``` ### Initialize the database Dirstarter uses Prisma to interact with the database. You can initialize the database by running the following command: ```package-install title="Terminal" npm run db:generate # Generate the Prisma client npm run db:migrate deploy # Apply migrations to the database npm run db:seed # Seed the database with the default data ``` This will generate the Prisma client, migrate the database and seed it with the default data. You can find more information about the database in the [Prisma Setup](/docs/database/prisma) page. ### Start the development server Now that you have the database set up, you can start the development server by running the following command: ```package-install title="Terminal" npm run dev ``` This will start the development server on `http://localhost:3000`. The website will be empty at this point, but we'll sort that out in the [First Steps](/docs/first-steps) guide. ## What's Next? Now that you have Dirstarter running, here are some next steps: file: ./content/docs/introduction.mdx meta: { "title": "Introduction", "description": "Introducing Dirstarter, a complete Next.js boilerplate for building directory websites", "icon": "Album" } ## What is Dirstarter? Welcome to Dirstarter, **a complete boilerplate for building directory websites**. This project is designed to accelerate your development with a robust, modular, and extensible foundation. Feel free to use it as is or extend it for your own needs. People use Dirstarter to build directory websites for a variety of reasons: * 🚀 **Fast & Modern** - Built with Next.js 15 App Router and TypeScript * 🎨 **Beautiful UI** - Powered by Tailwind CSS and shadcn/ui components * 📱 **Responsive** - Works great on all devices * 🔍 **SEO Optimized** - Built-in SEO features Whether you're a beginner or an experienced developer, this documentation will guide you through the setup, configuration, and deployment of your directory website. Let's get started! > This guide assumes you have some familiarity with Next.js, Tailwind CSS, and Prisma. If you don't have any of these, please refer to the [Official Documentation](#official-documentation) as we will not cover the basics in this guide. ## Tech Stack [**Next.js 15**](https://nextjs.org)
The React framework for building server-side rendered (SSR), static, and hybrid web applications using React. We've experimented with many different frameworks and have settled on Next.js as the best option for directory websites due to its **performance, scalability, and ease of use**.
[**Prisma**](https://prisma.io)
An ORM (Object-Relational Mapping) tool that helps you access your database in a type-safe way. We use Prisma to define our database schema and to generate the necessary TypeScript types for our application. It also helps us to **write queries in a type-safe way** and to easily migrate our database schema.
[**Tailwind CSS**](https://tailwindcss.com)
A utility-first CSS framework packed with utility classes that can be composed to build any design, directly in your markup. We use it to create a beautiful and **consistent UI design system** you can use to build your directory website.
[**Radix UI**](https://radix-ui.com) / [**shadcn/ui**](https://ui.shadcn.com)
A collection of headless UI components that help you build **accessible and composable user interfaces**. Dirstarter is also compatible with the popular wrapper of Radix called shadcn/ui and enables you to generate pre-designed components with a single command.
[**Better Auth**](https://better-auth.com)
A modern, secure, easy-to-use and framework agnostic **authentication solution**. It provides a comprehensive set of features for user authentication, including email/password, social login, and more. It can easily be extended to support more providers and features.
## Scope of This Documentation This documentation will guide you through configuring, running, and deploying the boilerplate, and will provide links to the official documentation of the underlying technologies where necessary. To fully grasp the boilerplate's capabilities, it's essential to understand these technologies, so be sure to explore their documentation as well. For anything strictly related to the [Next.js directory boilerplate](/), this documentation has you covered! ### Official Documentation For in-depth understanding of the underlying technologies, refer to their official documentation: * Next.js: [Next.js Documentation](https://nextjs.org/docs) * Prisma: [Prisma Documentation](https://www.prisma.io/docs) * Tailwind CSS: [Tailwind CSS Documentation](https://tailwindcss.com/docs) * Radix UI: [Radix UI Documentation](https://www.radix-ui.com/primitives/docs) * shadcn/ui: [shadcn/ui Documentation](https://ui.shadcn.com/docs) * Better Auth: [Better Auth Documentation](https://better-auth.com/docs) Understanding these technologies is crucial for building a successful directory website. ## What's Next? Dive into the documentation, explore the features, and start building your directory website today! 🚀 file: ./content/docs/monetization.mdx meta: { "title": "Monetization", "description": "Learn about different monetization methods available in Dirstarter" } Dirstarter offers multiple ways to monetize your directory website. This guide covers the main monetization methods and how to implement them effectively. ## Premium Listings Dirstarter offers three different listing plans. Feel free to experiment with pricing and extra features to find the best fit for your directory. ![Listing Plans](/docs/monetization-listings.webp) ### Free Standard listing with low priority processing time (depends on the tools in the queue). ### Expedited One-time payment option for fast-track processing. This is a great option for businesses wanting quick visibility. ### Featured Premium placement with homepage spotlight and monthly recurring payment. This is a great option for businesses wanting to stand out from the crowd. Upon submitting, the tool is saved as a draft, and users are redirected to the listing plan selection page where they can choose their preferred plan and complete the payment. If a premium listing is selected, the system notifies administrators to review and activate the submission. For featured listings, the tool receives automatic featured status. When the subscription is canceled, the featured status is automatically removed through a Stripe webhook integration. > For detailed Stripe integration and setup instructions, see our [Payments Guide](/docs/integrations/payments). ## Advertising System Adversing is another great way to monetize your directory. Dirstarter includes a built-in advertising system with two main ad types. ![Advertising System](/docs/monetization-ads.webp) #### Listing Ad Listing ads are displayed on the homepage and in the listings. They are a great way to get more exposure for the advertiser. #### Banner Ad Banner ads are displayed at the top of every page. They usually cost more than listing ads, but they are more visible and can be more effective. ### Ad Management Features * Flexible scheduling system * Automatic placement rotation * Multi-date selection * [Posthog](/docs/integrations/analytics) integration for analytics * Bulk booking discounts (30% off for longer periods - configurable) ## Affiliate Marketing The easiest way to monetize your directory is to use affiliate links. Dirstarter is built with affiliate marketing in mind. There's no special setup required. Just add your affiliate links to the directory and see the sales roll in. ### Performance Tracking A great way to track the performance of your affiliate links is to use link shortening services like [Dub](https://kulp.in/dub). Dub is a link shortening service that allows you to track the performance of your affiliate links. It also allows to share the link analytics with anyone, so can be used to track the click through rate of the ads and other metrics. file: ./content/docs/seo.mdx meta: { "title": "Search Engine Optimization", "description": "Learn how to optimize your Dirstarter website for search engines and social media sharing." } Dirstarter comes with built-in SEO features to help your directory website rank better in search engines and look great when shared on social media. ## Metadata Configuration The metadata configuration in Dirstarter is centralized and type-safe using Next.js's Metadata API. The base configuration is located in `config/metadata.ts`: ```ts title="config/metadata.ts" import type { Metadata } from "next" import { linksConfig } from "~/config/links" import { siteConfig } from "~/config/site" export const metadataConfig: Metadata = { openGraph: { url: "/", siteName: siteConfig.name, locale: "en_US", type: "website", images: { url: `${siteConfig.url}/opengraph.png`, width: 1200, height: 630 }, }, twitter: { site: "@dirstarter", creator: "@piotrkulpinski", card: "summary_large_image", }, alternates: { canonical: "/", types: { "application/rss+xml": linksConfig.feed }, }, } ``` ### Static Pages For static pages, you can define metadata directly in the page component: ```tsx title="app/(web)/about/page.tsx" export const metadata: Metadata = { title: "About Us", description: "Dirstarter is a complete Next.js solution for building profitable directory websites.", openGraph: { ...metadataConfig.openGraph, url: "/about" }, alternates: { ...metadataConfig.alternates, canonical: "/about" }, } ``` ### Dynamic Pages For dynamic pages (like tool pages, blog posts), use the `generateMetadata` function: ```tsx title="app/(web)/[slug]/page.tsx" export const generateMetadata = async (props: PageProps): Promise => { const tool = await getTool(props) const url = `/${tool.slug}` return { ...getMetadata(tool), alternates: { ...metadataConfig.alternates, canonical: url }, openGraph: { url, type: "website" }, } } ``` ## OpenGraph Images Dirstarter automatically generates OpenGraph images for tools using Next.js's Image Response API. The images are generated on-demand and optimized for social media sharing. ### Base Template The base OpenGraph image template is defined in `components/web/og/og-base.tsx`: ```tsx title="components/web/og/og-base.tsx" export const OgBase = ({ faviconUrl, name, description, children }: OgBaseProps) => { return (
{faviconUrl && ( )} {name}

{getExcerpt(description, 125)}

{config.site.name} — {config.site.tagline}
) } ``` ### Dynamic Generation OpenGraph images are generated dynamically for each tool using the `opengraph-image.tsx` route: ```tsx title="app/(web)/[slug]/opengraph-image.tsx" export default async function Image({ params }: PageProps) { const { slug } = await params const tool = await findTool({ where: { slug } }) return new ImageResponse( , { width: 1200, height: 630, fonts: [ { name: "Geist", data: await loadGoogleFont("Geist", 400), weight: 400, style: "normal", }, { name: "GeistBold", data: await loadGoogleFont("Geist", 600), weight: 600, style: "normal", }, ], } ) } ``` ## Sitemap Generation Dirstarter automatically generates a sitemap for all your pages using Next.js's built-in sitemap generation. The sitemap is configured in `app/sitemap.ts`: ```tsx title="app/sitemap.ts" export default async function Sitemap(): Promise { const [tools, categories, tags] = await Promise.all([ findToolSlugs({}), findCategorySlugs({}), findTagSlugs({}), ]) const pages = ["/about", "/advertise", "/submit"] const now = new Date() return [ // Home createEntry("", now, { changeFrequency: "daily", priority: 1 }), // Static pages ...pages.map(p => createEntry(p, now, { changeFrequency: "monthly" })), // Posts createEntry("/blog", now), ...posts.map(p => createEntry(`/blog/${p._meta.path}`, new Date(p.updatedAt ?? p.publishedAt))), // Tools ...tools.map(t => createEntry(`/${t.slug}`, t.updatedAt)), // Categories createEntry("/categories", now), ...categories.map(c => createEntry(`/categories/${c.slug}`, c.updatedAt)), // Tags createEntry("/tags", now), ...tags.map(t => createEntry(`/tags/${t.slug}`, t.updatedAt)), ] } ``` ## Robots.txt The robots.txt file is also automatically generated and configured to allow search engines to crawl your site while protecting sensitive routes: ```tsx title="app/robots.ts" export default function Robots(): MetadataRoute.Robots { const { url } = config.site return { rules: { userAgent: "*", allow: "/", disallow: ["/admin/*", "/auth/*"], }, host: url, sitemap: `${url}/sitemap.xml`, } } ``` ## Additional SEO Features 1. **Canonical URLs**: Every page has a canonical URL to prevent duplicate content issues. 2. **RSS Feed**: Built-in RSS feed support for blog posts and content updates. 3. **Structured Data**: Automatically generated JSON-LD for tools and blog posts. 4. **Mobile Optimization**: All pages are responsive and mobile-friendly by default. 5. **Performance**: Built-in image optimization and lazy loading for better Core Web Vitals. file: ./content/docs/theming.mdx meta: { "title": "Theming", "description": "Learn how to customize the appearance of your directory website using Tailwind CSS, Radix UI, and shadcn/ui components." } Dirstarter uses a modern theming system built on top of [Tailwind CSS](https://tailwindcss.com/docs/) v4, [Radix UI primitives](https://www.radix-ui.com), and [shadcn/ui](https://ui.shadcn.com) components. This guide will help you understand and customize the styles of your website. The combination of Tailwind CSS, Radix UI and shadcn/ui components allows ready-to-use, complex UI components that can be fully customized to match your brands design. ## Color System The color system is defined using CSS variables in the `@theme` configuration. The base theme includes: ```css title="app/styles.css" :root { --color-background: hsl(0 0% 100%); --color-foreground: hsl(0 0% 12%); --color-border: hsl(0 0% 88%); --color-input: hsl(0 0% 88%); --color-ring: hsl(0 0% 83%); --color-card: hsl(0 0% 98%); --color-card-foreground: hsl(0 0% 12%); --color-popover: hsl(0 0% 100%); --color-popover-foreground: hsl(0 0% 3.9%); --color-primary: hsl(234 98% 61%); --color-primary-foreground: hsl(0 0% 98%); --color-secondary: hsl(0 0% 96%); --color-secondary-foreground: hsl(0 0% 30%); --color-muted: hsl(0 0% 96%); --color-muted-foreground: hsl(0 0% 45.1%); --color-accent: hsl(0 0% 96%); --color-accent-foreground: hsl(0 0% 9%); --color-destructive: hsl(0 84.2% 60.2%); --color-destructive-foreground: hsl(0 0% 98%); } ``` ### Dark Mode Dark mode is automatically handled using the `prefers-color-scheme` media query. The dark theme colors are defined as: ```css title="app/styles.css" @media (prefers-color-scheme: dark) { :root { --color-background: hsl(0 0% 8%); --color-foreground: hsl(0 0% 90%); --color-border: hsl(0 0% 18%); --color-input: hsl(0 0% 18%); --color-ring: hsl(0 0% 23%); --color-card: hsl(0 0% 11%); --color-card-foreground: hsl(0 0% 90%); --color-popover: hsl(0 0% 8%); --color-popover-foreground: hsl(0 0% 90%); --color-primary: hsl(234 98% 61%); --color-primary-foreground: hsl(0 0% 98%); --color-secondary: hsl(0 0% 13%); --color-secondary-foreground: hsl(0 0% 70%); --color-muted: hsl(0 0% 13%); --color-muted-foreground: hsl(0 0% 60%); --color-accent: hsl(0 0% 13%); --color-accent-foreground: hsl(0 0% 90%); --color-destructive: hsl(0 62.8% 30.6%); --color-destructive-foreground: hsl(0 0% 98%); } } ``` ## Typography The typography system uses Geist font as the primary font family by default: ```css @theme { --font-system: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; --font-sans: var(--font-geist), var(--font-system); --font-display: var(--font-geist), var(--font-system); } ``` ## Component Variants Components use the [cva.style](https://beta.cva.style/) utility for variant management. This is a modern and powerful utility for defining and managing class variants. Here's an example of how variants are defined: ```tsx title="components/ui/button.tsx" const buttonVariants = cva({ base: "relative border text-sm/tight rounded-md", variants: { variant: { primary: "text-background bg-foreground hover:opacity-90", secondary: "bg-background text-secondary-foreground hover:bg-card", }, size: { sm: "px-2 py-1", md: "px-3 py-2", lg: "px-4 py-2.5", }, }, defaultVariants: { variant: "primary", size: "lg", }, }) ``` ## Customizing Components ### Using Radix UI Primitives Components are built on top of [Radix UI](https://www.radix-ui.com) primitives for accessibility and functionality. For example: ```tsx const Dialog = DialogPrimitive.Root const DialogTrigger = DialogPrimitive.Trigger const DialogPortal = DialogPrimitive.Portal const DialogClose = DialogPrimitive.Close ``` ### Styling with Tailwind Components use [Tailwind CSS](https://tailwindcss.com/docs) for styling. You can customize the appearance by: 1. Modifying the base styles in the component's `cva` configuration 2. Extending the component with additional classes using the `className` prop 3. Overriding the theme variables in your CSS ### Example: Custom Button ```tsx const CustomButton = ({ className, ...props }) => (