Jumpstart your enterprise project with our feature-packed, high-performance Next.js boilerplate!
Experience rapid UI development, AI-powered code reviews, and an extensive suite of tools for a smooth and
enjoyable development process.
{LP_GRID_ITEMS.map((singleItem) => (
{singleItem.icon}
{singleItem.title}
{singleItem.description}
))}
>
)
}
================================================
FILE: components/Button/Button.stories.tsx
================================================
import type { Meta, StoryObj } from "@storybook/react"
import { Button } from "./Button"
const meta: Meta = {
title: "Button",
component: Button,
args: {
intent: "primary",
underline: false,
children: "Button",
size: "lg",
},
argTypes: {
intent: {
options: ["primary", "secondary"],
control: { type: "select" },
},
size: {
options: ["sm", "lg"],
control: { type: "select" },
},
},
}
type Story = StoryObj
export const Default: Story = {
render: (args) => ,
}
export default meta
================================================
FILE: components/Button/Button.test.tsx
================================================
import { render, screen } from "@testing-library/react"
import { describe, expect, it } from "vitest"
import { Button } from "./Button"
describe("Button", () => {
it("renders with children", () => {
render()
expect(screen.getByText("Click me")).toBeInTheDocument()
})
it("applies correct intent classes", () => {
const { container } = render(
)
const link = container.querySelector("a")
expect(link).toHaveClass("bg-transparent")
expect(link).toHaveClass("text-blue-400")
})
it("applies correct size classes", () => {
const { container } = render(
)
const link = container.querySelector("a")
expect(link).toHaveClass("text-sm")
expect(link).toHaveClass("min-w-20")
})
})
================================================
FILE: components/Button/Button.tsx
================================================
import { cva, type VariantProps } from "class-variance-authority"
import { twMerge } from "tailwind-merge"
const button = cva(
[
"justify-center",
"inline-flex",
"items-center",
"rounded-xl",
"text-center",
"border",
"border-blue-400",
"transition-colors",
"delay-50",
],
{
variants: {
intent: {
primary: ["bg-blue-400", "text-white", "hover:enabled:bg-blue-700"],
secondary: ["bg-transparent", "text-blue-400", "hover:enabled:bg-blue-400", "hover:enabled:text-white"],
},
size: {
sm: ["min-w-20", "h-full", "min-h-10", "text-sm", "py-1.5", "px-4"],
lg: ["min-w-32", "h-full", "min-h-12", "text-lg", "py-2.5", "px-6"],
},
underline: { true: ["underline"], false: [] },
},
defaultVariants: {
intent: "primary",
size: "lg",
},
}
)
export interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps {
underline?: boolean
href: string
}
export function Button({ className, intent, size, underline, ...props }: ButtonProps) {
return (
{props.children}
)
}
================================================
FILE: components/Tooltip/Tooltip.tsx
================================================
"use client"
import * as RadixTooltip from "@radix-ui/react-tooltip"
import { cva, type VariantProps } from "class-variance-authority"
import React from "react"
import { twMerge } from "tailwind-merge"
const tooltipContent = cva([], {
variants: {
intent: {
primary: ["rounded-md", "bg-zinc-700", "font-sans", "text-white"],
},
size: {
md: ["px-4", "py-2.5", "text-xs"],
},
},
defaultVariants: {
intent: "primary",
size: "md",
},
})
const tooltipArrow = cva([], {
variants: {
intent: {
primary: ["fill-zinc-700"],
},
size: {
md: ["w-4", "h-2"],
},
},
defaultVariants: {
intent: "primary",
size: "md",
},
})
export interface TooltipProps extends VariantProps, RadixTooltip.TooltipProps {
explainer: React.ReactElement | string
children: React.ReactElement
className?: string
withArrow?: boolean
side?: "top" | "right" | "bottom" | "left"
}
export function Tooltip({
children,
explainer,
open,
defaultOpen,
onOpenChange,
intent,
size,
side = "top",
className,
withArrow,
}: TooltipProps) {
return (
{children}
{explainer}
{withArrow ? : null}
)
}
================================================
FILE: e2e/example.spec.ts
================================================
import { expect, test } from "@playwright/test"
test("has title", async ({ page }) => {
await page.goto("./")
await expect(page).toHaveTitle(/Next.js Enterprise Boilerplate/)
})
================================================
FILE: env.mjs
================================================
import { createEnv } from "@t3-oss/env-nextjs"
import { z } from "zod"
export const env = createEnv({
server: {
ANALYZE: z
.enum(["true", "false"])
.optional()
.transform((value) => value === "true"),
},
client: {},
runtimeEnv: {
ANALYZE: process.env.ANALYZE,
},
})
================================================
FILE: eslint.config.mjs
================================================
import * as fs from "fs"
// https://github.com/francoismassart/eslint-plugin-tailwindcss/pull/381
// import eslintPluginTailwindcss from "eslint-plugin-tailwindcss"
import eslintPluginImport from "eslint-plugin-import"
import eslintPluginNext from "@next/eslint-plugin-next"
import eslintPluginStorybook from "eslint-plugin-storybook"
import typescriptEslint from "typescript-eslint"
const eslintIgnore = [
".git/",
".next/",
"node_modules/",
"dist/",
"build/",
"coverage/",
"*.min.js",
"*.config.js",
"*.d.ts",
]
const config = typescriptEslint.config(
{
ignores: eslintIgnore,
},
...eslintPluginStorybook.configs["flat/recommended"],
// https://github.com/francoismassart/eslint-plugin-tailwindcss/pull/381
// ...eslintPluginTailwindcss.configs["flat/recommended"],
typescriptEslint.configs.recommended,
eslintPluginImport.flatConfigs.recommended,
{
plugins: {
"@next/next": eslintPluginNext,
},
rules: {
...eslintPluginNext.configs.recommended.rules,
...eslintPluginNext.configs["core-web-vitals"].rules,
},
},
{
settings: {
tailwindcss: {
callees: ["classnames", "clsx", "ctl", "cn", "cva"],
},
"import/resolver": {
typescript: true,
node: true,
},
},
rules: {
"@typescript-eslint/no-unused-vars": [
"warn",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
},
],
"sort-imports": [
"error",
{
ignoreCase: true,
ignoreDeclarationSort: true,
},
],
"import/order": [
"warn",
{
groups: ["external", "builtin", "internal", "sibling", "parent", "index"],
pathGroups: [
...getDirectoriesToSort().map((singleDir) => ({
pattern: `${singleDir}/**`,
group: "internal",
})),
{
pattern: "env",
group: "internal",
},
{
pattern: "theme",
group: "internal",
},
{
pattern: "public/**",
group: "internal",
position: "after",
},
],
pathGroupsExcludedImportTypes: ["internal"],
alphabetize: {
order: "asc",
caseInsensitive: true,
},
},
],
},
}
)
function getDirectoriesToSort() {
const ignoredSortingDirectories = [".git", ".next", ".vscode", "node_modules"]
return fs
.readdirSync(process.cwd())
.filter((file) => fs.statSync(process.cwd() + "/" + file).isDirectory())
.filter((f) => !ignoredSortingDirectories.includes(f))
}
export default config
================================================
FILE: git-conventional-commits.yaml
================================================
---
convention:
commitTypes:
- feat
- fix
- perf
- refactor
- style
- test
- build
- ops
- docs
- chore
- merge
- revert
commitScopes: []
releaseTagGlobPattern: v[0-9]*.[0-9]*.[0-9]*
changelog:
commitTypes:
- feat
- fix
- perf
- merge
includeInvalidCommits: true
commitIgnoreRegexPattern: "^WIP "
headlines:
feat: Features
fix: Bug Fixes
perf: Performance Improvements
merge: Merges
breakingChange: BREAKING CHANGES
## GitHub
# commitUrl: https://github.com/ACCOUNT/REPOSITORY/commit/%commit%
# commitRangeUrl: https://github.com/ACCOUNT/REPOSITORY/compare/%from%...%to%?diff=split
## GitHub Issues
# issueRegexPattern: "#[0-9]+"
# issueUrl: https://github.com/ACCOUNT/REPOSITORY/issues/%issue%
## Jira Issues
# issueRegexPattern: "[A-Z][A-Z0-9]+-[0-9]+"
# issueUrl: https://WORKSPACE.atlassian.net/browse/%issue%
================================================
FILE: instrumentation.ts
================================================
import { registerOTel } from "@vercel/otel"
export function register() {
registerOTel("next-app")
}
================================================
FILE: lp-items.tsx
================================================
export const LP_GRID_ITEMS = [
{
title: "Next.js",
description: "Fast by default, with config optimized for performance.",
icon: (
),
},
{
title: "Tailwind CSS",
description: "A utility-first CSS framework for rapid UI development.",
icon: (
),
},
{
title: "ESlint & Prettier",
description: "For clean, consistent, and error-free code.",
icon: (
),
},
{
title: "Extremely strict TypeScript",
description: "With `ts-reset` library for ultimate type safety.",
icon: (
),
},
{
title: "Bundle analyzer plugin",
description: "Keep an eye on your bundle size.",
icon: (
),
},
{
title: "Vitest & React Testing Library",
description: "For rock-solid unit and integration tests.",
icon: (
),
},
{
title: "Playwright",
description: "Write end-to-end tests like a pro.",
icon: (
),
},
{
title: "Storybook",
description: "Create, test, and showcase your components.",
icon: (
),
},
{
title: "Smoke Testing & Acceptance Tests",
description: "For confidence in your deployments.",
icon: (
),
},
{
title: "Conventional commits git hook",
description: "Keep your commit history neat and tidy.",
icon: (
),
},
{
title: "Observability",
description: "Open Telemetry integration for seamless monitoring.",
icon: (
),
},
{
title: "Absolute imports",
description: "No more spaghetti imports.",
icon: (
),
},
{
title: "Health checks",
description: "Kubernetes-compatible for robust deployments.",
icon: (
),
},
{
title: "Radix UI",
description: "Headless UI components for endless customization.",
icon: (
),
},
{
title: "CVA",
description: "Create a consistent, reusable, and atomic design system.",
icon: (
),
},
{
title: "Renovate BOT",
description: "Auto-updating dependencies, so you can focus on coding.",
icon: (
),
},
{
title: "Patch-package",
description: "Fix external dependencies without losing your mind.",
icon: (
),
},
{
title: "Components coupling & cohesion graph",
description: "A tool for managing component relationships.",
icon: (
),
},
{
title: "GitHub Actions",
description: "Pre-configured actions for smooth workflows, including Bundle Size and performance stats.",
icon: (
),
},
{
title: "Automated ChatGPT Code Reviews",
description: "Stay on the cutting edge with AI-powered code reviews!",
icon: (
),
},
{
title: "Semantic Release",
description: "For automatic changelog generation.",
icon: (
),
},
]
================================================
FILE: next-env.d.ts
================================================
///
///
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
================================================
FILE: next.config.ts
================================================
import withBundleAnalyzer from "@next/bundle-analyzer"
import { type NextConfig } from "next"
import { env } from "./env.mjs"
const config: NextConfig = {
reactStrictMode: true,
logging: {
fetches: {
fullUrl: true,
},
},
rewrites: async () => [
{ source: "/healthz", destination: "/api/health" },
{ source: "/api/healthz", destination: "/api/health" },
{ source: "/health", destination: "/api/health" },
{ source: "/ping", destination: "/api/health" },
],
}
export default env.ANALYZE ? withBundleAnalyzer({ enabled: env.ANALYZE })(config) : config
================================================
FILE: package.json
================================================
{
"name": "next-enterprise",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "cross-env FORCE_COLOR=1 next dev --turbo",
"build": "next build",
"start": "next start",
"lint": "next lint",
"lint:fix": "next lint --fix",
"prettier": "prettier --check \"**/*.{js,jsx,ts,tsx}\"",
"prettier:fix": "prettier --write \"**/*.{js,jsx,ts,tsx}\"",
"analyze": "cross-env ANALYZE=true pnpm run build",
"storybook": "cross-env FORCE_COLOR=1 storybook dev -p 6006",
"test-storybook": "cross-env FORCE_COLOR=1 test-storybook",
"build-storybook": "cross-env FORCE_COLOR=1 storybook build",
"test": "cross-env FORCE_COLOR=1 vitest",
"test:watch": "cross-env FORCE_COLOR=1 vitest --watch",
"test:ui": "cross-env FORCE_COLOR=1 vitest --ui",
"test:coverage": "cross-env FORCE_COLOR=1 vitest --coverage",
"e2e:headless": "playwright test",
"e2e:ui": "playwright test --ui",
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
"postinstall": "npx patch-package -y",
"coupling-graph": "npx madge --extensions js,jsx,ts,tsx,css,md,mdx ./ --exclude '.next|tailwind.config.js|reset.d.ts|prettier.config.js|postcss.config.js|playwright.config.ts|next.config.js|next-env.d.ts|instrumentation.ts|e2e/|README.md|.storybook/|.eslintrc.js' --image graph.svg"
},
"dependencies": {
"@next/bundle-analyzer": "^15.3.1",
"@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-form": "^0.1.8",
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-radio-group": "^1.3.8",
"@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-slider": "^1.3.6",
"@radix-ui/react-switch": "^1.2.6",
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-toggle-group": "^1.1.11",
"@radix-ui/react-tooltip": "^1.2.8",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/commit-analyzer": "^13.0.1",
"@semantic-release/git": "^10.0.1",
"@semantic-release/github": "^12.0.6",
"@semantic-release/npm": "^13.1.5",
"@semantic-release/release-notes-generator": "^14.1.0",
"@t3-oss/env-nextjs": "^0.13.10",
"@vercel/otel": "^1.12.0",
"class-variance-authority": "^0.7.1",
"lodash": "^4.17.23",
"next": "15.5.10",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"tailwind-merge": "^3.5.0",
"zod": "^3.24.4"
},
"devDependencies": {
"@babel/core": "^7.29.0",
"@babel/plugin-syntax-flow": "^7.28.6",
"@babel/plugin-transform-optional-chaining": "^7.28.6",
"@babel/plugin-transform-react-jsx": "^7.28.6",
"@eslint/eslintrc": "^3.3.5",
"@next/eslint-plugin-next": "15.1.6",
"@opentelemetry/api": "1.7.0",
"@opentelemetry/resources": "1.18.1",
"@opentelemetry/sdk-node": "0.45.1",
"@opentelemetry/sdk-trace-node": "1.18.1",
"@opentelemetry/semantic-conventions": "1.40.0",
"@playwright/test": "^1.58.2",
"@storybook/addon-controls": "^8.6.14",
"@storybook/addon-essentials": "^8.6.14",
"@storybook/addon-interactions": "^8.6.14",
"@storybook/addon-links": "^8.6.14",
"@storybook/blocks": "^8.6.14",
"@storybook/nextjs": "^8.6.14",
"@storybook/react": "^8.6.14",
"@storybook/test": "^8.6.18",
"@storybook/test-runner": "^0.21.3",
"@tailwindcss/postcss": "^4.2.1",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@total-typescript/ts-reset": "^0.6.1",
"@types/node": "^22.15.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@typescript-eslint/eslint-plugin": "^8.57.1",
"@vitejs/plugin-react": "^4.7.0",
"@vitest/ui": "^3.2.4",
"all-contributors-cli": "^6.26.1",
"cross-env": "^7.0.3",
"eslint": "^9.26.0",
"eslint-config-next": "15.1.6",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-storybook": "^0.11.6",
"eslint-plugin-tailwindcss": "^3.18.2",
"fetch-mock": "^12.6.0",
"gzip-size": "6.0.0",
"jsdom": "^26.1.0",
"mkdirp": "^3.0.1",
"patch-package": "^8.0.1",
"postcss": "^8.5.8",
"postcss-import": "^16.1.1",
"postinstall-postinstall": "^2.1.0",
"prettier": "^3.8.1",
"prettier-plugin-tailwindcss": "^0.7.2",
"semantic-release": "^25.0.3",
"storybook": "^8.6.18",
"tailwindcss": "^4.2.1",
"tsc": "^2.0.4",
"typed-query-selector": "^2.12.1",
"typescript": "^5.9.3",
"typescript-eslint": "^8.57.1",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.2.4",
"webpack": "5.99.9"
},
"engines": {
"node": ">=20.0.0"
},
"packageManager": "pnpm@10.0.0",
"pnpm": {
"overrides": {
"tmp@<=0.2.3": ">=0.2.4"
}
}
}
================================================
FILE: playwright.config.ts
================================================
import { defineConfig, devices } from "@playwright/test"
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// require('dotenv').config();
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: "./e2e",
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: "html",
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: "http://127.0.0.1:3000",
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry",
},
/* Configure projects for major browsers */
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
{
name: "firefox",
use: { ...devices["Desktop Firefox"] },
},
{
name: "webkit",
use: { ...devices["Desktop Safari"] },
},
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ..devices['Desktop Chrome'], channel: 'chrome' },
// },
],
/* Run your local dev server before starting the tests */
webServer: {
command: "pnpm dev",
url: "http://127.0.0.1:3000",
reuseExistingServer: !process.env.CI,
},
})
================================================
FILE: postcss.config.js
================================================
module.exports = {
plugins: {
"postcss-import": {},
"@tailwindcss/postcss": {},
},
}
================================================
FILE: prettier.config.js
================================================
module.exports = {
plugins: ["prettier-plugin-tailwindcss"],
trailingComma: "es5",
tabWidth: 2,
printWidth: 120,
semi: false,
}
================================================
FILE: renovate.json
================================================
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
],
"packageRules": [
{
"enabled": false,
"matchPackagePatterns": ["*"]
}
],
"vulnerabilityAlerts": {
"enabled": true
},
"osvVulnerabilityAlerts": true
}
================================================
FILE: report-bundle-size.js
================================================
#!/usr/bin/env node
/* eslint-disable no-console */
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
// edited to work with the appdir by @raphaelbadia
const gzSize = require("gzip-size")
const mkdirp = require("mkdirp")
const fs = require("fs")
const path = require("path")
// Pull options from `package.json`
const options = getOptions()
const BUILD_OUTPUT_DIRECTORY = getBuildOutputDirectory(options)
// first we check to make sure that the build output directory exists
const nextMetaRoot = path.join(process.cwd(), BUILD_OUTPUT_DIRECTORY)
try {
fs.accessSync(nextMetaRoot, fs.constants.R_OK)
} catch (err) {
console.error(
`No build output found at "${nextMetaRoot}" - you may not have your working directory set correctly, or not have run "next build".`
)
process.exit(1)
}
// if so, we can import the build manifest
const buildMeta = require(path.join(nextMetaRoot, "build-manifest.json"))
const appDirMeta = require(path.join(nextMetaRoot, "app-build-manifest.json"))
// this memory cache ensures we dont read any script file more than once
// bundles are often shared between pages
const memoryCache = {}
// since _app is the template that all other pages are rendered into,
// every page must load its scripts. we'll measure its size here
const globalBundle = buildMeta.pages["/_app"]
const globalBundleSizes = getScriptSizes(globalBundle)
// next, we calculate the size of each page's scripts, after
// subtracting out the global scripts
const allPageSizes = Object.values(buildMeta.pages).reduce((acc, scriptPaths, i) => {
const pagePath = Object.keys(buildMeta.pages)[i]
const scriptSizes = getScriptSizes(scriptPaths.filter((scriptPath) => !globalBundle.includes(scriptPath)))
acc[pagePath] = scriptSizes
return acc
}, {})
const globalAppDirBundle = buildMeta.rootMainFiles
const globalAppDirBundleSizes = getScriptSizes(globalAppDirBundle)
const allAppDirSizes = Object.values(appDirMeta.pages).reduce((acc, scriptPaths, i) => {
const pagePath = Object.keys(appDirMeta.pages)[i]
const scriptSizes = getScriptSizes(scriptPaths.filter((scriptPath) => !globalAppDirBundle.includes(scriptPath)))
acc[pagePath] = scriptSizes
return acc
}, {})
// format and write the output
const rawData = JSON.stringify({
...allAppDirSizes,
__global: globalAppDirBundleSizes,
})
// log ouputs to the gh actions panel
console.log(rawData)
mkdirp.sync(path.join(nextMetaRoot, "analyze/"))
fs.writeFileSync(path.join(nextMetaRoot, "analyze/__bundle_analysis.json"), rawData)
// --------------
// Util Functions
// --------------
// given an array of scripts, return the total of their combined file sizes
function getScriptSizes(scriptPaths) {
const res = scriptPaths.reduce(
(acc, scriptPath) => {
const [rawSize, gzipSize] = getScriptSize(scriptPath)
acc.raw += rawSize
acc.gzip += gzipSize
return acc
},
{ raw: 0, gzip: 0 }
)
return res
}
// given an individual path to a script, return its file size
function getScriptSize(scriptPath) {
const encoding = "utf8"
const p = path.join(nextMetaRoot, scriptPath)
let rawSize, gzipSize
if (Object.keys(memoryCache).includes(p)) {
rawSize = memoryCache[p][0]
gzipSize = memoryCache[p][1]
} else {
const textContent = fs.readFileSync(p, encoding)
rawSize = Buffer.byteLength(textContent, encoding)
gzipSize = gzSize.sync(textContent)
memoryCache[p] = [rawSize, gzipSize]
}
return [rawSize, gzipSize]
}
/**
* Reads options from `package.json`
*/
function getOptions(pathPrefix = process.cwd()) {
const pkg = require(path.join(pathPrefix, "package.json"))
return { ...pkg.nextBundleAnalysis, name: pkg.name }
}
/**
* Gets the output build directory, defaults to `.next`
*
* @param {object} options the options parsed from package.json.nextBundleAnalysis using `getOptions`
* @returns {string}
*/
function getBuildOutputDirectory(options) {
return options.buildOutputDirectory || ".next"
}
================================================
FILE: reset.d.ts
================================================
import "@total-typescript/ts-reset"
import "typed-query-selector/strict"
================================================
FILE: styles/tailwind.css
================================================
@import 'tailwindcss';
/*
The default border color has changed to `currentColor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentColor);
}
}
================================================
FILE: tsconfig.json
================================================
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Next.js",
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noUncheckedIndexedAccess": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"baseUrl": ".",
"types": ["node", "vitest/globals", "@testing-library/jest-dom"],
"plugins": [
{
"name": "next"
}
]
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.mjs", "vitest.config.ts", ".next/types/**/*.ts", "eslint.config.mjs"],
"exclude": ["node_modules"]
}
================================================
FILE: vercel.json
================================================
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"installCommand": "corepack enable && pnpm install"
}
================================================
FILE: vitest.config.ts
================================================
import { defineConfig } from "vitest/config"
import react from "@vitejs/plugin-react"
import tsconfigPaths from "vite-tsconfig-paths"
export default defineConfig({
plugins: [tsconfigPaths(), react()],
test: {
environment: "jsdom",
setupFiles: "./vitest.setup.ts",
globals: true,
include: ["**/*.test.{ts,tsx}", "**/*.spec.{ts,tsx}"],
exclude: ["**/node_modules/**", "**/dist/**", "**/e2e/**", ".next/**"],
},
})
================================================
FILE: vitest.setup.ts
================================================
import "@testing-library/jest-dom"