├── .autorc ├── .cursorrules.example ├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── SECURITY.md ├── copilot-instructions.md.example ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── build.yml │ └── release.yml ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── CHANGELOG.md ├── README.md ├── apps ├── api │ ├── .env.example │ ├── __tests__ │ │ └── health.test.ts │ ├── app │ │ ├── apple-icon.png │ │ ├── cron │ │ │ └── keep-alive │ │ │ │ └── route.ts │ │ ├── global-error.tsx │ │ ├── health │ │ │ └── route.ts │ │ ├── icon.png │ │ ├── layout.tsx │ │ ├── opengraph-image.png │ │ └── webhooks │ │ │ ├── auth │ │ │ └── route.ts │ │ │ └── payments │ │ │ └── route.ts │ ├── env.ts │ ├── instrumentation.ts │ ├── next.config.ts │ ├── package.json │ ├── scripts │ │ └── skip-ci.js │ ├── sentry.client.config.ts │ ├── tsconfig.json │ ├── vercel.json │ └── vitest.config.ts ├── app │ ├── .env.example │ ├── __tests__ │ │ ├── sign-in.test.tsx │ │ └── sign-up.test.tsx │ ├── app │ │ ├── (authenticated) │ │ │ ├── components │ │ │ │ ├── avatar-stack.tsx │ │ │ │ ├── collaboration-provider.tsx │ │ │ │ ├── cursors.tsx │ │ │ │ ├── header.tsx │ │ │ │ ├── posthog-identifier.tsx │ │ │ │ ├── search.tsx │ │ │ │ └── sidebar.tsx │ │ │ ├── layout.tsx │ │ │ ├── page.tsx │ │ │ ├── search │ │ │ │ └── page.tsx │ │ │ └── webhooks │ │ │ │ └── page.tsx │ │ ├── (unauthenticated) │ │ │ ├── layout.tsx │ │ │ ├── sign-in │ │ │ │ └── [[...sign-in]] │ │ │ │ │ └── page.tsx │ │ │ └── sign-up │ │ │ │ └── [[...sign-up]] │ │ │ │ └── page.tsx │ │ ├── .well-known │ │ │ └── vercel │ │ │ │ └── flags │ │ │ │ └── route.ts │ │ ├── actions │ │ │ └── users │ │ │ │ ├── get.ts │ │ │ │ └── search.ts │ │ ├── api │ │ │ └── collaboration │ │ │ │ └── auth │ │ │ │ └── route.ts │ │ ├── apple-icon.png │ │ ├── global-error.tsx │ │ ├── icon.png │ │ ├── layout.tsx │ │ ├── opengraph-image.png │ │ └── styles.css │ ├── env.ts │ ├── instrumentation.ts │ ├── liveblocks.config.ts │ ├── middleware.ts │ ├── next.config.ts │ ├── package.json │ ├── postcss.config.mjs │ ├── scripts │ │ └── skip-ci.js │ ├── sentry.client.config.ts │ ├── tsconfig.json │ ├── vercel.json │ └── vitest.config.ts ├── docs │ ├── api-reference │ │ ├── endpoint │ │ │ ├── create.mdx │ │ │ ├── delete.mdx │ │ │ └── get.mdx │ │ ├── introduction.mdx │ │ └── openapi.json │ ├── development.mdx │ ├── essentials │ │ ├── code.mdx │ │ ├── images.mdx │ │ ├── markdown.mdx │ │ ├── navigation.mdx │ │ ├── reusable-snippets.mdx │ │ └── settings.mdx │ ├── favicon.svg │ ├── images │ │ ├── checks-passed.png │ │ ├── hero-dark.svg │ │ └── hero-light.svg │ ├── introduction.mdx │ ├── logo │ │ ├── dark.svg │ │ └── light.svg │ ├── mint.json │ ├── package.json │ ├── quickstart.mdx │ └── snippets │ │ └── snippet-intro.mdx ├── email │ ├── package.json │ └── tsconfig.json ├── storybook │ ├── .storybook │ │ ├── main.ts │ │ ├── preview-head.html │ │ └── preview.tsx │ ├── README.md │ ├── next.config.ts │ ├── package.json │ ├── postcss.config.mjs │ ├── public │ │ └── favicon.ico │ ├── scripts │ │ └── skip-ci.js │ ├── stories │ │ ├── accordion.stories.tsx │ │ ├── alert-dialog.stories.tsx │ │ ├── alert.stories.tsx │ │ ├── aspect-ratio.stories.tsx │ │ ├── avatar.stories.tsx │ │ ├── badge.stories.tsx │ │ ├── breadcrumb.stories.tsx │ │ ├── button.stories.tsx │ │ ├── calendar.stories.tsx │ │ ├── card.stories.tsx │ │ ├── carousel.stories.tsx │ │ ├── chart.stories.tsx │ │ ├── checkbox.stories.tsx │ │ ├── collapsible.stories.tsx │ │ ├── command.stories.tsx │ │ ├── context-menu.stories.tsx │ │ ├── dialog.stories.tsx │ │ ├── drawer.stories.tsx │ │ ├── dropdown-menu.stories.tsx │ │ ├── form.stories.tsx │ │ ├── hover-card.stories.tsx │ │ ├── input-otp.stories.tsx │ │ ├── input.stories.tsx │ │ ├── label.stories.tsx │ │ ├── menubar.stories.tsx │ │ ├── navigation-menu.stories.tsx │ │ ├── pagination.stories.tsx │ │ ├── popover.stories.tsx │ │ ├── progress.stories.tsx │ │ ├── radio-group.stories.tsx │ │ ├── resizable.stories.tsx │ │ ├── scroll-area.stories.tsx │ │ ├── select.stories.tsx │ │ ├── separator.stories.tsx │ │ ├── sheet.stories.tsx │ │ ├── sidebar.stories.tsx │ │ ├── skeleton.stories.tsx │ │ ├── slider.stories.tsx │ │ ├── sonner.stories.tsx │ │ ├── switch.stories.tsx │ │ ├── table.stories.tsx │ │ ├── tabs.stories.tsx │ │ ├── textarea.stories.tsx │ │ ├── toggle-group.stories.tsx │ │ ├── toggle.stories.tsx │ │ └── tooltip.stories.tsx │ ├── tsconfig.json │ └── vercel.json ├── studio │ ├── package.json │ └── tsconfig.json └── web │ ├── .env.example │ ├── app │ ├── .well-known │ │ └── vercel │ │ │ └── flags │ │ │ └── route.ts │ └── [locale] │ │ ├── (home) │ │ ├── components │ │ │ ├── cases.tsx │ │ │ ├── cta.tsx │ │ │ ├── faq.tsx │ │ │ ├── features.tsx │ │ │ ├── hero.tsx │ │ │ ├── stats.tsx │ │ │ └── testimonials.tsx │ │ └── page.tsx │ │ ├── apple-icon.png │ │ ├── blog │ │ ├── [slug] │ │ │ └── page.tsx │ │ └── page.tsx │ │ ├── components │ │ ├── footer.tsx │ │ └── header │ │ │ ├── index.tsx │ │ │ ├── language-switcher.tsx │ │ │ └── logo.svg │ │ ├── contact │ │ ├── actions │ │ │ └── contact.tsx │ │ ├── components │ │ │ └── contact-form.tsx │ │ └── page.tsx │ │ ├── global-error.tsx │ │ ├── icon.png │ │ ├── layout.tsx │ │ ├── legal │ │ ├── [slug] │ │ │ └── page.tsx │ │ └── layout.tsx │ │ ├── opengraph-image.png │ │ ├── pricing │ │ └── page.tsx │ │ ├── robots.ts │ │ ├── sitemap.ts │ │ └── styles.css │ ├── components │ └── sidebar.tsx │ ├── env.ts │ ├── instrumentation.ts │ ├── middleware.ts │ ├── next.config.ts │ ├── package.json │ ├── postcss.config.mjs │ ├── scripts │ └── skip-ci.js │ ├── tsconfig.json │ └── vercel.json ├── biome.json ├── docs ├── .gitignore ├── README.md ├── app │ ├── [[...slug]] │ │ ├── (home) │ │ │ ├── components │ │ │ │ ├── apps │ │ │ │ │ ├── api.png │ │ │ │ │ ├── app.png │ │ │ │ │ ├── docs.png │ │ │ │ │ ├── email.png │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── storybook.png │ │ │ │ │ ├── studio.png │ │ │ │ │ └── web.png │ │ │ │ ├── cta.tsx │ │ │ │ ├── demo.tsx │ │ │ │ ├── features │ │ │ │ │ ├── arcjet.svg │ │ │ │ │ ├── basehub.svg │ │ │ │ │ ├── better-stack.svg │ │ │ │ │ ├── clerk.svg │ │ │ │ │ ├── cmdk.svg │ │ │ │ │ ├── google-analytics.svg │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── liveblocks.svg │ │ │ │ │ ├── lucide.svg │ │ │ │ │ ├── neon.svg │ │ │ │ │ ├── posthog.svg │ │ │ │ │ ├── prisma.svg │ │ │ │ │ ├── radix.svg │ │ │ │ │ ├── react-email.svg │ │ │ │ │ ├── react.svg │ │ │ │ │ ├── recharts.svg │ │ │ │ │ ├── resend.svg │ │ │ │ │ ├── sentry.svg │ │ │ │ │ ├── stripe.svg │ │ │ │ │ ├── svix.svg │ │ │ │ │ ├── tailwind.svg │ │ │ │ │ ├── typescript.svg │ │ │ │ │ ├── ultracite.svg │ │ │ │ │ ├── vercel.svg │ │ │ │ │ └── zod.svg │ │ │ │ ├── footer.tsx │ │ │ │ ├── hero.tsx │ │ │ │ ├── installer.tsx │ │ │ │ ├── open-source.tsx │ │ │ │ ├── review │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── rauchg.jpg │ │ │ │ │ └── vercel.jpg │ │ │ │ ├── social.tsx │ │ │ │ └── video.tsx │ │ │ └── index.tsx │ │ └── page.tsx │ ├── api │ │ └── search │ │ │ └── route.ts │ ├── apple-icon.png │ ├── global.css │ ├── icon.png │ ├── layout.config.tsx │ ├── layout.tsx │ ├── opengraph-image.png │ └── providers │ │ └── theme.tsx ├── components.json ├── components │ ├── authors.tsx │ ├── mermaid.tsx │ ├── ui │ │ ├── accordion.tsx │ │ ├── alert-dialog.tsx │ │ ├── alert.tsx │ │ ├── aspect-ratio.tsx │ │ ├── avatar.tsx │ │ ├── badge.tsx │ │ ├── breadcrumb.tsx │ │ ├── button.tsx │ │ ├── calendar.tsx │ │ ├── card.tsx │ │ ├── carousel.tsx │ │ ├── chart.tsx │ │ ├── checkbox.tsx │ │ ├── collapsible.tsx │ │ ├── command.tsx │ │ ├── context-menu.tsx │ │ ├── dialog.tsx │ │ ├── drawer.tsx │ │ ├── dropdown-menu.tsx │ │ ├── form.tsx │ │ ├── hover-card.tsx │ │ ├── input-otp.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── menubar.tsx │ │ ├── navigation-menu.tsx │ │ ├── pagination.tsx │ │ ├── popover.tsx │ │ ├── progress.tsx │ │ ├── radio-group.tsx │ │ ├── resizable.tsx │ │ ├── scroll-area.tsx │ │ ├── select.tsx │ │ ├── separator.tsx │ │ ├── sheet.tsx │ │ ├── sidebar.tsx │ │ ├── skeleton.tsx │ │ ├── slider.tsx │ │ ├── sonner.tsx │ │ ├── switch.tsx │ │ ├── table.tsx │ │ ├── tabs.tsx │ │ ├── textarea.tsx │ │ ├── toggle-group.tsx │ │ ├── toggle.tsx │ │ └── tooltip.tsx │ └── vercel.tsx ├── content │ └── docs │ │ ├── addons │ │ ├── dub.mdx │ │ ├── friendlier-words.mdx │ │ ├── fuse.mdx │ │ ├── meta.json │ │ ├── metabase.mdx │ │ ├── motion.mdx │ │ ├── next-safe-action.mdx │ │ ├── nuqs.mdx │ │ ├── react-wrap-balancer.mdx │ │ └── zustand.mdx │ │ ├── apps │ │ ├── api.mdx │ │ ├── app.mdx │ │ ├── docs.mdx │ │ ├── email.mdx │ │ ├── meta.json │ │ ├── storybook.mdx │ │ ├── studio.mdx │ │ └── web.mdx │ │ ├── docs │ │ ├── deployment │ │ │ ├── netlify.mdx │ │ │ └── vercel.mdx │ │ ├── examples │ │ │ └── ai-chatbot.mdx │ │ ├── faq.mdx │ │ ├── index.mdx │ │ ├── meta.json │ │ ├── philosophy.mdx │ │ ├── setup │ │ │ ├── env.mdx │ │ │ ├── installation.mdx │ │ │ └── prerequisites.mdx │ │ ├── structure.mdx │ │ └── updates.mdx │ │ ├── migrations │ │ ├── authentication │ │ │ ├── authjs.mdx │ │ │ └── better-auth.mdx │ │ ├── cms │ │ │ └── content-collections.mdx │ │ ├── database │ │ │ ├── drizzle.mdx │ │ │ ├── edgedb.mdx │ │ │ ├── planetscale.mdx │ │ │ ├── prisma-postgres.mdx │ │ │ ├── supabase.mdx │ │ │ └── turso.mdx │ │ ├── documentation │ │ │ └── fumadocs.mdx │ │ ├── flags │ │ │ └── hypertune.mdx │ │ ├── formatting │ │ │ └── eslint.mdx │ │ ├── meta.json │ │ ├── payments │ │ │ ├── lemon-squeezy.mdx │ │ │ └── paddle.mdx │ │ └── storage │ │ │ └── upload-thing.mdx │ │ └── packages │ │ ├── ai.mdx │ │ ├── analytics │ │ ├── product.mdx │ │ └── web.mdx │ │ ├── authentication.mdx │ │ ├── cms │ │ ├── components.mdx │ │ ├── meta.json │ │ ├── metadata.mdx │ │ └── overview.mdx │ │ ├── collaboration.mdx │ │ ├── cron.mdx │ │ ├── database.mdx │ │ ├── design-system │ │ ├── colors.mdx │ │ ├── components.mdx │ │ ├── dark-mode.mdx │ │ ├── meta.json │ │ ├── provider.mdx │ │ └── typography.mdx │ │ ├── email.mdx │ │ ├── flags.mdx │ │ ├── formatting.mdx │ │ ├── internationalization.mdx │ │ ├── meta.json │ │ ├── next-config │ │ ├── bundle-analysis.mdx │ │ ├── meta.json │ │ └── overview.mdx │ │ ├── notifications.mdx │ │ ├── observability │ │ ├── debugging.mdx │ │ ├── error-capture.mdx │ │ ├── logging.mdx │ │ └── uptime.mdx │ │ ├── payments.mdx │ │ ├── security │ │ ├── application.mdx │ │ ├── dependencies.mdx │ │ ├── headers.mdx │ │ ├── ip-geolocation.mdx │ │ └── rate-limiting.mdx │ │ ├── seo │ │ ├── json-ld.mdx │ │ ├── meta.json │ │ ├── metadata.mdx │ │ └── sitemap.mdx │ │ ├── storage.mdx │ │ ├── testing.mdx │ │ ├── toolbar.mdx │ │ └── webhooks │ │ ├── inbound.mdx │ │ └── outbound.mdx ├── hooks │ └── use-mobile.ts ├── lib │ ├── source.ts │ └── utils.ts ├── mdx-components.tsx ├── next.config.mjs ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── postcss.config.mjs ├── public │ └── images │ │ ├── ai-chatbot.png │ │ ├── ai.png │ │ ├── app.png │ │ ├── authentication.png │ │ ├── authors │ │ ├── arcjet │ │ │ ├── davidmytton.jpg │ │ │ └── logo.jpg │ │ ├── basehub │ │ │ ├── julianbenegas8.jpg │ │ │ └── logo.jpg │ │ ├── better-auth │ │ │ ├── imbereket.jpg │ │ │ └── logo.jpg │ │ ├── drizzle │ │ │ ├── alexblokh.jpg │ │ │ └── logo.jpg │ │ ├── dub │ │ │ ├── logo.jpg │ │ │ └── steventey.jpg │ │ ├── edgedb │ │ │ ├── beerose.jpg │ │ │ └── logo.jpg │ │ ├── eslint │ │ │ ├── logo.jpg │ │ │ └── slicknet.jpg │ │ ├── fumadocs │ │ │ ├── fuma-nama.jpg │ │ │ └── logo.jpg │ │ ├── hypertune │ │ │ ├── logo.jpg │ │ │ ├── michalbock.jpg │ │ │ └── miraantabrez.jpg │ │ ├── knock │ │ │ ├── j_everhart383.jpg │ │ │ └── logo.jpg │ │ ├── languine │ │ │ ├── logo.jpg │ │ │ └── pontusab.jpg │ │ ├── liveblocks │ │ │ ├── ctnicholas.jpg │ │ │ └── logo.jpg │ │ ├── metabase │ │ │ ├── heffhq.jpg │ │ │ └── logo.jpg │ │ ├── mintlify │ │ │ ├── fmerian.jpg │ │ │ └── logo.jpg │ │ ├── paddle │ │ │ ├── danbillson.jpg │ │ │ ├── heymcgovern.jpg │ │ │ └── logo.jpg │ │ ├── prisma │ │ │ ├── logo.jpg │ │ │ └── nikolasburk.jpg │ │ ├── turso │ │ │ ├── logo.jpg │ │ │ └── notrab.jpg │ │ ├── upstash │ │ │ ├── fahreddin.jpg │ │ │ └── logo.jpg │ │ └── vercel │ │ │ ├── anthonysheww.jpg │ │ │ ├── haydenbleasel.jpg │ │ │ └── logo.jpg │ │ ├── collaboration.png │ │ ├── database.png │ │ ├── deployment │ │ ├── netlify.png │ │ └── vercel.png │ │ ├── docs.png │ │ ├── dub-create.png │ │ ├── dub-register.png │ │ ├── email.png │ │ ├── icon.png │ │ ├── internationalization.png │ │ ├── language-switcher.png │ │ ├── metabase-add-database.png │ │ ├── opengraph-image.png │ │ ├── rate-limit.png │ │ ├── sign-in.png │ │ ├── storybook.png │ │ ├── structure.png │ │ ├── testing.png │ │ ├── upstash-ratelimit-dashboard.png │ │ ├── upstash-ratelimit-navbar.png │ │ ├── vercel-toolbar.png │ │ ├── web-analytics.png │ │ └── web.png ├── scripts │ ├── skip-ci.js │ └── validate-links.ts ├── source.config.ts ├── tsconfig.json └── vercel.json ├── license.md ├── package.json ├── packages ├── ai │ ├── components │ │ ├── message.tsx │ │ └── thread.tsx │ ├── index.ts │ ├── keys.ts │ ├── lib │ │ ├── models.ts │ │ └── react.ts │ ├── package.json │ └── tsconfig.json ├── analytics │ ├── google.ts │ ├── index.tsx │ ├── keys.ts │ ├── package.json │ ├── posthog │ │ ├── client.tsx │ │ └── server.ts │ ├── tsconfig.json │ └── vercel.ts ├── auth │ ├── client.ts │ ├── components │ │ ├── sign-in.tsx │ │ └── sign-up.tsx │ ├── keys.ts │ ├── middleware.ts │ ├── package.json │ ├── provider.tsx │ ├── server.ts │ └── tsconfig.json ├── cms │ ├── .env.example │ ├── components │ │ ├── body.tsx │ │ ├── code-block.tsx │ │ ├── feed.tsx │ │ ├── image.tsx │ │ ├── toc.tsx │ │ └── toolbar.tsx │ ├── index.ts │ ├── keys.ts │ ├── next-config.ts │ ├── package.json │ ├── tsconfig.json │ └── typescript-config.json ├── collaboration │ ├── auth.ts │ ├── config.ts │ ├── hooks.ts │ ├── keys.ts │ ├── package.json │ ├── room.tsx │ └── tsconfig.json ├── database │ ├── .env.example │ ├── index.ts │ ├── keys.ts │ ├── package.json │ ├── prisma │ │ └── schema.prisma │ └── tsconfig.json ├── design-system │ ├── components.json │ ├── components │ │ ├── mode-toggle.tsx │ │ └── ui │ │ │ ├── accordion.tsx │ │ │ ├── alert-dialog.tsx │ │ │ ├── alert.tsx │ │ │ ├── aspect-ratio.tsx │ │ │ ├── avatar.tsx │ │ │ ├── badge.tsx │ │ │ ├── breadcrumb.tsx │ │ │ ├── button.tsx │ │ │ ├── calendar.tsx │ │ │ ├── card.tsx │ │ │ ├── carousel.tsx │ │ │ ├── chart.tsx │ │ │ ├── checkbox.tsx │ │ │ ├── collapsible.tsx │ │ │ ├── command.tsx │ │ │ ├── context-menu.tsx │ │ │ ├── dialog.tsx │ │ │ ├── drawer.tsx │ │ │ ├── dropdown-menu.tsx │ │ │ ├── form.tsx │ │ │ ├── hover-card.tsx │ │ │ ├── input-otp.tsx │ │ │ ├── input.tsx │ │ │ ├── label.tsx │ │ │ ├── menubar.tsx │ │ │ ├── navigation-menu.tsx │ │ │ ├── pagination.tsx │ │ │ ├── popover.tsx │ │ │ ├── progress.tsx │ │ │ ├── radio-group.tsx │ │ │ ├── resizable.tsx │ │ │ ├── scroll-area.tsx │ │ │ ├── select.tsx │ │ │ ├── separator.tsx │ │ │ ├── sheet.tsx │ │ │ ├── sidebar.tsx │ │ │ ├── skeleton.tsx │ │ │ ├── slider.tsx │ │ │ ├── sonner.tsx │ │ │ ├── switch.tsx │ │ │ ├── table.tsx │ │ │ ├── tabs.tsx │ │ │ ├── textarea.tsx │ │ │ ├── toggle-group.tsx │ │ │ ├── toggle.tsx │ │ │ └── tooltip.tsx │ ├── hooks │ │ └── use-mobile.ts │ ├── index.tsx │ ├── lib │ │ ├── fonts.ts │ │ └── utils.ts │ ├── package.json │ ├── postcss.config.mjs │ ├── providers │ │ └── theme.tsx │ ├── styles │ │ └── globals.css │ └── tsconfig.json ├── email │ ├── index.ts │ ├── keys.ts │ ├── package.json │ ├── templates │ │ └── contact.tsx │ └── tsconfig.json ├── feature-flags │ ├── access.ts │ ├── components │ │ └── toolbar.tsx │ ├── index.ts │ ├── keys.ts │ ├── lib │ │ ├── create-flag.ts │ │ └── toolbar.ts │ ├── package.json │ └── tsconfig.json ├── internationalization │ ├── .env.example │ ├── dictionaries │ │ ├── de.json │ │ ├── en.json │ │ ├── es.json │ │ ├── fr.json │ │ ├── pt.json │ │ └── zh.json │ ├── index.ts │ ├── languine.json │ ├── languine.lock │ ├── middleware.ts │ ├── package.json │ └── tsconfig.json ├── next-config │ ├── index.ts │ ├── keys.ts │ ├── package.json │ └── tsconfig.json ├── notifications │ ├── components │ │ ├── provider.tsx │ │ └── trigger.tsx │ ├── index.ts │ ├── keys.ts │ ├── package.json │ ├── styles.css │ └── tsconfig.json ├── observability │ ├── client.ts │ ├── error.ts │ ├── instrumentation.ts │ ├── keys.ts │ ├── log.ts │ ├── next-config.ts │ ├── package.json │ ├── status │ │ ├── index.tsx │ │ └── types.ts │ └── tsconfig.json ├── payments │ ├── ai.ts │ ├── index.ts │ ├── keys.ts │ ├── package.json │ └── tsconfig.json ├── rate-limit │ ├── index.ts │ ├── keys.ts │ └── package.json ├── security │ ├── index.ts │ ├── keys.ts │ ├── middleware.ts │ ├── package.json │ └── tsconfig.json ├── seo │ ├── json-ld.tsx │ ├── metadata.ts │ ├── package.json │ └── tsconfig.json ├── storage │ ├── client.ts │ ├── index.ts │ ├── keys.ts │ ├── package.json │ └── tsconfig.json ├── testing │ ├── index.js │ ├── package.json │ └── tsconfig.json ├── typescript-config │ ├── base.json │ ├── nextjs.json │ ├── package.json │ └── react-library.json └── webhooks │ ├── index.ts │ ├── keys.ts │ ├── lib │ └── svix.ts │ ├── package.json │ └── tsconfig.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── scripts ├── index.ts ├── initialize.ts ├── update.ts └── utils.ts ├── tsconfig.json ├── tsup.config.ts ├── turbo.json └── turbo └── generators ├── config.ts ├── package.json └── templates ├── package.json.hbs └── tsconfig.json.hbs /.autorc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["npm", "first-time-contributor", "released"], 3 | "owner": "vercel", 4 | "repo": "next-forge", 5 | "name": "Hayden Bleasel", 6 | "email": "hello@haydenbleasel.com" 7 | } 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **next-forge version** 14 | I am using version ... 15 | 16 | **To Reproduce** 17 | Steps to reproduce the behavior: 18 | 1. Go to '...' 19 | 2. Click on '....' 20 | 3. Scroll down to '....' 21 | 4. See error 22 | 23 | **Expected behavior** 24 | A clear and concise description of what you expected to happen. 25 | 26 | **Screenshots** 27 | If applicable, add screenshots to help explain your problem. 28 | 29 | **Desktop (please complete the following information):** 30 | - OS: [e.g. MacOS] 31 | - Browser [e.g. chrome v130, safari] 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Currently, only the latest on `main` branch is supported with security updates. 6 | 7 | ## Reporting a Vulnerability 8 | 9 | To report a vulnerability, open a new issue. 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | 4 | # Maintain dependencies for GitHub Actions 5 | - package-ecosystem: "github-actions" 6 | directory: "/" 7 | open-pull-requests-limit: 10 8 | schedule: 9 | interval: "monthly" 10 | 11 | # Maintain dependencies for npm 12 | - package-ecosystem: "npm" 13 | directory: "/" 14 | open-pull-requests-limit: 10 15 | schedule: 16 | interval: "monthly" 17 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | Please provide a brief description of the changes introduced in this pull request. 4 | 5 | ## Related Issues 6 | 7 | Closes # 8 | 9 | ## Checklist 10 | 11 | - [ ] My code follows the code style of this project. 12 | - [ ] I have performed a self-review of my code. 13 | - [ ] I have commented my code, particularly in hard-to-understand areas. 14 | - [ ] I have updated the documentation, if necessary. 15 | - [ ] I have added tests that prove my fix is effective or my feature works. 16 | - [ ] New and existing tests pass locally with my changes. 17 | 18 | ## Screenshots (if applicable) 19 | 20 | 21 | 22 | ## Additional Notes 23 | 24 | 25 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "biomejs.biome", 4 | "bradlc.vscode-tailwindcss", 5 | "Prisma.prisma", 6 | "unifiedjs.vscode-mdx", 7 | "mikestead.dotenv", 8 | "christian-kohler.npm-intellisense" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[javascript]": { 3 | "editor.defaultFormatter": "biomejs.biome" 4 | }, 5 | "[json]": { 6 | "editor.defaultFormatter": "biomejs.biome" 7 | }, 8 | "[jsonc]": { 9 | "editor.defaultFormatter": "biomejs.biome" 10 | }, 11 | "[typescript]": { 12 | "editor.defaultFormatter": "biomejs.biome" 13 | }, 14 | "[typescriptreact]": { 15 | "editor.defaultFormatter": "biomejs.biome" 16 | }, 17 | "editor.codeActionsOnSave": { 18 | "quickfix.biome": "explicit", 19 | "source.organizeImports.biome": "explicit" 20 | }, 21 | "editor.defaultFormatter": "biomejs.biome", 22 | "editor.formatOnPaste": true, 23 | "editor.formatOnSave": true, 24 | "emmet.showExpandedAbbreviation": "never", 25 | "prettier.enable": false, 26 | "typescript.tsdk": "node_modules/typescript/lib", 27 | "tailwindCSS.experimental.configFile": "packages/design-system/styles/globals.css" 28 | } 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ▲ / next-forge 2 | 3 | **Production-grade Turborepo template for Next.js apps.** 4 | 5 |
6 | 7 | 8 | 9 |
10 | 11 | ## Overview 12 | 13 | [next-forge](https://github.com/vercel/next-forge) is a [Next.js](https://nextjs.org/) project boilerplate for modern web application. It is designed to be a comprehensive starting point for new apps, providing a solid, opinionated foundation with a minimal amount of configuration. 14 | 15 | ## Getting Started 16 | 17 | Clone the repo using: 18 | 19 | ```sh 20 | npx next-forge@latest init 21 | ``` 22 | 23 | Then read the [docs](https://www.next-forge.com/docs) for more information. 24 | 25 | ## Contributors 26 | 27 | 28 | 29 | 30 | 31 | Made with [contrib.rocks](https://contrib.rocks). 32 | -------------------------------------------------------------------------------- /apps/api/.env.example: -------------------------------------------------------------------------------- 1 | # Server 2 | CLERK_SECRET_KEY="" 3 | CLERK_WEBHOOK_SECRET="" 4 | RESEND_FROM="" 5 | DATABASE_URL="" 6 | RESEND_TOKEN="" 7 | STRIPE_SECRET_KEY="" 8 | STRIPE_WEBHOOK_SECRET="" 9 | BETTERSTACK_API_KEY="" 10 | BETTERSTACK_URL="" 11 | FLAGS_SECRET="" 12 | ARCJET_KEY="" 13 | SVIX_TOKEN="" 14 | LIVEBLOCKS_SECRET="" 15 | BASEHUB_TOKEN="" 16 | VERCEL_PROJECT_PRODUCTION_URL="http://localhost:3002" 17 | KNOCK_API_KEY="" 18 | KNOCK_FEED_CHANNEL_ID="" 19 | 20 | # Client 21 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="" 22 | NEXT_PUBLIC_CLERK_SIGN_IN_URL="/sign-in" 23 | NEXT_PUBLIC_CLERK_SIGN_UP_URL="/sign-up" 24 | NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL="/" 25 | NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL="/" 26 | NEXT_PUBLIC_GA_MEASUREMENT_ID="" 27 | NEXT_PUBLIC_POSTHOG_KEY="" 28 | NEXT_PUBLIC_POSTHOG_HOST="" 29 | NEXT_PUBLIC_DOCS_URL="http://localhost:3004" 30 | NEXT_PUBLIC_APP_URL="http://localhost:3000" 31 | NEXT_PUBLIC_WEB_URL="http://localhost:3001" -------------------------------------------------------------------------------- /apps/api/__tests__/health.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from 'vitest'; 2 | import { GET } from '../app/health/route'; 3 | 4 | test('Health Check', async () => { 5 | const response = await GET(); 6 | expect(response.status).toBe(200); 7 | expect(await response.text()).toBe('OK'); 8 | }); 9 | -------------------------------------------------------------------------------- /apps/api/app/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vercel/next-forge/00c42ee4a6d967865e8d5e50c485edd295a98f45/apps/api/app/apple-icon.png -------------------------------------------------------------------------------- /apps/api/app/cron/keep-alive/route.ts: -------------------------------------------------------------------------------- 1 | import { database } from '@repo/database'; 2 | 3 | export const GET = async () => { 4 | const newPage = await database.page.create({ 5 | data: { 6 | name: 'cron-temp', 7 | }, 8 | }); 9 | 10 | await database.page.delete({ 11 | where: { 12 | id: newPage.id, 13 | }, 14 | }); 15 | 16 | return new Response('OK', { status: 200 }); 17 | }; 18 | -------------------------------------------------------------------------------- /apps/api/app/global-error.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { Button } from '@repo/design-system/components/ui/button'; 4 | import { fonts } from '@repo/design-system/lib/fonts'; 5 | import { captureException } from '@sentry/nextjs'; 6 | import type NextError from 'next/error'; 7 | import { useEffect } from 'react'; 8 | 9 | type GlobalErrorProperties = { 10 | readonly error: NextError & { digest?: string }; 11 | readonly reset: () => void; 12 | }; 13 | 14 | const GlobalError = ({ error, reset }: GlobalErrorProperties) => { 15 | useEffect(() => { 16 | captureException(error); 17 | }, [error]); 18 | 19 | return ( 20 | 21 | 22 |

Oops, something went wrong

23 | 24 | 25 | 26 | ); 27 | }; 28 | 29 | export default GlobalError; 30 | -------------------------------------------------------------------------------- /apps/api/app/health/route.ts: -------------------------------------------------------------------------------- 1 | export const runtime = 'edge'; 2 | 3 | export const GET = (): Response => new Response('OK', { status: 200 }); 4 | -------------------------------------------------------------------------------- /apps/api/app/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vercel/next-forge/00c42ee4a6d967865e8d5e50c485edd295a98f45/apps/api/app/icon.png -------------------------------------------------------------------------------- /apps/api/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { ReactNode } from 'react'; 2 | 3 | type RootLayoutProperties = { 4 | readonly children: ReactNode; 5 | }; 6 | 7 | const RootLayout = ({ children }: RootLayoutProperties) => ( 8 | 9 | {children} 10 | 11 | ); 12 | 13 | export default RootLayout; 14 | -------------------------------------------------------------------------------- /apps/api/app/opengraph-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vercel/next-forge/00c42ee4a6d967865e8d5e50c485edd295a98f45/apps/api/app/opengraph-image.png -------------------------------------------------------------------------------- /apps/api/env.ts: -------------------------------------------------------------------------------- 1 | import { keys as analytics } from '@repo/analytics/keys'; 2 | import { keys as auth } from '@repo/auth/keys'; 3 | import { keys as database } from '@repo/database/keys'; 4 | import { keys as email } from '@repo/email/keys'; 5 | import { keys as core } from '@repo/next-config/keys'; 6 | import { keys as observability } from '@repo/observability/keys'; 7 | import { keys as payments } from '@repo/payments/keys'; 8 | import { createEnv } from '@t3-oss/env-nextjs'; 9 | 10 | export const env = createEnv({ 11 | extends: [ 12 | auth(), 13 | analytics(), 14 | core(), 15 | database(), 16 | email(), 17 | observability(), 18 | payments(), 19 | ], 20 | server: {}, 21 | client: {}, 22 | runtimeEnv: {}, 23 | }); 24 | -------------------------------------------------------------------------------- /apps/api/instrumentation.ts: -------------------------------------------------------------------------------- 1 | import { initializeSentry } from '@repo/observability/instrumentation'; 2 | 3 | export const register = initializeSentry(); 4 | -------------------------------------------------------------------------------- /apps/api/next.config.ts: -------------------------------------------------------------------------------- 1 | import { env } from '@/env'; 2 | import { config, withAnalyzer } from '@repo/next-config'; 3 | import { withLogging, withSentry } from '@repo/observability/next-config'; 4 | import type { NextConfig } from 'next'; 5 | 6 | let nextConfig: NextConfig = withLogging(config); 7 | 8 | if (env.VERCEL) { 9 | nextConfig = withSentry(nextConfig); 10 | } 11 | 12 | if (env.ANALYZE === 'true') { 13 | nextConfig = withAnalyzer(nextConfig); 14 | } 15 | 16 | export default nextConfig; 17 | -------------------------------------------------------------------------------- /apps/api/scripts/skip-ci.js: -------------------------------------------------------------------------------- 1 | const { execSync } = require('child_process'); 2 | 3 | const commitMessage = execSync('git log -1 --pretty=%B').toString().trim(); 4 | 5 | if (commitMessage.includes('[skip ci]')) { 6 | console.log('Skipping build due to [skip ci] in commit message.'); 7 | process.exit(0); // this causes Vercel to skip the build 8 | } 9 | 10 | process.exit(1); // continue with build 11 | -------------------------------------------------------------------------------- /apps/api/sentry.client.config.ts: -------------------------------------------------------------------------------- 1 | import { initializeSentry } from '@repo/observability/client'; 2 | 3 | initializeSentry(); 4 | -------------------------------------------------------------------------------- /apps/api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@repo/typescript-config/nextjs.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "paths": { 6 | "@/*": ["./*"], 7 | "@repo/*": ["../../packages/*"] 8 | } 9 | }, 10 | "include": [ 11 | "next-env.d.ts", 12 | "next.config.ts", 13 | "**/*.ts", 14 | "**/*.tsx", 15 | ".next/types/**/*.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /apps/api/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://openapi.vercel.sh/vercel.json", 3 | "ignoreCommand": "node scripts/skip-ci.js", 4 | "crons": [ 5 | { 6 | "path": "/cron/keep-alive", 7 | "schedule": "0 1 * * *" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /apps/api/vitest.config.ts: -------------------------------------------------------------------------------- 1 | export { default } from '@repo/testing'; 2 | -------------------------------------------------------------------------------- /apps/app/.env.example: -------------------------------------------------------------------------------- 1 | # Server 2 | CLERK_SECRET_KEY="" 3 | CLERK_WEBHOOK_SECRET="" 4 | RESEND_FROM="" 5 | DATABASE_URL="" 6 | RESEND_TOKEN="" 7 | STRIPE_SECRET_KEY="" 8 | STRIPE_WEBHOOK_SECRET="" 9 | BETTERSTACK_API_KEY="" 10 | BETTERSTACK_URL="" 11 | FLAGS_SECRET="" 12 | ARCJET_KEY="" 13 | SVIX_TOKEN="" 14 | LIVEBLOCKS_SECRET="" 15 | BASEHUB_TOKEN="" 16 | VERCEL_PROJECT_PRODUCTION_URL="http://localhost:3000" 17 | KNOCK_API_KEY="" 18 | KNOCK_FEED_CHANNEL_ID="" 19 | KNOCK_SECRET_API_KEY="" 20 | 21 | # Client 22 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="" 23 | NEXT_PUBLIC_CLERK_SIGN_IN_URL="/sign-in" 24 | NEXT_PUBLIC_CLERK_SIGN_UP_URL="/sign-up" 25 | NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL="/" 26 | NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL="/" 27 | NEXT_PUBLIC_GA_MEASUREMENT_ID="" 28 | NEXT_PUBLIC_KNOCK_API_KEY="" 29 | NEXT_PUBLIC_KNOCK_FEED_CHANNEL_ID="" 30 | NEXT_PUBLIC_POSTHOG_KEY="" 31 | NEXT_PUBLIC_POSTHOG_HOST="" 32 | NEXT_PUBLIC_APP_URL="http://localhost:3000" 33 | NEXT_PUBLIC_WEB_URL="http://localhost:3001" 34 | NEXT_PUBLIC_DOCS_URL="http://localhost:3004" 35 | -------------------------------------------------------------------------------- /apps/app/__tests__/sign-in.test.tsx: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import { expect, test } from 'vitest'; 3 | import Page from '../app/(unauthenticated)/sign-in/[[...sign-in]]/page'; 4 | 5 | test('Sign In Page', () => { 6 | render(); 7 | expect( 8 | screen.getByRole('heading', { 9 | level: 1, 10 | name: 'Welcome back', 11 | }) 12 | ).toBeDefined(); 13 | }); 14 | -------------------------------------------------------------------------------- /apps/app/__tests__/sign-up.test.tsx: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import { expect, test } from 'vitest'; 3 | import Page from '../app/(unauthenticated)/sign-up/[[...sign-up]]/page'; 4 | 5 | test('Sign Up Page', () => { 6 | render(); 7 | expect( 8 | screen.getByRole('heading', { 9 | level: 1, 10 | name: 'Create an account', 11 | }) 12 | ).toBeDefined(); 13 | }); 14 | -------------------------------------------------------------------------------- /apps/app/app/(authenticated)/components/search.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@repo/design-system/components/ui/button'; 2 | import { Input } from '@repo/design-system/components/ui/input'; 3 | import { ArrowRightIcon, SearchIcon } from 'lucide-react'; 4 | 5 | export const Search = () => ( 6 |
7 |
8 |
9 | 10 |
11 | 17 | 24 |
25 |
26 | ); 27 | -------------------------------------------------------------------------------- /apps/app/app/(authenticated)/webhooks/page.tsx: -------------------------------------------------------------------------------- 1 | import { webhooks } from '@repo/webhooks'; 2 | import { notFound } from 'next/navigation'; 3 | 4 | export const metadata = { 5 | title: 'Webhooks', 6 | description: 'Send webhooks to your users.', 7 | }; 8 | 9 | const WebhooksPage = async () => { 10 | const response = await webhooks.getAppPortal(); 11 | 12 | if (!response?.url) { 13 | notFound(); 14 | } 15 | 16 | return ( 17 |
18 |