├── src
├── types
│ └── global.ts
├── components
│ ├── ui
│ │ ├── Tag.tsx
│ │ └── Button.tsx
│ ├── Header.tsx
│ ├── Footer.tsx
│ └── SignOutForm.tsx
├── app
│ ├── api
│ │ └── auth
│ │ │ └── [...nextauth]
│ │ │ └── route.ts
│ ├── not-found.tsx
│ ├── robots.ts
│ ├── sitemap.ts
│ ├── (unauth)
│ │ ├── layout.tsx
│ │ ├── products
│ │ │ ├── components
│ │ │ │ └── ProductCard.tsx
│ │ │ ├── layout.tsx
│ │ │ └── page.tsx
│ │ ├── sign-in
│ │ │ ├── components
│ │ │ │ └── SignInForm.tsx
│ │ │ ├── layout.tsx
│ │ │ └── page.tsx
│ │ └── page.tsx
│ ├── (auth)
│ │ ├── profile
│ │ │ └── page.tsx
│ │ └── layout.tsx
│ └── layout.tsx
├── libs
│ ├── axios.ts
│ └── auth.ts
├── utils
│ └── helpers.ts
└── styles
│ └── global.css
├── .husky
├── commit-msg
└── pre-commit
├── .prettierrc
├── public
└── assets
│ └── images
│ └── favicon.ico
├── postcss.config.mjs
├── lint-staged.config.js
├── commitlint.config.ts
├── next.config.mjs
├── tailwind.config.ts
├── .gitignore
├── .eslintrc.json
├── LICENSE
├── package.json
├── tsconfig.json
└── README.md
/src/types/global.ts:
--------------------------------------------------------------------------------
1 | // Global shared types
2 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | cd "$(dirname "$0")/.." && npx --no -- commitlint --edit $1
3 |
--------------------------------------------------------------------------------
/src/components/ui/Tag.tsx:
--------------------------------------------------------------------------------
1 | export default function Tag() {
2 | return
Tag
;
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/ui/Button.tsx:
--------------------------------------------------------------------------------
1 | export default function Button() {
2 | return Button
;
3 | }
4 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "tabWidth": 2,
4 | "semi": true,
5 | "singleQuote": true
6 | }
7 |
--------------------------------------------------------------------------------
/public/assets/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nhanluongoe/nextjs-boilerplate/HEAD/public/assets/images/favicon.ico
--------------------------------------------------------------------------------
/src/app/api/auth/[...nextauth]/route.ts:
--------------------------------------------------------------------------------
1 | import { handlers } from '@/libs/auth';
2 |
3 | export const { GET, POST } = handlers;
4 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # Disable concurent to run `check-types` after ESLint in lint-staged
3 | cd "$(dirname "$0")/.." && npx lint-staged --concurrent false
4 |
--------------------------------------------------------------------------------
/src/libs/axios.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | const instance = axios.create({
4 | baseURL: 'https://api.example.com',
5 | });
6 |
7 | export default instance;
8 |
--------------------------------------------------------------------------------
/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('postcss-load-config').Config} */
2 | const config = {
3 | plugins: {
4 | tailwindcss: {},
5 | },
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/lint-staged.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | '*.{js,jsx,ts,tsx}': ['eslint --fix', 'eslint'],
3 | '**/*.ts?(x)': () => 'npm run check-types',
4 | '*.{json,yaml}': ['prettier --write'],
5 | };
6 |
--------------------------------------------------------------------------------
/src/libs/auth.ts:
--------------------------------------------------------------------------------
1 | import NextAuth from 'next-auth';
2 | import github from 'next-auth/providers/github';
3 |
4 | export const { handlers, signIn, signOut, auth } = NextAuth({
5 | providers: [github],
6 | });
7 |
--------------------------------------------------------------------------------
/commitlint.config.ts:
--------------------------------------------------------------------------------
1 | import type { UserConfig } from '@commitlint/types';
2 |
3 | const Configuration: UserConfig = {
4 | extends: ['@commitlint/config-conventional'],
5 | };
6 |
7 | export default Configuration;
8 |
--------------------------------------------------------------------------------
/src/components/Header.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 |
3 | export default function Header() {
4 | return (
5 |
6 |
7 | Next.js Starter Template
8 |
9 |
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/next.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | images: {
4 | remotePatterns: [
5 | {
6 | protocol: 'https',
7 | hostname: 'avatars.githubusercontent.com',
8 | },
9 | ],
10 | },
11 | };
12 |
13 | export default nextConfig;
14 |
--------------------------------------------------------------------------------
/src/app/not-found.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 |
3 | export default function NotFound() {
4 | return (
5 |
6 |
404 Not Found
7 |
8 | Return Home
9 |
10 |
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/src/app/robots.ts:
--------------------------------------------------------------------------------
1 | import type { MetadataRoute } from 'next';
2 | import { getBaseUrl } from '@/utils/helpers';
3 |
4 | export default function robots(): MetadataRoute.Robots {
5 | return {
6 | rules: {
7 | userAgent: '*',
8 | allow: '/',
9 | },
10 | sitemap: `${getBaseUrl()}/sitemap.xml`,
11 | };
12 | }
13 |
--------------------------------------------------------------------------------
/src/components/Footer.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 |
3 | export default function Footer() {
4 | return (
5 |
6 |
7 | Created by{' '}
8 |
9 | Nhan Luong
10 |
11 |
12 |
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/src/app/sitemap.ts:
--------------------------------------------------------------------------------
1 | import { getBaseUrl } from '@/utils/helpers';
2 | import type { MetadataRoute } from 'next';
3 |
4 | export default function sitemap(): MetadataRoute.Sitemap {
5 | return [
6 | {
7 | url: `${getBaseUrl()}/`,
8 | lastModified: new Date(),
9 | changeFrequency: 'daily',
10 | priority: 0.7,
11 | },
12 | // Add more URLs here
13 | ];
14 | }
15 |
--------------------------------------------------------------------------------
/src/components/SignOutForm.tsx:
--------------------------------------------------------------------------------
1 | import { signOut } from '@/libs/auth';
2 |
3 | export default function SignOutForm() {
4 | return (
5 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/src/app/(unauth)/layout.tsx:
--------------------------------------------------------------------------------
1 | import Footer from '@/components/Footer';
2 | import Header from '@/components/Header';
3 | import { ReactNode } from 'react';
4 |
5 | export default function UnauthLayout({ children }: { children: ReactNode }) {
6 | return (
7 |
8 |
9 | {children}
10 |
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'tailwindcss';
2 |
3 | const config: Config = {
4 | content: [
5 | './src/pages/**/*.{js,ts,jsx,tsx,mdx}',
6 | './src/components/**/*.{js,ts,jsx,tsx,mdx}',
7 | './src/app/**/*.{js,ts,jsx,tsx,mdx}',
8 | ],
9 | theme: {
10 | extend: {
11 | colors: {
12 | dark: '#313638',
13 | },
14 | },
15 | },
16 | plugins: [],
17 | };
18 | export default config;
19 |
--------------------------------------------------------------------------------
/src/app/(unauth)/products/components/ProductCard.tsx:
--------------------------------------------------------------------------------
1 | interface ProductCardProps {
2 | name: string;
3 | description: string;
4 | price: number;
5 | }
6 | export default function ProductCard(props: ProductCardProps) {
7 | const { name, description, price } = props;
8 | return (
9 |
10 |
{name}
11 |
{description}
12 |
{price} $
13 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/src/app/(unauth)/sign-in/components/SignInForm.tsx:
--------------------------------------------------------------------------------
1 | import { signIn } from '@/libs/auth';
2 |
3 | export default function SignInForm() {
4 | return (
5 |
6 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/src/utils/helpers.ts:
--------------------------------------------------------------------------------
1 | import { twMerge } from 'tailwind-merge';
2 | import { type ClassValue, clsx } from 'clsx';
3 |
4 | export const getBaseUrl = () => {
5 | if (process.env.NEXT_PUBLIC_APP_URL) {
6 | return process.env.NEXT_PUBLIC_APP_URL;
7 | }
8 |
9 | if (process.env.VERCEL_URL) {
10 | return `https://${process.env.VERCEL_URL}`;
11 | }
12 |
13 | return 'http://localhost:3000';
14 | };
15 |
16 | export function cn(...inputs: ClassValue[]) {
17 | return twMerge(clsx(inputs));
18 | }
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/src/app/(unauth)/products/layout.tsx:
--------------------------------------------------------------------------------
1 | import { Metadata } from 'next';
2 | import React from 'react';
3 |
4 | export const metadata: Metadata = {
5 | title: 'Product Layout',
6 | description: 'Product layout description',
7 | openGraph: {
8 | type: 'website',
9 | locale: 'en_US',
10 | // ... add more open graph meta tags
11 | },
12 | };
13 |
14 | export default function ProductLayout({
15 | children,
16 | }: {
17 | children: Readonly;
18 | }) {
19 | return (
20 | {children}
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/src/app/(unauth)/sign-in/layout.tsx:
--------------------------------------------------------------------------------
1 | import { Metadata } from 'next';
2 | import React from 'react';
3 |
4 | export const metadata: Metadata = {
5 | title: 'Sign In Layout Page',
6 | description: 'Desc for Sign In Layout Page',
7 | openGraph: {
8 | type: 'website',
9 | locale: 'en_US',
10 | // ... add more open graph meta tags
11 | },
12 | };
13 |
14 | export default function SignInLayout({
15 | children,
16 | }: {
17 | children: Readonly;
18 | }) {
19 | return (
20 | {children}
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "next/core-web-vitals",
4 | "plugin:prettier/recommended",
5 | "plugin:react/recommended",
6 | "plugin:@typescript-eslint/recommended",
7 | "airbnb",
8 | "airbnb/hooks",
9 | "plugin:react/jsx-runtime"
10 | ],
11 | "rules": {
12 | "react/jsx-filename-extension": [1, { "extensions": [".tsx"] }],
13 | "react/jsx-one-expression-per-line": "off",
14 | "import/extensions": [
15 | "error",
16 | "ignorePackages",
17 | {
18 | "js": "never",
19 | "jsx": "never",
20 | "ts": "never",
21 | "tsx": "never"
22 | }
23 | ],
24 | "comma-dangle": "off",
25 | "object-curly-newline": "off"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/app/(auth)/profile/page.tsx:
--------------------------------------------------------------------------------
1 | import SignOutForm from '@/components/SignOutForm';
2 | import { auth } from '@/libs/auth';
3 | import Image from 'next/image';
4 |
5 | export default async function ProfilePage() {
6 | const session = await auth();
7 | if (!session) return null;
8 |
9 | const { user } = session;
10 | if (!user) return null;
11 |
12 | const { name, email, image } = user;
13 |
14 | return (
15 |
16 |
Profile
17 |
18 | This page is protected. You can only see your own profile when you are
19 | signed in.
20 |
21 |
22 |
Name: {name}
23 |
Email: {email}
24 |
25 |
26 |
27 |
28 | );
29 | }
30 |
--------------------------------------------------------------------------------
/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from 'next';
2 | import { Inter } from 'next/font/google';
3 | import '@/styles/global.css';
4 | import { ReactNode } from 'react';
5 | import { cn } from '@/utils/helpers';
6 |
7 | const inter = Inter({ subsets: ['latin'] });
8 |
9 | export const metadata: Metadata = {
10 | title: 'Create Next App',
11 | description: 'Generated by create next app',
12 | openGraph: {
13 | type: 'website',
14 | locale: 'en_US',
15 | // ... add more open graph meta tags
16 | },
17 | };
18 |
19 | export default function RootLayout({
20 | children,
21 | }: Readonly<{
22 | children: ReactNode;
23 | }>) {
24 | return (
25 |
26 |
32 | {children}
33 |
34 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/src/app/(auth)/layout.tsx:
--------------------------------------------------------------------------------
1 | import { auth } from '@/libs/auth';
2 | import { Metadata } from 'next';
3 | import Link from 'next/link';
4 | import React from 'react';
5 |
6 | export const metadata: Metadata = {
7 | title: 'Auth module',
8 | description: 'Description of the auth module',
9 | openGraph: {
10 | type: 'website',
11 | locale: 'en_US',
12 | // ... add more open graph meta tags
13 | },
14 | };
15 |
16 | export default async function AuthLayout({
17 | children,
18 | }: {
19 | children: Readonly;
20 | }) {
21 | const session = await auth();
22 | return (
23 |
24 | {session ? (
25 | children
26 | ) : (
27 |
28 |
Not authenticated
29 |
30 | Sign In
31 |
32 |
33 | )}
34 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/src/styles/global.css:
--------------------------------------------------------------------------------
1 | /* Global styles */
2 | @tailwind base;
3 | @tailwind components;
4 | @tailwind utilities;
5 |
6 | @layer base {
7 | h1 {
8 | @apply text-4xl font-bold mb-4;
9 | }
10 |
11 | h2 {
12 | @apply text-3xl font-bold mb-4;
13 | }
14 |
15 | h3 {
16 | @apply text-2xl font-bold mb-4;
17 | }
18 |
19 | h4 {
20 | @apply text-xl font-bold mb-4;
21 | }
22 |
23 | h5 {
24 | @apply text-lg font-bold mb-4;
25 | }
26 |
27 | h6 {
28 | @apply text-base font-bold mb-4;
29 | }
30 |
31 | h7 {
32 | @apply text-sm font-bold mb-4;
33 | }
34 | }
35 |
36 | @layer components {
37 | .btn {
38 | @apply px-4 py-2 rounded;
39 | }
40 |
41 | .primary-btn {
42 | @apply btn bg-blue-500 text-white;
43 | }
44 |
45 | .secondary-btn {
46 | @apply bgn bg-gray-500 text-white;
47 | }
48 |
49 | .danger-btn {
50 | @apply btn bg-red-500 text-white;
51 | }
52 |
53 | .link {
54 | @apply text-blue-500;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/app/(unauth)/sign-in/page.tsx:
--------------------------------------------------------------------------------
1 | import { auth } from '@/libs/auth';
2 | import SignOutForm from '@/components/SignOutForm';
3 |
4 | import { Metadata } from 'next';
5 | import Link from 'next/link';
6 | import SignInForm from './components/SignInForm';
7 |
8 | export const metadata: Metadata = {
9 | title: 'Sign In page',
10 | description: 'Description of the sign in page',
11 | openGraph: {
12 | type: 'website',
13 | locale: 'en_US',
14 | // ... add more open graph meta tags
15 | },
16 | };
17 |
18 | export default async function SignInPage() {
19 | const session = await auth();
20 |
21 | if (session) {
22 | return (
23 |
24 |
You are already signed in
25 |
26 |
27 | Profile
28 |
29 |
30 |
31 |
32 | );
33 | }
34 |
35 | return ;
36 | }
37 |
--------------------------------------------------------------------------------
/src/app/(unauth)/page.tsx:
--------------------------------------------------------------------------------
1 | import { auth } from '@/libs/auth';
2 | import { Metadata } from 'next';
3 | import Link from 'next/link';
4 |
5 | export const metadata: Metadata = {
6 | title: 'Index page',
7 | description: 'Description of the index page',
8 | openGraph: {
9 | type: 'website',
10 | locale: 'en_US',
11 | // ... add more open graph meta tags
12 | },
13 | };
14 |
15 | export default async function IndexPage() {
16 | const session = await auth();
17 |
18 | return (
19 |
20 |
Index Page
21 |
22 | This is the index page of unauth routes. This may be the landing page
23 |
24 |
25 | {session ? (
26 |
27 | Profile
28 |
29 | ) : (
30 |
31 | Sign in
32 |
33 | )}
34 |
35 |
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Nhan Luong
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/app/(unauth)/products/page.tsx:
--------------------------------------------------------------------------------
1 | import { Metadata } from 'next';
2 | import ProductCard from './components/ProductCard';
3 |
4 | export const metadata: Metadata = {
5 | title: 'Product Page',
6 | description: 'Description of the product page',
7 | openGraph: {
8 | type: 'website',
9 | locale: 'en_US',
10 | // ... add more open graph meta tags
11 | },
12 | };
13 |
14 | const MOCK_PRODUCTS = [
15 | {
16 | id: '1',
17 | name: 'Product 1',
18 | price: 100,
19 | description: 'Description of product 1',
20 | },
21 | {
22 | id: '2',
23 | name: 'Product 2',
24 | price: 200,
25 | description: 'Description of product 2',
26 | },
27 | {
28 | id: '3',
29 | name: 'Product 3',
30 | price: 300,
31 | description: 'Description of product 3',
32 | },
33 | ];
34 |
35 | export default function ProductPage() {
36 | return (
37 |
38 |
Product Page
39 |
40 | {MOCK_PRODUCTS.map((product) => (
41 |
46 | ))}
47 |
48 |
49 | );
50 | }
51 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nextjs-boilerplate",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint",
10 | "check-types": "tsc --noEmit --pretty",
11 | "format": "next lint --fix && prettier '**/*.{json,yaml}' --write --ignore-path .gitignore"
12 | },
13 | "dependencies": {
14 | "axios": "^1.8.2",
15 | "clsx": "^2.1.1",
16 | "next": "^16.0.10",
17 | "next-auth": "^5.0.0-beta.25",
18 | "react": "^19.1.0",
19 | "react-dom": "^19.1.0",
20 | "tailwind-merge": "^2.3.0"
21 | },
22 | "devDependencies": {
23 | "@commitlint/cli": "^19.3.0",
24 | "@commitlint/config-conventional": "^19.2.2",
25 | "@eslint/js": "^9.7.0",
26 | "@types/eslint__js": "^8.42.3",
27 | "@types/node": "^20",
28 | "@types/react": "^19",
29 | "@types/react-dom": "^19",
30 | "eslint": "^9.15.0",
31 | "eslint-config-next": "^16.0.10",
32 | "eslint-config-prettier": "^9.1.0",
33 | "eslint-plugin-prettier": "^5.1.3",
34 | "husky": "^9.0.11",
35 | "postcss": "^8",
36 | "prettier": "3.2.5",
37 | "tailwindcss": "^3.4.1",
38 | "typescript": "^5.5.3",
39 | "typescript-eslint": "^8.18.0"
40 | },
41 | "author": "Nhan Luong"
42 | }
43 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["dom", "dom.iterable", "esnext"],
4 | "module": "esnext",
5 | "moduleResolution": "bundler",
6 | "resolveJsonModule": true,
7 | "removeComments": true,
8 | "preserveConstEnums": true,
9 | "strict": true,
10 | "alwaysStrict": true,
11 | "strictNullChecks": true,
12 | "noUncheckedIndexedAccess": true,
13 | "noImplicitAny": true,
14 | "noImplicitReturns": true,
15 | "noImplicitThis": true,
16 | "noUnusedLocals": true,
17 | "noUnusedParameters": true,
18 | "allowUnreachableCode": false,
19 | "noFallthroughCasesInSwitch": true,
20 | "target": "es2017",
21 | "outDir": "out",
22 | "sourceMap": true,
23 | "esModuleInterop": true,
24 | "allowSyntheticDefaultImports": true,
25 | "allowJs": true,
26 | "checkJs": true,
27 | "skipLibCheck": true,
28 | "forceConsistentCasingInFileNames": true,
29 | "jsx": "react-jsx",
30 | "noEmit": true,
31 | "isolatedModules": true,
32 | "incremental": true,
33 | "plugins": [
34 | {
35 | "name": "next"
36 | }
37 | ],
38 | "baseUrl": ".",
39 | "paths": {
40 | "@/*": ["./src/*"],
41 | "@/public/*": ["./public/*"]
42 | }
43 | },
44 | "include": [
45 | "next-env.d.ts",
46 | "**/*.ts",
47 | "**/*.tsx",
48 | ".next/types/**/*.ts",
49 | ".next/dev/types/**/*.ts"
50 | ],
51 | "exclude": ["node_modules"]
52 | }
53 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Next.js Boilerplate and Starter-Kit. Minimal yet scalable setup for Next.js projects
2 |
3 | A Next.js 16 boilerplate that helps you jump right into building your app without having to set up everything from scratch every time you start a new project. It comes with a minimal yet scalable setup that includes Next.js 16 with Turbopack, React 19, TypeScript, TailwindCSS, ESLint 9, Prettier, Husky, Lint-staged, Commitlint, and SEO-friendly features.
4 |
5 | ## Live Demo
6 |
7 | Check out the live demo of this boilerplate in action:
8 |
9 | [https://nextjs-boilerplate-oe.vercel.app](https://nextjs-boilerplate-oe.vercel.app)
10 |
11 | ## Features
12 |
13 | - [x] [Next.js 16](https://nextjs.org): Full-fledged framework for React apps with Turbopack
14 | - [x] [Typescript](https://typescriptlang.org): Type checking and better code quality
15 | - [x] [TailwindCSS](https://tailwindcss.com/): Utility-first CSS framework
16 | - [x] [ESLint 9](https://eslint.org): Modern pluggable linting utility for JavaScript
17 | - [x] [Prettier](https://prettier.io): Opinionated code formatter
18 | - [x] [Husky](https://typicode.github.io/husky): Git hooks made easy
19 | - [x] [Lint-staged](https://github.com/lint-staged/lint-staged): Run linters on git staged files
20 | - [x] [Commitlint](https://commitlint.js.org/): Lint commit messages
21 | - [x] [NextAuth](https://authjs.dev/): Powerful authentication for Next.js
22 | - [x] SEO-friendly: Meta tags, Open Graph, sitemap.xml and robots.txt
23 | - [x] Absolute imports: Import directories and files using the @ alias
24 | - [x] Route grouping: Group routes for auth and public pages
25 |
26 | ## Use this boilerplate
27 |
28 | You can either clone this repository using command line or clicking on the "Use this template" button to create a new repository with the same directory structure.
29 |
30 | ```sh
31 | git clone --depth=1 https://github.com/nhanluongoe/nextjs-boilerplate.git project-name
32 | cd project-name
33 | npm install
34 | npm run dev
35 | ```
36 |
37 | Make sure you config your NextAuth before running the project. For this boilerplate, it uses Github as the authentication provider so you need to create a Github OAuth app and set the `GITHUB_ID` and `GITHUB_SECRET` environment variables in your `.env` file. You can find more information about how to do this in the [NextAuth documentation](https://authjs.dev/guides/configuring-github).
38 |
39 | Basically your .env file should look like this:
40 |
41 | ```env
42 | AUTH_SECRET="yourAuthSecret"
43 | AUTH_GITHUB_ID=yourGithubClientId
44 | AUTH_GITHUB_SECRET=yourGithubClientSecret
45 | ```
46 |
47 | ## Project Structure
48 |
49 | - All common things (project-wide, global) are placed in the `src` directory. This includes components, types, styles, and utility functions. This is where we put things that are shared across the project.
50 | - In the project wide components, we have `ui` directory that contains atomic design components like `Button`, `Tag`. Outside of the `ui` directory, there are components composed of atomic components like `Header`, `Footer`.
51 | - In the `app` directory we have domains/features. This includes components, types, hooks, api (route handler) ... that are only used in that domain/feature. Basically these directories include things similar to the global things but are only used in the domain/feature.
52 | - The project uses Next.js App Router to handle routing. The router is divided into two groups: `auth` and `unauth`. The `auth` group contains routes that require authentication, while the `unauth` group contains public routes.
53 |
54 | ```
55 | |-- public # Static files
56 | |-- src # Next.js source directory
57 | | |-- app # Next.js App Router
58 | | | |-- (auth) # (Group) Private routes
59 | | | | |-- layout.tsx # Authenticated layout
60 | | | | |-- profile # (Module) Profile page
61 | | | | | `-- page.tsx
62 | | | |-- (unauth) # (Group) Public routes
63 | | | | |-- layout.tsx # Unauthenticated layout
64 | | | | |-- page.tsx # Unauthenticated index page
65 | | | | `-- products # (Module) Product page
66 | | | | |-- components # Components for the product page
67 | | | | | `-- ProductCard.tsx
68 | | | | |-- layout.tsx
69 | | | | `-- page.tsx
70 | | | |-- layout.tsx # App layout
71 | | | |-- robots.ts # Robots.txt
72 | | | `-- sitemap.ts # Sitemap.xml
73 | | |-- components # Global components
74 | | | |-- Footer.tsx
75 | | | |-- Header.tsx
76 | | | `-- ui # Atomic design components
77 | | | |-- Button.tsx
78 | | | `-- Tag.tsx
79 | | |-- api # Global API route handlers
80 | | | `-- auth
81 | | |-- styles
82 | | | `-- global.css # Global styles
83 | | |-- types # Global types
84 | | | `-- global.ts
85 | | |-- libs # 3rd-party libraries
86 | | | `-- auth.ts
87 | | `-- utils # Global utility functions
88 | | `-- helpers.ts
89 | |-- tailwind.config.ts # TailwindCSS configuration
90 | |-- .eslintrc.json # ESLint configuration
91 | |-- .prettierrc # Prettier configuration
92 | |-- LICENSE
93 | |-- README.md
94 | |-- commitlint.config.ts # Commitlint configuration
95 | |-- lint-staged.config.js # Lint-staged configuration
96 | |-- next-env.d.ts
97 | |-- next.config.mjs
98 | |-- package-lock.json
99 | |-- package.json
100 | |-- postcss.config.mjs # PostCSS configuration
101 | `-- tsconfig.json # Typescript configuration
102 | ```
103 |
104 | ## Commit message convention
105 |
106 | - The project uses Husky and Lint-staged to run ESLint and Prettier on staged files before committing.
107 | - Commit messages must follow [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/).
108 |
109 | ## Eslint configuration
110 |
111 | - The project uses modern ESLint 9 configuration compatible with Next.js 16. The configuration is based on the following packages:
112 | - [eslint-config-next](https://www.npmjs.com/package/eslint-config-next): Next.js ESLint configuration.
113 | - [typescript-eslint v8](https://typescript-eslint.io/): Enables ESLint to lint TypeScript code.
114 |
115 | ## Contributing
116 |
117 | If you have a question or have found a bug, or have any suggestions for improvement, feel free to create an issue or a pull request. Everyone is welcome.
118 |
119 | ## License
120 |
121 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
122 |
--------------------------------------------------------------------------------