├── .nvmrc ├── sandbox.config.json ├── .github ├── FUNDING.yml └── workflows │ ├── codesee-arch-diagram.yml │ ├── code-quality.yml │ ├── pre-commit.yml │ ├── review-dog.yml │ ├── pr-check.yml │ └── codeql-analysis.yml ├── bun.lockb ├── screenshot.png ├── public ├── favicon.ico └── mockServiceWorker.js ├── store ├── index.ts └── counter.ts ├── postcss.config.js ├── logger.ts ├── .vscode └── settings.json ├── components ├── Header │ ├── types.ts │ ├── header.module.css │ └── index.tsx ├── utils.ts ├── theme-provider.tsx ├── Layout │ └── index.tsx ├── Counter │ └── index.tsx ├── theme-switcher.tsx └── ui │ ├── card.tsx │ ├── button.tsx │ └── dropdown-menu.tsx ├── prisma ├── migrations │ ├── migration_lock.toml │ └── 20221223143416_add_next_auth_schema │ │ └── migration.sql └── schema.prisma ├── mocks ├── server.ts ├── browser.ts └── handlers.ts ├── pages ├── ssr.tsx ├── api │ ├── hello.ts │ └── auth │ │ └── [...nextauth].ts ├── _app.tsx ├── contact.tsx └── index.tsx ├── cspell-tool.txt ├── .storybook ├── preview.js └── main.js ├── renovate.json ├── next-env.d.ts ├── tests ├── Button.test.tsx └── Header.test.tsx ├── vitest.config.ts ├── components.json ├── next.config.js ├── cspell.json ├── .env.example ├── stories ├── Header.stories.tsx └── Button.stories.tsx ├── .gitignore ├── SECURITY.md ├── tsconfig.json ├── .pre-commit-config.yaml ├── app └── layout.tsx ├── LICENSE ├── types └── next-auth.d.ts ├── .all-contributorsrc ├── styles ├── Home.module.css └── globals.css ├── package.json ├── README.md └── biome.json /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/* 2 | -------------------------------------------------------------------------------- /sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "template": "next" 3 | } 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [jellydn] 2 | ko_fi: dunghd 3 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellydn/next-app-starter/HEAD/bun.lockb -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellydn/next-app-starter/HEAD/screenshot.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellydn/next-app-starter/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /store/index.ts: -------------------------------------------------------------------------------- 1 | import counterAtom from './counter'; 2 | 3 | export default { 4 | counterAtom, 5 | }; 6 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | '@tailwindcss/postcss': {}, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /logger.ts: -------------------------------------------------------------------------------- 1 | import { consola } from 'consola'; 2 | 3 | const logger = consola.create({}); 4 | 5 | export default logger; 6 | -------------------------------------------------------------------------------- /store/counter.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'jotai'; 2 | 3 | const counterAtom = atom(0); 4 | 5 | export default counterAtom; 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "typescript.enablePromptUseWorkspaceTsdk": true 4 | } -------------------------------------------------------------------------------- /components/Header/types.ts: -------------------------------------------------------------------------------- 1 | export type HeaderProps = { 2 | links: Array<{ 3 | title: string; 4 | url: string; 5 | }>; 6 | }; 7 | -------------------------------------------------------------------------------- /prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "postgresql" -------------------------------------------------------------------------------- /mocks/server.ts: -------------------------------------------------------------------------------- 1 | import { setupServer } from 'msw/node'; 2 | 3 | import handlers from './handlers'; 4 | 5 | export const server = setupServer(...handlers); 6 | 7 | export default server; 8 | -------------------------------------------------------------------------------- /mocks/browser.ts: -------------------------------------------------------------------------------- 1 | import { setupWorker } from 'msw/browser'; 2 | 3 | import handlers from './handlers'; 4 | 5 | export const browser = setupWorker(...handlers); 6 | 7 | export default browser; 8 | -------------------------------------------------------------------------------- /components/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 | -------------------------------------------------------------------------------- /pages/ssr.tsx: -------------------------------------------------------------------------------- 1 | import Index from './index'; 2 | 3 | export default function ServerSideRendering() { 4 | return ; 5 | } 6 | 7 | export function getServerSideProps() { 8 | return { props: { initialState: 1000 } }; 9 | } 10 | -------------------------------------------------------------------------------- /cspell-tool.txt: -------------------------------------------------------------------------------- 1 | adsbygoogle 2 | Nonoctal 3 | Instantiator 4 | consola 5 | typecheck 6 | headlessui 7 | loglevel 8 | biomejs 9 | rustywind 10 | shadcn 11 | jellydn 12 | kofi 13 | buymeacoffee 14 | Huynh 15 | Hoang 16 | Salman 17 | automerge 18 | Bitstream -------------------------------------------------------------------------------- /.storybook/preview.js: -------------------------------------------------------------------------------- 1 | export const parameters = { 2 | actions: { argTypesRegex: '^on[A-Z].*' }, 3 | controls: { 4 | matchers: { 5 | color: /(background|color)$/i, 6 | date: /Date$/, 7 | }, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /components/Header/header.module.css: -------------------------------------------------------------------------------- 1 | .avatar { 2 | border-radius: 2rem; 3 | float: left; 4 | height: 2.8rem; 5 | width: 2.8rem; 6 | background-color: white; 7 | background-size: cover; 8 | background-repeat: no-repeat; 9 | } 10 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base", 4 | "group:allNonMajor", 5 | ":pinAllExceptPeerDependencies" 6 | ], 7 | "lockFileMaintenance": { 8 | "enabled": true 9 | }, 10 | "automerge": true, 11 | "major": { 12 | "automerge": false 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | 6 | // NOTE: This file should not be edited 7 | // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. 8 | -------------------------------------------------------------------------------- /components/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { ThemeProvider as NextThemesProvider } from 'next-themes'; 4 | import * as React from 'react'; 5 | 6 | export function ThemeProvider({ 7 | children, 8 | ...props 9 | }: React.ComponentProps) { 10 | return {children}; 11 | } 12 | -------------------------------------------------------------------------------- /tests/Button.test.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@testing-library/react'; 2 | import { describe, test } from 'vitest'; 3 | import { Button } from '../components/ui/button'; 4 | 5 | /** 6 | * @jest-environment jsdom 7 | */ 8 | describe('Button component', () => { 9 | test('should render button', () => { 10 | render(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import react from '@vitejs/plugin-react'; 3 | import { defineConfig } from 'vitest/config'; 4 | 5 | // @ts-ignore 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | // @ts-ignore 9 | plugins: [react()], 10 | test: { 11 | globals: true, 12 | environment: 'jsdom', 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": false, 5 | "tailwind": { 6 | "config": "tailwind.config.js", 7 | "css": "styles/globals.css", 8 | "baseColor": "neutral", 9 | "cssVariables": true 10 | }, 11 | "aliases": { 12 | "components": "/components", 13 | "utils": "/components/utils" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | import { type NextApiRequest, type NextApiResponse } from 'next'; 2 | import { withValidation } from 'next-validations'; 3 | import { z } from 'zod'; 4 | 5 | const schema = z.object({ 6 | name: z.string().min(5), 7 | }); 8 | 9 | const validate = withValidation({ 10 | schema, 11 | mode: 'query', 12 | }); 13 | 14 | const handler = (req: NextApiRequest, res: NextApiResponse) => { 15 | res.status(200).json(req.query); 16 | }; 17 | 18 | export default validate(handler); 19 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | const { createSecureHeaders } = require('next-secure-headers'); 2 | 3 | /** @type {import('next').NextConfig} */ 4 | module.exports = { 5 | async headers() { 6 | return [{ source: '/(.*)', headers: createSecureHeaders() }]; 7 | }, 8 | images: { 9 | dangerouslyAllowSVG: true, 10 | remotePatterns: [ 11 | { 12 | protocol: 'https', 13 | hostname: 'vercel.com', 14 | }, 15 | ], 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json", 3 | "version": "0.2", 4 | "language": "en", 5 | "globRoot": ".", 6 | "dictionaryDefinitions": [ 7 | { 8 | "name": "cspell-tool", 9 | "path": "./cspell-tool.txt", 10 | "addWords": true 11 | } 12 | ], 13 | "dictionaries": ["cspell-tool"], 14 | "ignorePaths": ["node_modules", "dist", "build", "/cspell-tool.txt"] 15 | } 16 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_API_MOCKING=yes/no 2 | NEXTAUTH_URL=http://localhost:3000 3 | EMAIL_SERVER= 4 | EMAIL_FROM= 5 | DATABASE_URL='postgresql://' 6 | SHADOW_DATABASE_URL='postgresql://' 7 | # Used to encrypt the NextAuth.js JWT, and to hash email verification tokens. This is the default value for the secret option in NextAuth and Middleware. 8 | NEXTAUTH_SECRET=JWT_SECRET_KEY 9 | 10 | # Github NextAuth Credentials 11 | GITHUB_ID= 12 | GITHUB_SECRET= 13 | 14 | 15 | # Google NextAuth Credentials 16 | GOOGLE_CLIENT_ID= 17 | GOOGLE_CLIENT_SECRET= -------------------------------------------------------------------------------- /components/Layout/index.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | import Header from '../Header'; 3 | 4 | function Layout({ children }: { readonly children: ReactNode }) { 5 | return ( 6 |
7 |
15 | {children} 16 |
17 | ); 18 | } 19 | 20 | export default Layout; 21 | -------------------------------------------------------------------------------- /stories/Header.stories.tsx: -------------------------------------------------------------------------------- 1 | import { StoryFn } from '@storybook/react'; 2 | 3 | import Header, { HeaderProps } from '../components/Header'; 4 | 5 | export default { 6 | title: 'Header', 7 | argTypes: { onSignIn: { action: 'clicked' } }, 8 | }; 9 | 10 | const Template: StoryFn = (args) =>
; 11 | 12 | export const ContactLink = Template.bind({}); 13 | 14 | ContactLink.args = { 15 | links: [ 16 | { 17 | title: 'Contact', 18 | url: '/contact', 19 | }, 20 | ], 21 | }; 22 | -------------------------------------------------------------------------------- /tests/Header.test.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@testing-library/react'; 2 | import { describe, test, vi } from 'vitest'; 3 | 4 | import { SessionProvider } from 'next-auth/react'; 5 | import Header from '../components/Header'; 6 | 7 | global.fetch = vi.fn(); 8 | 9 | /** 10 | * @jest-environment jsdom 11 | */ 12 | describe('Header component', () => { 13 | test('should render header', () => { 14 | render( 15 | 16 |
17 | , 18 | ); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /.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 | 27 | # local env files 28 | .env 29 | .env.local 30 | .env.development.local 31 | .env.test.local 32 | .env.production.local 33 | 34 | # vercel 35 | .vercel 36 | 37 | .husky 38 | 39 | .eslintcache 40 | .vscode 41 | .yarn 42 | -------------------------------------------------------------------------------- /.github/workflows/codesee-arch-diagram.yml: -------------------------------------------------------------------------------- 1 | # This workflow was added by CodeSee. Learn more at https://codesee.io/ 2 | # This is v2.0 of this workflow file 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request_target: 8 | types: [opened, synchronize, reopened] 9 | 10 | name: CodeSee 11 | 12 | permissions: read-all 13 | 14 | jobs: 15 | codesee: 16 | runs-on: ubuntu-latest 17 | continue-on-error: true 18 | name: Analyze the repo with CodeSee 19 | steps: 20 | - uses: Codesee-io/codesee-action@v2 21 | with: 22 | codesee-token: ${{ secrets.CODESEE_ARCH_DIAG_API_TOKEN }} 23 | -------------------------------------------------------------------------------- /stories/Button.stories.tsx: -------------------------------------------------------------------------------- 1 | import { StoryFn } from '@storybook/react'; 2 | import { Button, ButtonProps } from '../components/ui/button'; 3 | 4 | export default { 5 | title: 'Button', 6 | argTypes: { onClick: { action: 'clicked' } }, 7 | }; 8 | 9 | const Template: StoryFn = (args) => ( 10 | 11 | ); 12 | 13 | export const Small = Template.bind({}); 14 | 15 | Small.args = { 16 | size: 's', 17 | }; 18 | 19 | export const Medium = Template.bind({}); 20 | 21 | Medium.args = { 22 | size: 'm', 23 | }; 24 | 25 | export const Large = Template.bind({}); 26 | 27 | Large.args = { 28 | size: 'l', 29 | }; 30 | -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | import { mergeConfig } from 'vite'; 2 | 3 | export default { 4 | stories: [ 5 | '../stories/**/*.stories.mdx', 6 | '../stories/**/*.stories.@(js|jsx|ts|tsx)', 7 | ], 8 | addons: ['@storybook/addon-essentials', 'storybook-addon-designs'], 9 | framework: '@storybook/nextjs', 10 | async viteFinal(config) { 11 | // Merge custom configuration into the default config 12 | return mergeConfig(config, { 13 | // Add storybook-specific dependencies to pre-optimization 14 | optimizeDeps: { 15 | include: ['storybook-addon-designs'], 16 | }, 17 | }); 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /components/Counter/index.tsx: -------------------------------------------------------------------------------- 1 | import { useAtom } from 'jotai'; 2 | 3 | import counterAtom from '../../store/counter'; 4 | import { Button } from '../ui/button'; 5 | 6 | function Counter() { 7 | const [count, setCount] = useAtom(counterAtom); 8 | return ( 9 |
10 | 18 | Counter: {count} 19 |
20 | ); 21 | } 22 | 23 | export default Counter; 24 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 5.1.x | :white_check_mark: | 11 | | 5.0.x | :x: | 12 | | 4.0.x | :white_check_mark: | 13 | | < 4.0 | :x: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | Use this section to tell people how to report a vulnerability. 18 | 19 | Tell them where to go, how often they can expect to get an update on a 20 | reported vulnerability, what to expect if the vulnerability is accepted or 21 | declined, etc. 22 | -------------------------------------------------------------------------------- /.github/workflows/code-quality.yml: -------------------------------------------------------------------------------- 1 | name: Code quality 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | quality: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | # Give the default GITHUB_TOKEN write permission to commit and push the 12 | # added or changed files to the repository. 13 | contents: write 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | - name: Setup Biome 18 | uses: biomejs/setup-biome@v2 19 | with: 20 | version: latest 21 | - name: Run Biome 22 | run: biome ci . 23 | continue-on-error: true # Continue even if biome fails 24 | 25 | # Commit all changed files back to the repository 26 | - uses: stefanzweifel/git-auto-commit-action@v5 27 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 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": "preserve", 16 | "incremental": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ] 22 | }, 23 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 24 | "exclude": ["node_modules"] 25 | } 26 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yml: -------------------------------------------------------------------------------- 1 | name: pre-commit 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [main] 7 | 8 | jobs: 9 | pre-commit: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | # Give the default GITHUB_TOKEN write permission to commit and push the 13 | # added or changed files to the repository. 14 | contents: write 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: actions/setup-python@v5 18 | - name: Install pre-commit 19 | run: pip install pre-commit 20 | 21 | - name: Run pre-commit 22 | run: pre-commit run --all-files 23 | continue-on-error: true # Continue even if pre-commit fails 24 | 25 | # Commit all changed files back to the repository 26 | - uses: stefanzweifel/git-auto-commit-action@v5 27 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/jellydn/sort-package-json 3 | rev: "1686d9d2ddfb065c4514c637ee4b9985dfbf01dd" # Use the sha / tag you want to point at 4 | hooks: 5 | - id: sort-package-json 6 | 7 | - repo: https://github.com/pre-commit/mirrors-prettier 8 | rev: "v4.0.0-alpha.8" # Use the sha or tag you want to point at 9 | hooks: 10 | - id: prettier 11 | # Those are not supported by biomejs yet, refer https://biomejs.dev/internals/language-support/ 12 | types_or: [html, css, markdown] 13 | - repo: https://github.com/biomejs/pre-commit 14 | rev: "v2.0.0-beta.1" # Use the sha / tag you want to point at 15 | hooks: 16 | - id: biome-check 17 | exclude: "package.json" 18 | additional_dependencies: ["@biomejs/biome@1.9.4"] 19 | -------------------------------------------------------------------------------- /.github/workflows/review-dog.yml: -------------------------------------------------------------------------------- 1 | name: reviewdog 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | review: 8 | name: Biome 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: read 12 | pull-requests: write 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: mongolyy/reviewdog-action-biome@v1 16 | with: 17 | github_token: ${{ secrets.github_token }} 18 | reporter: github-pr-review 19 | 20 | misspell: 21 | name: Misspell 22 | runs-on: ubuntu-latest 23 | permissions: 24 | contents: read 25 | pull-requests: write 26 | steps: 27 | - uses: actions/checkout@v4 28 | - name: misspell 29 | uses: reviewdog/action-misspell@v1 30 | with: 31 | github_token: ${{ secrets.github_token }} 32 | locale: "US" 33 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import Script from 'next/script'; 2 | import { ReactNode } from 'react'; 3 | 4 | function RootLayout({ 5 | // Layouts must accept a children prop. 6 | // This will be populated with nested layouts or pages 7 | children, 8 | }: { 9 | readonly children: ReactNode; 10 | }) { 11 | return ( 12 | 13 | 14 |