Nextjs project setup
- Install Nextjs
- Prettier
- Husky, lint-stage and commit-lint
- Env validation
- Shadcn
- Zod
- React Query
- types utils (isNilOrEmpty, isObjectWithKey etc)
- Install Nextjs
pnpm create next-app@latest my-app --yes
- Prettier
pnpm install -D prettier eslint-config-prettier
// .prettierrc
{
"semi": true,
"singleQuote": true,
"trailingComma": "all"
}
// in eslint.config.mjs (or .eslintrc)
{
extends: ['next/core-web-vitals', 'prettier'],
},
// packages.json > scripts
"format": "prettier --write .",
- Husky, lint-stage and commit-lint
pnpm install -D husky lint-staged
npx husky init
// package.json > scripts
"prepare": "husky"
// commit lint
pnpm add -D @commitlint/cli @commitlint/config-conventional\n
// adding scripts: packages.json > scripts
"lint-staged": "lint-staged"
// modify .husky/pre-commit
pnpm run lint-staged
// .husky/_/commit-msg
. "$(dirname "$0")/h"
// adding to package.json file:
"lint-staged": {
"*.ts": [
"eslint --fix",
"prettier --write"
]
}
// commitlint.config.cjs
module.exports = {
extends: ["@commitlint/config-conventional"],
rules: {
"subject-case": [
2,
"never",
["sentence-case", "start-case", "pascal-case"],
],
"type-enum": [
2,
"always",
["feat", "fix", "docs", "style", "refactor", "test", "chore"],
],
},
};
- Env validation
// env.ts
import { z } from "zod";
const serverEnvSchema = z.object({
NODE_ENV: z.enum(["development", "test", "production"]),
});
const clientEnvSchema = z.object({
NEXT_PUBLIC_API_URL: z.string().url(),
});
const serverEnv = serverEnvSchema.safeParse(process.env);
const clientEnv = clientEnvSchema.safeParse(process.env);
if (!serverEnv.success) {
console.error(
"❌ Invalid SERVER env:",
serverEnv.error.flatten().fieldErrors,
);
throw new Error("Invalid server environment variables");
}
if (!clientEnv.success) {
console.error(
"❌ Invalid CLIENT env:",
clientEnv.error.flatten().fieldErrors,
);
throw new Error("Invalid client environment variables");
}
export const env = {
...serverEnv.data,
...clientEnv.data,
};
// instrumentation.ts
import "@/env";
export function register() {
// noop
}
- Shadcn
// Installation
npx shadcn@latest init
// Adding ui component
pnpm dlx shadcn@latest add button
- Zod
- React Query
pnpm add @tanstack/react-query
pnpm add -D @tanstack/eslint-plugin-query
// client provider
"use client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
export const queryClient = new QueryClient();
export const ClientProvider = ({ children }: { children: React.ReactNode }) => {
return (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);
};
- types utils
// isNilOrEmpty
export function isNilOrEmpty(value: any): boolean {
// null or undefined
if (value === null || value === undefined) return true;
// string or array
if (typeof value === "string" || Array.isArray(value)) {
return value.length === 0;
}
// plain object (NOT array-like)
if (typeof value === "object") {
return Object.keys(value).length === 0;
}
return false;
}
// isObjectWithKey
export function isObjectWithKey(obj: any, key: string): boolean {
return (
!isNilOrEmpty(obj) &&
typeof obj === "object" &&
!Array.isArray(obj) &&
Object.prototype.hasOwnProperty.call(obj, key)
);
}