├── 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 |
{ 7 | 'use server'; 8 | 9 | await signOut({ redirectTo: '/' }); 10 | }} 11 | > 12 | 15 |
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 |
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 |
{ 8 | 'use server'; 9 | 10 | await signIn('github', { redirectTo: '/' }); 11 | }} 12 | > 13 | 16 |
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 | {name!} 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 | --------------------------------------------------------------------------------