├── src ├── config │ ├── index.ts │ └── env │ │ └── index.ts ├── app │ ├── favicon.ico │ ├── icon1.png │ ├── icon2.png │ ├── icon3.png │ ├── icon4.png │ ├── apple-icon.png │ ├── (root) │ │ └── page.tsx │ ├── globals.css │ ├── sitemap.ts │ ├── robots.ts │ └── layout.tsx ├── ui │ ├── header │ │ ├── header.tsx │ │ └── header-content-container.tsx │ ├── home │ │ ├── hero-section.tsx │ │ └── hero-content-container.tsx │ └── footer │ │ ├── footer.tsx │ │ └── footer-content-container.tsx ├── components │ └── container.tsx ├── lib │ └── utils.ts └── consts │ └── index.ts ├── .husky └── pre-commit ├── .env.local.example ├── pnpm-workspace.yaml ├── postcss.config.js ├── public └── images │ └── og.png ├── prettier.config.js ├── next.config.mjs ├── lint-staged.config.js ├── eslint.config.mjs ├── .gitignore ├── tsconfig.json ├── .github └── workflows │ └── ci.yml ├── LICENSE ├── README.md ├── package.json └── pnpm-lock.yaml /src/config/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./env"; 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | pnpm lint-staged 3 | -------------------------------------------------------------------------------- /.env.local.example: -------------------------------------------------------------------------------- 1 | APP_ENV=dev 2 | SITE_URL=http://localhost:3000 3 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | ignoredBuiltDependencies: 2 | - sharp 3 | - unrs-resolver 4 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alipiry/nextjs-ts-tailwind-starter/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/icon1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alipiry/nextjs-ts-tailwind-starter/HEAD/src/app/icon1.png -------------------------------------------------------------------------------- /src/app/icon2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alipiry/nextjs-ts-tailwind-starter/HEAD/src/app/icon2.png -------------------------------------------------------------------------------- /src/app/icon3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alipiry/nextjs-ts-tailwind-starter/HEAD/src/app/icon3.png -------------------------------------------------------------------------------- /src/app/icon4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alipiry/nextjs-ts-tailwind-starter/HEAD/src/app/icon4.png -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | "@tailwindcss/postcss": {}, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /public/images/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alipiry/nextjs-ts-tailwind-starter/HEAD/public/images/og.png -------------------------------------------------------------------------------- /src/app/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alipiry/nextjs-ts-tailwind-starter/HEAD/src/app/apple-icon.png -------------------------------------------------------------------------------- /src/config/env/index.ts: -------------------------------------------------------------------------------- 1 | export const APP_ENV = process.env.APP_ENV || "dev"; 2 | export const SITE_URL = process.env.SITE_URL || ""; 3 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | bracketSpacing: true, 3 | semi: true, 4 | trailingComma: "all", 5 | printWidth: 80, 6 | tabWidth: 2, 7 | plugins: ["prettier-plugin-tailwindcss"], 8 | }; 9 | -------------------------------------------------------------------------------- /src/app/(root)/page.tsx: -------------------------------------------------------------------------------- 1 | import HeroSection from "@/ui/home/hero-section"; 2 | 3 | export default function Home() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /src/ui/header/header.tsx: -------------------------------------------------------------------------------- 1 | import HeaderContentContainer from "./header-content-container"; 2 | 3 | export default function Header() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /src/ui/home/hero-section.tsx: -------------------------------------------------------------------------------- 1 | import HeroContentContainer from "./hero-content-container"; 2 | 3 | export default function HeroSection() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /src/ui/footer/footer.tsx: -------------------------------------------------------------------------------- 1 | import FooterContentContainer from "./footer-content-container"; 2 | 3 | export default function Footer() { 4 | return ( 5 | 8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | 3 | @plugin "@tailwindcss/typography"; 4 | 5 | @theme inline { 6 | --font-sans: var(--font-montserrat); 7 | } 8 | 9 | @theme { 10 | --z-index-1: 1; 11 | --z-index-2: 2; 12 | --z-index-3: 3; 13 | --z-index-4: 4; 14 | --z-index-5: 5; 15 | } 16 | -------------------------------------------------------------------------------- /src/components/container.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | 3 | export default function Container({ 4 | className, 5 | ...props 6 | }: React.ComponentPropsWithoutRef<"div">) { 7 | return ( 8 |
12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | 8 | import { Montserrat } from "next/font/google"; 9 | 10 | export const montserratFont = Montserrat({ 11 | weight: ["100", "200", "300", "400", "500", "600", "700", "800", "900"], 12 | subsets: ["latin"], 13 | variable: "--font-montserrat", 14 | }); 15 | -------------------------------------------------------------------------------- /src/app/sitemap.ts: -------------------------------------------------------------------------------- 1 | import { MetadataRoute } from "next"; 2 | import { APP_ENV, SITE_URL } from "@/config"; 3 | 4 | export default function sitemap(): MetadataRoute.Sitemap { 5 | if (APP_ENV === "production") { 6 | return [""].map((route) => ({ 7 | url: `${SITE_URL}${route}`, 8 | lastModified: new Date(), 9 | changeFrequency: "monthly" as const, 10 | priority: route === "" ? 1 : 0.8, 11 | })); 12 | } 13 | 14 | return []; 15 | } 16 | -------------------------------------------------------------------------------- /src/app/robots.ts: -------------------------------------------------------------------------------- 1 | import { MetadataRoute } from "next"; 2 | import { APP_ENV, SITE_URL } from "@/config"; 3 | 4 | export default function robots(): MetadataRoute.Robots { 5 | if (APP_ENV === "production") { 6 | return { 7 | rules: { 8 | userAgent: "*", 9 | allow: "/", 10 | }, 11 | sitemap: `${SITE_URL}/sitemap.xml`, 12 | }; 13 | } 14 | 15 | return { 16 | rules: { 17 | userAgent: "*", 18 | disallow: "/", 19 | }, 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | 3 | const nextConfig = { 4 | async headers() { 5 | const headers = []; 6 | 7 | if (process.env.APP_ENV !== "production") { 8 | headers.push({ 9 | headers: [ 10 | { 11 | key: "X-Robots-Tag", 12 | value: "noindex", 13 | }, 14 | ], 15 | 16 | source: "/:path*", 17 | }); 18 | } 19 | 20 | return headers; 21 | }, 22 | }; 23 | 24 | export default nextConfig; 25 | -------------------------------------------------------------------------------- /lint-staged.config.js: -------------------------------------------------------------------------------- 1 | // lint-staged.config.js 2 | module.exports = { 3 | // Type check TypeScript files 4 | "**/*.(ts|tsx)": () => "pnpm tsc --noEmit", 5 | 6 | // Lint then format TypeScript and JavaScript files 7 | "**/*.(ts|tsx|js)": (filenames) => [ 8 | `pnpm eslint --fix ${filenames.join(" ")}`, 9 | `pnpm prettier --write ${filenames.join(" ")}`, 10 | ], 11 | 12 | // Format MarkDown and JSON 13 | "**/*.(md|json)": (filenames) => 14 | `pnpm prettier --write ${filenames.join(" ")}`, 15 | }; 16 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig, globalIgnores } from "eslint/config"; 2 | import nextVitals from "eslint-config-next/core-web-vitals"; 3 | import nextTs from "eslint-config-next/typescript"; 4 | 5 | const eslintConfig = defineConfig([ 6 | ...nextVitals, 7 | ...nextTs, 8 | // Override default ignores of eslint-config-next. 9 | globalIgnores([ 10 | // Default ignores of eslint-config-next: 11 | ".next/**", 12 | "out/**", 13 | "build/**", 14 | "next-env.d.ts", 15 | ]), 16 | ]); 17 | 18 | export default eslintConfig; 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 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | pnpm-debug.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 | 38 | # next-sitemap 39 | public/robots.txt 40 | public/sitemap.xml 41 | public/sitemap-*.xml 42 | -------------------------------------------------------------------------------- /src/ui/header/header-content-container.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { motion } from "framer-motion"; 4 | import Container from "@/components/container"; 5 | import { fadeUpAnimationVariants } from "@/consts"; 6 | 7 | export default function HeaderContentContainer() { 8 | return ( 9 | 10 |
11 | 19 |

Header

20 |
21 |
22 |
23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /src/ui/footer/footer-content-container.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { motion } from "framer-motion"; 4 | import Container from "@/components/container"; 5 | import { currentYear, fadeUpAnimationVariants } from "@/consts"; 6 | 7 | export default function FooterContentContainer() { 8 | return ( 9 | 10 |
11 | 20 | © {currentYear} NextJS Starter 21 | 22 |
23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/ui/home/hero-content-container.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { motion } from "framer-motion"; 4 | import Container from "@/components/container"; 5 | import { fadeUpAnimationVariants, staggerContainerVariants } from "@/consts"; 6 | 7 | export default function HeroContentContainer() { 8 | return ( 9 | 10 | 17 | 21 | NextJS Starter 22 | 23 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "react-jsx", 16 | "incremental": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ], 22 | "baseUrl": ".", 23 | "paths": { 24 | "@/*": ["./src/*"] 25 | } 26 | }, 27 | "include": [ 28 | "next-env.d.ts", 29 | "**/*.ts", 30 | "**/*.tsx", 31 | ".next/types/**/*.ts", 32 | ".next/dev/types/**/*.ts" 33 | ], 34 | "exclude": ["node_modules"] 35 | } 36 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Code Quality Check 2 | 3 | on: 4 | push: 5 | branches: [main, develop] 6 | 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.ref }} 9 | cancel-in-progress: true 10 | 11 | jobs: 12 | code-quality: 13 | name: Code Quality 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v4 19 | 20 | - name: Setup pnpm 21 | uses: pnpm/action-setup@v4 22 | 23 | - name: Setup Node.js 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: 20 27 | cache: "pnpm" 28 | 29 | - name: Install dependencies 30 | run: pnpm install --frozen-lockfile 31 | 32 | - name: Run ESLint 33 | run: pnpm lint:strict 34 | 35 | - name: Run TypeScript check 36 | run: pnpm typecheck 37 | 38 | - name: Check Prettier formatting 39 | run: pnpm format:check 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Ali Piry 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 | 23 | 24 | -------------------------------------------------------------------------------- /src/consts/index.ts: -------------------------------------------------------------------------------- 1 | export const metaTitle = "NextJS Starter"; 2 | export const metaDesc = 3 | "A starter template for Next.js projects with Tailwind CSS and Framer Motion."; 4 | 5 | export const ogSize = { 6 | width: 1600, 7 | height: 800, 8 | }; 9 | 10 | export const ogImageProps = { 11 | ...ogSize, 12 | alt: "NextJS Starter", 13 | contentType: "image/png", 14 | }; 15 | 16 | export const defaultOpenGraph = { 17 | siteName: "NextJS Starter", 18 | images: [ 19 | { 20 | url: "/images/og.png", 21 | ...ogImageProps, 22 | }, 23 | ], 24 | locale: "en_US", 25 | type: "website", 26 | }; 27 | 28 | export const defaultTwitter = { 29 | card: "summary_large_image", 30 | images: [ 31 | { 32 | url: "/images/og.png", 33 | ...ogImageProps, 34 | }, 35 | ], 36 | }; 37 | 38 | export const currentYear = new Date().getFullYear(); 39 | 40 | export const staggerContainerVariants = { 41 | hidden: {}, 42 | show: { 43 | transition: { 44 | staggerChildren: 0.2, 45 | }, 46 | }, 47 | }; 48 | 49 | export const fadeUpAnimationVariants = { 50 | hidden: { opacity: 0, y: -10 }, 51 | show: { opacity: 1, y: 0, transition: { type: "spring" as const } }, 52 | }; 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Next.js + TailwindCSS + TypeScript Starter Template 2 | 3 |
4 |

Made with ❤ by Ali Piry

5 |
6 | 7 | ## Features 8 | 9 | - Next.js 16 10 | - React.js 19 11 | - TypeScript 5 12 | - TailwindCSS 4 13 | - Framer Motion 14 | - Absolute Import and Path Alias — Import components using `@/` prefix 15 | - ESLint 16 | - Prettier 17 | - Husky & Lint Staged 18 | - Site Map — Automatically generate sitemap.xml 19 | 20 | ## How to use 21 | 22 | Execute 23 | [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) 24 | with [pnpm](https://pnpm.io/) to bootstrap the template: 25 | 26 | ```bash 27 | pnpm create next-app --example https://github.com/alipiry/nextjs-ts-tailwind-starter nextjs-ts-tailwind-starter 28 | ``` 29 | 30 | Or clone the repository and install dependencies: 31 | 32 | ```bash 33 | git clone https://github.com/alipiry/nextjs-ts-tailwind-starter.git 34 | cd nextjs-ts-tailwind-starter 35 | pnpm install 36 | cp .env.local.example .env.local 37 | ``` 38 | 39 | ## Getting Started 40 | 41 | Run the development server: 42 | 43 | ```bash 44 | pnpm dev 45 | ``` 46 | 47 | Build for production: 48 | 49 | ```bash 50 | pnpm build 51 | ``` 52 | 53 | Start the production server: 54 | 55 | ```bash 56 | pnpm start 57 | ``` 58 | 59 | ## Deploy your own 60 | 61 | Deploy the template using 62 | [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-template): 63 | 64 | [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/alipiry/nextjs-ts-tailwind-starter) 65 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-ts-tailwind-starter", 3 | "version": "0.0.1", 4 | "description": "A Next.js starter template with TypeScript and Tailwind CSS", 5 | "author": "Ali Piry", 6 | "license": "MIT", 7 | "keywords": [ 8 | "nextjs", 9 | "typescript", 10 | "tailwindcss", 11 | "starter" 12 | ], 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/alipiry/nextjs-ts-tailwind-starter.git" 16 | }, 17 | "bugs": { 18 | "url": "https://github.com/alipiry/nextjs-ts-tailwind-starter/issues" 19 | }, 20 | "homepage": "https://github.com/alipiry/nextjs-ts-tailwind-starter#readme", 21 | "scripts": { 22 | "dev": "next dev", 23 | "build": "next build", 24 | "start": "next start", 25 | "lint": "next lint", 26 | "lint:strict": "eslint --max-warnings=0 src", 27 | "format": "prettier -w .", 28 | "format:check": "prettier -c .", 29 | "typecheck": "tsc --noEmit --incremental false", 30 | "prepare": "husky" 31 | }, 32 | "engines": { 33 | "node": ">=20.0.0", 34 | "pnpm": ">=10.0.0" 35 | }, 36 | "packageManager": "pnpm@10.25.0", 37 | "dependencies": { 38 | "@tailwindcss/postcss": "^4.1.18", 39 | "framer-motion": "^12.23.26", 40 | "next": "^16.0.10", 41 | "react": "^19.2.3", 42 | "react-dom": "^19.2.3", 43 | "sharp": "^0.34.5" 44 | }, 45 | "devDependencies": { 46 | "@tailwindcss/typography": "^0.5.19", 47 | "@types/node": "^25.0.1", 48 | "@types/react": "^19.2.7", 49 | "@types/react-dom": "^19.2.3", 50 | "clsx": "^2.1.1", 51 | "eslint": "^9.39.2", 52 | "eslint-config-next": "^16.0.10", 53 | "eslint-config-prettier": "^10.1.8", 54 | "husky": "^9.1.7", 55 | "lint-staged": "^16.2.7", 56 | "postcss": "^8.5.6", 57 | "prettier": "^3.7.4", 58 | "prettier-plugin-tailwindcss": "^0.7.2", 59 | "tailwind-merge": "^3.4.0", 60 | "tailwindcss": "^4.1.18", 61 | "typescript": "^5.9.3" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | import { Metadata } from "next"; 3 | import Header from "@/ui/header/header"; 4 | import Footer from "@/ui/footer/footer"; 5 | import { 6 | defaultOpenGraph, 7 | defaultTwitter, 8 | metaDesc, 9 | metaTitle, 10 | } from "@/consts"; 11 | import { cn, montserratFont } from "@/lib/utils"; 12 | import { APP_ENV, SITE_URL } from "@/config"; 13 | import "@/app/globals.css"; 14 | 15 | export const metadata: Metadata = { 16 | title: { 17 | template: `%s | ${metaTitle}`, 18 | default: metaTitle, 19 | }, 20 | description: metaDesc, 21 | keywords: ["Nextjs", "Starter", "App Router"], 22 | formatDetection: { 23 | telephone: false, 24 | }, 25 | metadataBase: new URL(SITE_URL), 26 | openGraph: { 27 | ...defaultOpenGraph, 28 | title: metaTitle, 29 | description: metaDesc, 30 | url: SITE_URL, 31 | }, 32 | twitter: { 33 | ...defaultTwitter, 34 | title: metaTitle, 35 | description: metaDesc, 36 | }, 37 | robots: { 38 | index: APP_ENV === "production", 39 | follow: APP_ENV === "production", 40 | "max-image-preview": "large", 41 | "max-video-preview": -1, 42 | "max-snippet": -1, 43 | googleBot: { 44 | index: APP_ENV === "production", 45 | follow: APP_ENV === "production", 46 | "max-image-preview": "large", 47 | "max-video-preview": -1, 48 | "max-snippet": -1, 49 | }, 50 | }, 51 | }; 52 | 53 | export const viewport = { 54 | width: "device-width", 55 | initialScale: 1, 56 | themeColor: "#ffffff", 57 | }; 58 | 59 | interface RootLayoutProps { 60 | children: ReactNode; 61 | } 62 | 63 | export default function RootLayout({ children }: Readonly) { 64 | return ( 65 | 66 | 73 |
74 | {children} 75 |