├── .eslintignore
├── .vscode
├── settings.json
└── tsreact.code-snippets
├── css
└── tailwind.css
├── commitlint.config.js
├── public
├── favicon
│ ├── favicon.ico
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── apple-touch-icon.png
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ └── site.webmanifest
├── robots.txt
├── sitemap.xml
└── sitemap-0.xml
├── .env.example
├── .husky
├── pre-commit
└── commit-msg
├── postcss.config.js
├── .prettierignore
├── renovate.json
├── next.config.js
├── src
├── lib
│ ├── __test__
│ │ └── utils.test.ts
│ └── utils.ts
├── constant
│ ├── app-config.ts
│ └── theme.ts
├── layouts
│ └── container.tsx
└── components
│ ├── icons
│ └── index.tsx
│ ├── meta
│ └── index.tsx
│ └── theme-selector
│ └── index.tsx
├── next-sitemap.config.js
├── pages
├── _app.tsx
├── api
│ └── hello.ts
├── index.tsx
└── _document.tsx
├── next-env.d.ts
├── vitest.config.ts
├── prettier.config.js
├── .lintstagedrc.js
├── .gitignore
├── .github
├── FUNDING.yml
└── workflows
│ └── nextjs_bundle_analysis.yml
├── tsconfig.json
├── tailwind.config.js
├── .eslintrc.js
├── README.md
└── package.json
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "typescript.tsdk": "node_modules/typescript/lib"
3 | }
4 |
--------------------------------------------------------------------------------
/css/tailwind.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = { extends: ["@commitlint/config-conventional"] };
2 |
--------------------------------------------------------------------------------
/public/favicon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fiqryq/kit/HEAD/public/favicon/favicon.ico
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | # place your url for generate sitemap after build
2 | process.env.NEXT_PUBLIC_SITE_URL=
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | pnpm lint-staged
5 |
6 |
--------------------------------------------------------------------------------
/public/favicon/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fiqryq/kit/HEAD/public/favicon/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicon/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fiqryq/kit/HEAD/public/favicon/favicon-32x32.png
--------------------------------------------------------------------------------
/public/favicon/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fiqryq/kit/HEAD/public/favicon/apple-touch-icon.png
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | npx --no -- commitlint --edit ${1}
5 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/favicon/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fiqryq/kit/HEAD/public/favicon/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/favicon/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fiqryq/kit/HEAD/public/favicon/android-chrome-512x512.png
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .next
2 | .cache
3 | package-lock.json
4 | public
5 | node_modules
6 | next-env.d.ts
7 | next.config.ts
8 | yarn.lock
9 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": [
4 | "config:base"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | swcMinify: true,
5 | };
6 |
7 | module.exports = nextConfig;
8 |
--------------------------------------------------------------------------------
/src/lib/__test__/utils.test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest';
2 | import { cn } from '../utils';
3 |
4 | test(cn('bg-white-500', 'bg-red-500'), () => {
5 | expect('bg-red-500');
6 | });
7 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # *
2 | User-agent: *
3 | Allow: /
4 |
5 | # Host
6 | Host: https://nextboilerplate.fiqry.dev/
7 |
8 | # Sitemaps
9 | Sitemap: https://nextboilerplate.fiqry.dev/sitemap.xml
10 |
--------------------------------------------------------------------------------
/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { ClassValue, clsx } from 'clsx';
2 | import { twMerge } from 'tailwind-merge';
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
--------------------------------------------------------------------------------
/next-sitemap.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next-sitemap').IConfig} */
2 | module.exports = {
3 | siteUrl: process.env.NEXT_PUBLIC_SITE_URL,
4 | generateRobotsTxt: true,
5 | changefreq: 'daily',
6 | priority: 0.7
7 | };
8 |
--------------------------------------------------------------------------------
/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import type { AppProps } from 'next/app';
2 | import 'css/tailwind.css';
3 |
4 | function MyApp({ Component, pageProps }: AppProps) {
5 | return ;
6 | }
7 |
8 | export default MyApp;
9 |
--------------------------------------------------------------------------------
/public/sitemap.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | https://nextboilerplate.fiqry.dev/sitemap-0.xml
4 |
--------------------------------------------------------------------------------
/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vitest/config';
2 | import react from '@vitejs/plugin-react';
3 |
4 | export default defineConfig({
5 | plugins: [react()],
6 | test: {
7 | environment: 'jsdom'
8 | }
9 | });
10 |
--------------------------------------------------------------------------------
/src/constant/app-config.ts:
--------------------------------------------------------------------------------
1 | export const AppConfig = {
2 | site_name: 'next-boilerplate',
3 | title: 'next-boilerplate',
4 | description:
5 | 'simple nextjs boilerplate with tailwindcss + typescript ⚡️ for fast development.',
6 | locale: 'en'
7 | };
8 |
--------------------------------------------------------------------------------
/public/favicon/site.webmanifest:
--------------------------------------------------------------------------------
1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
--------------------------------------------------------------------------------
/src/constant/theme.ts:
--------------------------------------------------------------------------------
1 | import { LightIcon, DarkIcon, SystemIcon } from '@/components/icons';
2 |
3 | export const themes = [
4 | { name: 'Light', value: 'light', icon: LightIcon },
5 | { name: 'Dark', value: 'dark', icon: DarkIcon },
6 | { name: 'System', value: 'system', icon: SystemIcon }
7 | ];
8 |
--------------------------------------------------------------------------------
/src/layouts/container.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | interface Props {
4 | children: React.ReactNode;
5 | }
6 |
7 | const Container: React.FC = ({ children }) => {
8 | return {children}
;
9 | };
10 |
11 | export default Container;
12 |
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | singleQuote: true,
3 | trailingComma: 'none',
4 | arrowParens: 'avoid',
5 | proseWrap: 'preserve',
6 | quoteProps: 'as-needed',
7 | bracketSameLine: false,
8 | bracketSpacing: true,
9 | tabWidth: 2,
10 | plugins: [require('prettier-plugin-tailwindcss')]
11 | };
12 |
--------------------------------------------------------------------------------
/pages/api/hello.ts:
--------------------------------------------------------------------------------
1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2 | import type { NextApiRequest, NextApiResponse } from 'next';
3 |
4 | type Data = {
5 | name: string;
6 | };
7 |
8 | export default function handler(
9 | req: NextApiRequest,
10 | res: NextApiResponse
11 | ) {
12 | res.status(200).json({ name: 'John Doe' });
13 | }
14 |
--------------------------------------------------------------------------------
/.lintstagedrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | // Type check TypeScript files
3 | '**/*.(ts|tsx)': () => 'pnpm tsc --noEmit',
4 |
5 | // Lint & Prettify TS and JS files
6 | '**/*.(ts|tsx|js)': filenames => [
7 | 'pnpm test:all',
8 | 'pnpm lint',
9 | `pnpm prettier --write ${filenames.join(' ')}`
10 | ],
11 |
12 | // Prettify only Markdown and JSON files
13 | '**/*.(md|json)': filenames => `pnpm prettier --write ${filenames.join(' ')}`
14 | };
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /.pnp
5 | .pnp.js
6 | /node_modules
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 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: fiqryq
2 | patreon: # Replace with a single Patreon username
3 | open_collective: # Replace with a single Open Collective username
4 | ko_fi: fiqryq
5 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
6 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
7 | liberapay: # Replace with a single Liberapay username
8 | issuehunt: # Replace with a single IssueHunt username
9 | otechie: # Replace with a single Otechie username
10 | custom: https://saweria.co/fiqryq
11 |
--------------------------------------------------------------------------------
/public/sitemap-0.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | https://nextboilerplate.fiqry.dev2022-12-20T01:40:24.664Zdaily0.7
4 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["dom", "dom.iterable", "esnext"],
4 | "allowJs": true,
5 | "skipLibCheck": true,
6 | "strict": false,
7 | "forceConsistentCasingInFileNames": true,
8 | "noEmit": true,
9 | "incremental": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "baseUrl": ".",
17 | "paths": {
18 | "@/*": ["./src/*"],
19 | "@/public/*": ["./public/*"]
20 | }
21 | },
22 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "commitlint.config.js"],
23 | "exclude": ["node_modules"]
24 | }
25 |
--------------------------------------------------------------------------------
/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import { Meta } from '@/components/meta';
2 | import ThemeSelector from '@/components/theme-selector';
3 |
4 | import type { NextPage } from 'next';
5 |
6 | const Home: NextPage = () => {
7 | return (
8 | <>
9 |
13 |
22 | >
23 | );
24 | };
25 |
26 | export default Home;
27 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 | /** @type {import('tailwindcss').Config} */
3 | module.exports = {
4 | content: [
5 | './pages/**/*.{js,ts,jsx,tsx}',
6 | './layouts/**/*.{js,ts,jsx,tsx}',
7 | './src/**/*.{js,ts,jsx,tsx}'
8 | ],
9 | darkMode: 'class',
10 | theme: {
11 | extend: {
12 | aspectRatio: {
13 | auto: 'auto',
14 | square: '1 / 1',
15 | video: '16 / 9',
16 | 1: '1',
17 | 2: '2',
18 | 3: '3',
19 | 4: '4',
20 | 5: '5',
21 | 6: '6',
22 | 7: '7',
23 | 8: '8',
24 | 9: '9',
25 | 10: '10',
26 | 11: '11',
27 | 12: '12',
28 | 13: '13',
29 | 14: '14',
30 | 15: '15',
31 | 16: '16'
32 | }
33 | }
34 | },
35 | corePlugins: {
36 | aspectRatio: false
37 | },
38 | plugins: [
39 | require('@tailwindcss/forms'),
40 | require('@tailwindcss/typography'),
41 | require('@tailwindcss/aspect-ratio'),
42 | require('@tailwindcss/container-queries')
43 | ]
44 | };
45 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | parser: '@typescript-eslint/parser',
4 | env: {
5 | browser: true,
6 | amd: true,
7 | node: true,
8 | es6: true
9 | },
10 | plugins: ['@typescript-eslint', 'prettier'],
11 | extends: [
12 | 'eslint:recommended',
13 | 'plugin:@typescript-eslint/eslint-recommended',
14 | 'plugin:@typescript-eslint/recommended',
15 | 'plugin:jsx-a11y/recommended',
16 | 'prettier',
17 | 'next',
18 | 'next/core-web-vitals'
19 | ],
20 | rules: {
21 | 'react/react-in-jsx-scope': 'off',
22 | 'jsx-a11y/anchor-is-valid': [
23 | 'error',
24 | {
25 | components: ['Link'],
26 | specialLink: ['hrefLeft', 'hrefRight'],
27 | aspects: ['invalidHref', 'preferButton']
28 | }
29 | ],
30 | 'react/prop-types': 0,
31 | 'no-unused-vars': 0,
32 | 'prettier/prettier': 'warn',
33 | 'no-console': 'warn',
34 | 'react/no-unescaped-entities': 'off',
35 | '@typescript-eslint/explicit-module-boundary-types': 'off',
36 | '@typescript-eslint/no-var-requires': 'off',
37 | '@typescript-eslint/ban-ts-comment': 'off',
38 | '@typescript-eslint/no-explicit-any': 'off'
39 | }
40 | };
41 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | [](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Ffiqryq%2Fnext-boilerplate)
4 |
5 | ### Getting Started
6 |
7 | create new project :
8 |
9 | ```
10 | npx create-next-app --example https://github.com/fiqryq/next-boilerplate
11 | ```
12 |
13 | or use this template and clone then run :
14 |
15 | ```bash
16 | # install dependency
17 | yarn install
18 | # run local server development
19 | yarn run dev
20 | ```
21 |
22 | ### Features
23 |
24 | - absolute path import.
25 | - integrate with [Tailwind CSS](https://tailwindcss.com/).
26 | - integrate with all tailwind plugins.
27 | - precommit using husky.
28 | - commitlint.
29 | - lint-staged.
30 | - prettier.
31 | - theme selector.
32 | - auto generate sitemap.
33 | - auto update dependencies using [Renovate](https://github.com/renovatebot/renovate).
34 | - analyzes each PR's impact on your next.js app's bundle size and displays it using a commen using [nextjs bundle analysis](https://github.com/hashicorp/nextjs-bundle-analysis).
35 |
36 | ### 🤝 Contributing
37 |
38 | 1. find [issue](https://github.com/fiqryq/next-boilerplate/issues)
39 | 1. fork this repository
40 | 1. create your branch: `git checkout -b new-feature`
41 | 1. commit your changes: `git commit -m 'feat: add some feature (#issue_number)'`
42 | 1. push to the branch: `git push origin new-feature`.
43 |
44 | **After your pull request is merged**, you can safely delete your branch.
45 |
46 | ---
47 |
48 | Made with ♥ by Fiqry choerudin
49 |
--------------------------------------------------------------------------------
/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import { Head, Html, Main, NextScript } from 'next/document';
2 |
3 | const themeScript = `
4 | let isDarkMode = window.matchMedia('(prefers-color-scheme: dark)')
5 |
6 | function updateTheme(theme) {
7 | theme = theme ?? window.localStorage.theme ?? 'system'
8 |
9 | if (theme === 'dark' || (theme === 'system' && isDarkMode.matches)) {
10 | document.documentElement.classList.add('dark')
11 | } else if (theme === 'light' || (theme === 'system' && !isDarkMode.matches)) {
12 | document.documentElement.classList.remove('dark')
13 | }
14 |
15 | return theme
16 | }
17 |
18 | function updateThemeWithoutTransitions(theme) {
19 | updateTheme(theme)
20 | document.documentElement.classList.add('[&_*]:!transition-none')
21 | window.setTimeout(() => {
22 | document.documentElement.classList.remove('[&_*]:!transition-none')
23 | }, 0)
24 | }
25 |
26 | document.documentElement.setAttribute('data-theme', updateTheme())
27 |
28 | new MutationObserver(([{ oldValue }]) => {
29 | let newValue = document.documentElement.getAttribute('data-theme')
30 | if (newValue !== oldValue) {
31 | try {
32 | window.localStorage.setItem('theme', newValue)
33 | } catch {}
34 | updateThemeWithoutTransitions(newValue)
35 | }
36 | }).observe(document.documentElement, { attributeFilter: ['data-theme'], attributeOldValue: true })
37 |
38 | isDarkMode.addEventListener('change', () => updateThemeWithoutTransitions())
39 | `;
40 |
41 | export default function Document() {
42 | return (
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | );
53 | }
54 |
--------------------------------------------------------------------------------
/src/components/icons/index.tsx:
--------------------------------------------------------------------------------
1 | const DarkIcon = (props: any) => {
2 | return (
3 |
10 | );
11 | };
12 |
13 | const LightIcon = (props: any) => {
14 | return (
15 |
22 | );
23 | };
24 |
25 | const SystemIcon = (props: any) => {
26 | return (
27 |
34 | );
35 | };
36 |
37 | export { DarkIcon, LightIcon, SystemIcon };
38 |
--------------------------------------------------------------------------------
/.vscode/tsreact.code-snippets:
--------------------------------------------------------------------------------
1 | {
2 | "React Functional Components": {
3 | "prefix": "rfc",
4 | "body": [
5 | "",
6 | "interface Props {",
7 | "",
8 | "}",
9 | "const ${1:${TM_FILENAME_BASE}}: React.FC = ({ }) => {",
10 | "\treturn $0
",
11 | "}",
12 | "",
13 | "export default ${1:${TM_FILENAME_BASE}}"
14 | ]
15 | },
16 | "import React": {
17 | "prefix": "ir",
18 | "body": [
19 | "import * as React from 'react';"
20 | ]
21 | },
22 | "React.useState": {
23 | "prefix": "us",
24 | "body": [
25 | "const [${1}, set${1/(^[a-zA-Z])(.*)/${1:/upcase}${2}/}] = React.useState<$3>(${2:initial${1/(^[a-zA-Z])(.*)/${1:/upcase}${2}/}})$0"
26 | ]
27 | },
28 | "React.useEffect": {
29 | "prefix": "ufe",
30 | "body": [
31 | "React.useEffect(() => {",
32 | " $0",
33 | "}, []);"
34 | ]
35 | },
36 | "React.useRef": {
37 | "prefix": "urf",
38 | "body": [
39 | "const ${1:someRef} = React.useRef($0)"
40 | ]
41 | },
42 | "Get Static Props": {
43 | "prefix": "gsp",
44 | "body": [
45 | "export const getStaticProps = async (context: GetStaticPropsContext) => {",
46 | " return {",
47 | " props: {}",
48 | " };",
49 | "}"
50 | ]
51 | },
52 | "Get Static Paths": {
53 | "prefix": "gspa",
54 | "body": [
55 | "export const getStaticPaths: GetStaticPaths = async () => {",
56 | " return {",
57 | " paths: [",
58 | " { params: { $1 }}",
59 | " ],",
60 | " fallback: ",
61 | " };",
62 | "}"
63 | ]
64 | },
65 | "Get Server Side Props": {
66 | "prefix": "gssp",
67 | "body": [
68 | "export const getServerSideProps = async (context: GetServerSidePropsContext) => {",
69 | " return {",
70 | " props: {}",
71 | " };",
72 | "}"
73 | ]
74 | },
75 | "loger": {
76 | "prefix": "log",
77 | "body": [
78 | "console.log($0)"
79 | ]
80 | }
81 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next-tailwind-ts",
3 | "version": "2.3.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "postbuild": "next-sitemap",
9 | "start": "next start",
10 | "lint": "next lint --fix --dir pages --dir lib --dir src",
11 | "prepare": "husky install",
12 | "test:all": "vitest --run --passWithNoTests --coverage",
13 | "test:browser": "vitest --browser"
14 | },
15 | "dependencies": {
16 | "@commitlint/cli": "^17.2.0",
17 | "@headlessui/react": "^1.7.6",
18 | "@next/env": "*",
19 | "@tailwindcss/container-queries": "^0.1.0",
20 | "@typescript-eslint/eslint-plugin": "^5.13.0",
21 | "@typescript-eslint/parser": "^5.0.0",
22 | "axios": "^1.0.0",
23 | "clsx": "^1.2.1",
24 | "next": "^13.1.2",
25 | "next-seo": "^5.15.0",
26 | "next-sitemap": "^3.1.43",
27 | "react": "^18.2.0",
28 | "react-dom": "^18.2.0",
29 | "retry-axios": "^3.0.0",
30 | "tailwind-merge": "^1.8.1"
31 | },
32 | "devDependencies": {
33 | "@vitejs/plugin-react": "^3.0.1",
34 | "@commitlint/config-conventional": "^17.2.0",
35 | "@tailwindcss/aspect-ratio": "^0.4.2",
36 | "@tailwindcss/forms": "^0.5.3",
37 | "@tailwindcss/typography": "^0.5.7",
38 | "@testing-library/react": "^13.4.0",
39 | "@types/node": "18.11.18",
40 | "@types/react": "18.0.27",
41 | "@types/react-dom": "18.0.10",
42 | "@vitest/browser": "^0.28.3",
43 | "@vitest/coverage-c8": "^0.28.3",
44 | "@vitest/ui": "^0.28.2",
45 | "autoprefixer": "^10.4.4",
46 | "eslint": "8.32.0",
47 | "eslint-config-next": "^13.1.2",
48 | "eslint-config-prettier": "^8.5.0",
49 | "eslint-plugin-jsx-a11y": "^6.4.1",
50 | "eslint-plugin-prettier": "^4.2.1",
51 | "husky": "^8.0.0",
52 | "jsdom": "^21.1.0",
53 | "lint-staged": "^13.0.4",
54 | "postcss": "^8.4.12",
55 | "prettier": "^2.8.0",
56 | "prettier-plugin-tailwindcss": "^0.2.0",
57 | "tailwindcss": "^3.2.1",
58 | "typescript": "4.9.4",
59 | "vite": "^4.0.4",
60 | "vitest": "^0.28.2"
61 | },
62 | "nextBundleAnalysis": {
63 | "budget": 358400,
64 | "budgetPercentIncreaseRed": 20,
65 | "showDetails": true
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/components/meta/index.tsx:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import { useRouter } from 'next/router';
3 | import { NextSeo } from 'next-seo';
4 | import { AppConfig } from '@/constant/app-config';
5 |
6 | type IMetaProps = {
7 | title: string;
8 | description: string;
9 | canonical?: string;
10 | };
11 |
12 | const Meta = (props: IMetaProps) => {
13 | const router = useRouter();
14 | return (
15 | <>
16 |
17 |
18 |
23 |
28 |
35 |
42 |
49 |
56 |
61 |
62 |
74 | >
75 | );
76 | };
77 |
78 | export { Meta };
79 |
--------------------------------------------------------------------------------
/src/components/theme-selector/index.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import { Listbox } from '@headlessui/react';
3 | import { LightIcon, DarkIcon } from '../icons';
4 | import { themes } from '@/constant/theme';
5 | import { cn } from '@/lib/utils';
6 | interface Props {
7 | className?: string;
8 | }
9 |
10 | const ThemeSelector: React.FC = ({ className, ...props }) => {
11 | const [selectedTheme, setSelectedTheme] = useState({
12 | name: 'Light',
13 | value: 'light'
14 | });
15 |
16 | useEffect(() => {
17 | if (selectedTheme) {
18 | document.documentElement.setAttribute('data-theme', selectedTheme.value);
19 | } else {
20 | setSelectedTheme(
21 | themes.find(
22 | theme =>
23 | theme.value === document.documentElement.getAttribute('data-theme')
24 | )
25 | );
26 | }
27 | }, [selectedTheme]);
28 |
29 | useEffect(() => {
30 | const handler = () =>
31 | setSelectedTheme(
32 | themes.find(
33 | theme => theme.value === (window.localStorage.theme ?? 'system')
34 | )
35 | );
36 |
37 | window.addEventListener('storage', handler);
38 |
39 | return () => window.removeEventListener('storage', handler);
40 | }, []);
41 |
42 | return (
43 |
50 | Theme
51 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | {themes.map(theme => (
63 |
67 | cn(
68 | 'flex cursor-pointer select-none items-center rounded-[0.625rem] p-1',
69 | {
70 | 'text-sky-500': selected,
71 | 'text-slate-900 dark:text-white': active && !selected,
72 | 'text-slate-700 dark:text-slate-400': !active && !selected,
73 | 'bg-slate-100 dark:bg-slate-900/40': active
74 | }
75 | )
76 | }
77 | >
78 | {({ selected }) => (
79 | <>
80 |
81 |
89 |
90 | {theme.name}
91 | >
92 | )}
93 |
94 | ))}
95 |
96 |
97 | );
98 | };
99 |
100 | export default ThemeSelector;
101 |
--------------------------------------------------------------------------------
/.github/workflows/nextjs_bundle_analysis.yml:
--------------------------------------------------------------------------------
1 | name: 'Next.js Bundle Analysis'
2 |
3 | on:
4 | pull_request:
5 | push:
6 | branches:
7 | - main # change this if your default branch is named differently
8 | workflow_dispatch:
9 |
10 | defaults:
11 | run:
12 | # change this if your nextjs app does not live at the root of the repo
13 | working-directory: ./
14 |
15 | jobs:
16 | analyze:
17 | runs-on: ubuntu-latest
18 | steps:
19 | - uses: actions/checkout@v3
20 |
21 | - name: Set up node
22 | uses: actions/setup-node@v3
23 | with:
24 | node-version: '14.x'
25 |
26 | - name: Install dependencies
27 | uses: bahmutov/npm-install@v1
28 |
29 | - name: Build next.js app
30 | # change this if your site requires a custom build command
31 | run: ./node_modules/.bin/next build
32 |
33 | # Here's the first place where next-bundle-analysis' own script is used
34 | # This step pulls the raw bundle stats for the current bundle
35 | - name: Analyze bundle
36 | run: npx -p nextjs-bundle-analysis report
37 |
38 | - name: Upload bundle
39 | uses: actions/upload-artifact@v3
40 | with:
41 | name: bundle
42 | path: .next/analyze/__bundle_analysis.json
43 |
44 | - name: Download base branch bundle stats
45 | uses: dawidd6/action-download-artifact@v2
46 | continue-on-error: true
47 | if: success() && github.event.number
48 | with:
49 | workflow: nextjs_bundle_analysis.yml
50 | branch: ${{ github.event.pull_request.base.ref }}
51 | path: .next/analyze/base
52 |
53 | # And here's the second place - this runs after we have both the current and
54 | # base branch bundle stats, and will compare them to determine what changed.
55 | # There are two configurable arguments that come from package.json:
56 | #
57 | # - budget: optional, set a budget (bytes) against which size changes are measured
58 | # it's set to 350kb here by default, as informed by the following piece:
59 | # https://infrequently.org/2021/03/the-performance-inequality-gap/
60 | #
61 | # - red-status-percentage: sets the percent size increase where you get a red
62 | # status indicator, defaults to 20%
63 | #
64 | # Either of these arguments can be changed or removed by editing the `nextBundleAnalysis`
65 | # entry in your package.json file.
66 | - name: Compare with base branch bundle
67 | if: success() && github.event.number
68 | run: ls -laR .next/analyze/base && npx -p nextjs-bundle-analysis compare
69 |
70 | - name: Get comment body
71 | id: get-comment-body
72 | if: success() && github.event.number
73 | run: |
74 | body=$(cat .next/analyze/__bundle_analysis_comment.txt)
75 | body="${body//'%'/'%25'}"
76 | body="${body//$'\n'/'%0A'}"
77 | body="${body//$'\r'/'%0D'}"
78 | echo ::set-output name=body::$body
79 |
80 | - name: Find Comment
81 | uses: peter-evans/find-comment@v2
82 | if: success() && github.event.number
83 | id: fc
84 | with:
85 | issue-number: ${{ github.event.number }}
86 | body-includes: ''
87 |
88 | - name: Create Comment
89 | uses: peter-evans/create-or-update-comment@v2.1.0
90 | if: success() && github.event.number && steps.fc.outputs.comment-id == 0
91 | with:
92 | issue-number: ${{ github.event.number }}
93 | body: ${{ steps.get-comment-body.outputs.body }}
94 |
95 | - name: Update Comment
96 | uses: peter-evans/create-or-update-comment@v2.1.0
97 | if: success() && github.event.number && steps.fc.outputs.comment-id != 0
98 | with:
99 | issue-number: ${{ github.event.number }}
100 | body: ${{ steps.get-comment-body.outputs.body }}
101 | comment-id: ${{ steps.fc.outputs.comment-id }}
102 | edit-mode: replace
103 |
--------------------------------------------------------------------------------