├── .env ├── .husky ├── commit-msg └── prepare-commit-msg ├── src ├── styles │ └── tailwind.css ├── vite-env.d.ts ├── common │ ├── utils.ts │ ├── README.md │ ├── types.ts │ └── i18n.ts ├── reset.d.ts ├── assets │ ├── locales │ │ ├── en │ │ │ └── translations.json │ │ └── es │ │ │ └── translations.json │ └── README.md ├── hooks │ └── README.md ├── store │ └── README.md ├── components │ ├── charts │ │ ├── bar │ │ │ └── README.md │ │ ├── pie │ │ │ └── README.md │ │ ├── line │ │ │ └── README.md │ │ └── README.md │ ├── ui │ │ └── README.md │ ├── forms │ │ └── README.md │ ├── utils │ │ └── development-tools │ │ │ ├── ReactHookFormDevelopmentTools.tsx │ │ │ ├── TanStackRouterDevelopmentTools.tsx │ │ │ └── TanStackTableDevelopmentTools.tsx │ ├── README.md │ └── layout │ │ └── README.md ├── routes │ ├── __root.ts │ └── index.ts ├── i18next.d.ts ├── features │ └── README.md ├── environment.d.ts ├── pages │ └── Home.tsx ├── main.tsx ├── App.tsx └── routeTree.gen.ts ├── commitlint.config.cjs ├── public └── vite-react-boilerplate.png ├── Dockerfile ├── .dockerignore ├── tailwind.config.js ├── tsconfig.node.json ├── index.html ├── vitest.setup.ts ├── .vscode ├── extensions.json └── launch.json ├── .storybook ├── main.ts └── preview.ts ├── .gitignore ├── prettier.config.js ├── e2e └── example.spec.ts ├── tsconfig.json ├── vite.config.ts ├── LICENSE ├── playwright.config.ts ├── package.json ├── eslint.config.js └── README.md /.env: -------------------------------------------------------------------------------- 1 | VITE_APP_ENVIRONMENT="development" -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | pnpm run commitlint ${1} 2 | -------------------------------------------------------------------------------- /src/styles/tailwind.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | -------------------------------------------------------------------------------- /.husky/prepare-commit-msg: -------------------------------------------------------------------------------- 1 | pnpm run commitizen # commitizen 2 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /commitlint.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { extends: ["@commitlint/config-conventional"] }; 2 | -------------------------------------------------------------------------------- /src/common/utils.ts: -------------------------------------------------------------------------------- 1 | export const isProduction = import.meta.env.MODE === "production"; 2 | -------------------------------------------------------------------------------- /src/reset.d.ts: -------------------------------------------------------------------------------- 1 | // Do not add any other lines of code to this file! 2 | import "@total-typescript/ts-reset"; 3 | -------------------------------------------------------------------------------- /src/assets/locales/en/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "home": { 3 | "greeting": "Hello, world!" 4 | } 5 | } -------------------------------------------------------------------------------- /src/assets/locales/es/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "home": { 3 | "greeting": "¡Hola, Mundo!" 4 | } 5 | } -------------------------------------------------------------------------------- /src/hooks/README.md: -------------------------------------------------------------------------------- 1 | # src/hooks 2 | 3 | Any React Hooks that might be used in across the application should be placed here. 4 | -------------------------------------------------------------------------------- /src/store/README.md: -------------------------------------------------------------------------------- 1 | # src/store 2 | 3 | The store directory should contain files relating to global data stores (zustand). 4 | -------------------------------------------------------------------------------- /src/components/charts/bar/README.md: -------------------------------------------------------------------------------- 1 | # src/components/charts/bar 2 | 3 | Bar chart components should be placed in this directory. 4 | -------------------------------------------------------------------------------- /src/components/charts/pie/README.md: -------------------------------------------------------------------------------- 1 | # src/components/charts/pie 2 | 3 | Pie chart components should be placed in this directory. 4 | -------------------------------------------------------------------------------- /src/routes/__root.ts: -------------------------------------------------------------------------------- 1 | import { createRootRoute } from "@tanstack/react-router"; 2 | 3 | export const Route = createRootRoute(); 4 | -------------------------------------------------------------------------------- /src/components/charts/line/README.md: -------------------------------------------------------------------------------- 1 | # src/components/charts/line 2 | 3 | Line chart components should be placed in this directory. 4 | -------------------------------------------------------------------------------- /public/vite-react-boilerplate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RicardoValdovinos/vite-react-boilerplate/HEAD/public/vite-react-boilerplate.png -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:stable-alpine 2 | 3 | WORKDIR /app 4 | 5 | COPY . . 6 | 7 | RUN cp -r /app/dist/* /usr/share/nginx/html 8 | 9 | EXPOSE 80 -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .vscode 3 | .dockerignore 4 | .gitignore 5 | .env 6 | config 7 | build 8 | node_modules 9 | docker-compose.yaml 10 | Dockerfile 11 | README.md -------------------------------------------------------------------------------- /src/routes/index.ts: -------------------------------------------------------------------------------- 1 | import { createFileRoute } from "@tanstack/react-router"; 2 | import { Home } from "../pages/Home"; 3 | 4 | export const Route = createFileRoute("/")({ 5 | component: Home, 6 | }); 7 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | }; 9 | -------------------------------------------------------------------------------- /src/i18next.d.ts: -------------------------------------------------------------------------------- 1 | import type {resources, defaultNS } from "./common/i18n"; 2 | 3 | declare module "i18next" { 4 | interface CustomTypeOptions { 5 | defaultNS: typeof defaultNS; 6 | resources: typeof resources["en"]; 7 | } 8 | } -------------------------------------------------------------------------------- /src/components/ui/README.md: -------------------------------------------------------------------------------- 1 | # src/components/ui 2 | 3 | Components relating to ui should be placed in this directory. 4 | 5 | Example: 6 | 7 | - ui/Button/ 8 | - Button.tsx 9 | - Button.stories.tsx 10 | - Button.test.tsx 11 | - index.ts 12 | -------------------------------------------------------------------------------- /src/common/README.md: -------------------------------------------------------------------------------- 1 | # src/common 2 | 3 | The common directory (also called lib) contains files common to the entire application. 4 | 5 | Currently, the directory contains a `types.ts` file with 2 exported types. One for function components and another for heroicons. 6 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /src/components/charts/README.md: -------------------------------------------------------------------------------- 1 | # src/components/charts 2 | 3 | Chart components with uses across multiple pages/components should be placed in this directory. 4 | 5 | Example: 6 | 7 | - components/charts/bar - bar charts here 8 | - components/charts/line - line charts here 9 | - components/charts/pie - pie charts here 10 | -------------------------------------------------------------------------------- /src/common/types.ts: -------------------------------------------------------------------------------- 1 | export type FunctionComponent = React.ReactElement | null; 2 | 3 | type HeroIconSVGProps = React.PropsWithoutRef> & 4 | React.RefAttributes; 5 | type IconProps = HeroIconSVGProps & { 6 | title?: string; 7 | titleId?: string; 8 | }; 9 | export type Heroicon = React.FC; 10 | -------------------------------------------------------------------------------- /src/components/forms/README.md: -------------------------------------------------------------------------------- 1 | # src/components/forms 2 | 3 | Components relating to forms should be placed in this directory. 4 | 5 | Each component should be placed in its own folder with an index.ts file to handle exports. 6 | 7 | Example: 8 | 9 | - forms/Input 10 | - Input.tsx 11 | - Input.stories.tsx 12 | - Input.test.tsx 13 | - index.ts 14 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Vite React Boilerplate 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/components/utils/development-tools/ReactHookFormDevelopmentTools.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { isProduction } from "../../../common/utils"; 3 | 4 | export const ReactHookFormDevelopmentTools = isProduction 5 | ? (): null => null 6 | : React.lazy(() => 7 | import("@hookform/devtools").then((result) => ({ 8 | default: result.DevTool, 9 | })) 10 | ); 11 | -------------------------------------------------------------------------------- /src/components/utils/development-tools/TanStackRouterDevelopmentTools.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { isProduction } from "../../../common/utils"; 3 | 4 | export const TanStackRouterDevelopmentTools = isProduction 5 | ? (): null => null 6 | : React.lazy(() => 7 | import("@tanstack/router-devtools").then((result) => ({ 8 | default: result.TanStackRouterDevtools, 9 | })) 10 | ); 11 | -------------------------------------------------------------------------------- /src/components/utils/development-tools/TanStackTableDevelopmentTools.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { isProduction } from "../../../common/utils"; 3 | 4 | export const TanStackTableDevelopmentTools = isProduction 5 | ? (): null => null 6 | : React.lazy(() => 7 | import("@tanstack/react-table-devtools").then((result) => ({ 8 | default: result.ReactTableDevtools, 9 | })) 10 | ); 11 | -------------------------------------------------------------------------------- /vitest.setup.ts: -------------------------------------------------------------------------------- 1 | import { expect, afterEach } from "vitest"; 2 | import { cleanup } from "@testing-library/react"; 3 | import matchers from "@testing-library/jest-dom/matchers"; 4 | 5 | // extends Vitest's expect method with methods from react-testing-library 6 | expect.extend(matchers); 7 | 8 | // runs a cleanup after each test case (e.g. clearing jsdom) 9 | afterEach(() => { 10 | cleanup(); 11 | }); 12 | -------------------------------------------------------------------------------- /src/features/README.md: -------------------------------------------------------------------------------- 1 | # src/features 2 | 3 | The features directory should contain individual features organized by directories. Each directory should mimic the root src directory as needed. 4 | 5 | Example: 6 | 7 | - src/features/some-feature 8 | 9 | - some-feature/assets 10 | - some-feature/components 11 | - some-feature/hooks 12 | 13 | - src/features/another-feature 14 | 15 | - another-feature/components 16 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "steoates.autoimport", 4 | "formulahendry.auto-rename-tag", 5 | "ms-azuretools.vscode-docker", 6 | "mikestead.dotenv", 7 | "dsznajder.es7-react-js-snippets", 8 | "dbaeumer.vscode-eslint", 9 | "esbenp.prettier-vscode", 10 | "ms-playwright.playwright", 11 | "yoavbls.pretty-ts-errors", 12 | "bradlc.vscode-tailwindcss", 13 | "wayou.vscode-todo-highlight" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /src/components/README.md: -------------------------------------------------------------------------------- 1 | # src/components 2 | 3 | The components directory should contain components that may be used across the application. 4 | 5 | As a general rule, each component should be placed in its own directory with an accompanying story, unit test, and index file (to handle exports). 6 | 7 | Example: 8 | 9 | - src/components/ui/Example/ 10 | - Example.tsx 11 | - Example.stories.tsx 12 | - Example.test.tsx 13 | - index.ts 14 | -------------------------------------------------------------------------------- /src/environment.d.ts: -------------------------------------------------------------------------------- 1 | // TypeScript IntelliSense for VITE_ .env variables. 2 | // VITE_ prefixed variables are exposed to the client while non-VITE_ variables aren't 3 | // https://vitejs.dev/guide/env-and-mode.html 4 | 5 | /// 6 | 7 | interface ImportMetaEnv { 8 | readonly VITE_APP_TITLE: string; 9 | // more env variables... 10 | } 11 | 12 | interface ImportMeta { 13 | readonly env: ImportMetaEnv; 14 | } 15 | -------------------------------------------------------------------------------- /.storybook/main.ts: -------------------------------------------------------------------------------- 1 | import type { StorybookConfig } from "@storybook/react-vite"; 2 | const config: StorybookConfig = { 3 | stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"], 4 | addons: [ 5 | "@storybook/addon-links", 6 | "@storybook/addon-themes", 7 | "@storybook/addon-docs", 8 | ], 9 | framework: { 10 | name: "@storybook/react-vite", 11 | options: {}, 12 | }, 13 | docs: { 14 | defaultName: "Documentation", 15 | docsMode: true, 16 | }, 17 | }; 18 | export default config; 19 | -------------------------------------------------------------------------------- /src/assets/README.md: -------------------------------------------------------------------------------- 1 | # src/assets 2 | 3 | Any assets placed in src/assets will be bundled with application. 4 | 5 | If you intend to reference static assets from your application code then they must be placed in this directory (or anywhere inside of the src directory). 6 | 7 | Consider adding additional subdirectories to aid in organization. 8 | 9 | Example: 10 | 11 | - src/assets/images 12 | - src/assets/images/webp 13 | - src/assets/images/png 14 | - src/assets/icons 15 | - src/assets/fonts 16 | - src/assets/locales 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | !.vscode/launch.json 19 | .idea 20 | .DS_Store 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | /test-results/ 27 | /playwright-report/ 28 | /playwright/.cache/ 29 | 30 | # storybook 31 | storybook-static 32 | 33 | # tests 34 | /coverage/ 35 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "program": "${file}" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | const prettierConfig = { 2 | printWidth: 80, 3 | tabWidth: 2, 4 | useTabs: true, 5 | semi: true, 6 | singleQuote: false, 7 | quoteProps: "as-needed", 8 | jsxSingleQuote: false, 9 | trailingComma: "es5", 10 | bracketSpacing: true, 11 | bracketSameLine: false, 12 | arrowParens: "always", 13 | requirePragma: false, 14 | insertPragma: false, 15 | proseWrap: "preserve", 16 | htmlWhitespaceSensitivity: "css", 17 | endOfLine: "lf", 18 | embeddedLanguageFormatting: "auto", 19 | singleAttributePerLine: false 20 | } 21 | 22 | export default prettierConfig 23 | -------------------------------------------------------------------------------- /e2e/example.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from '@playwright/test'; 2 | 3 | test('has title', async ({ page }) => { 4 | await page.goto('https://playwright.dev/'); 5 | 6 | // Expect a title "to contain" a substring. 7 | await expect(page).toHaveTitle(/Playwright/); 8 | }); 9 | 10 | test('get started link', async ({ page }) => { 11 | await page.goto('https://playwright.dev/'); 12 | 13 | // Click the get started link. 14 | await page.getByRole('link', { name: 'Get started' }).click(); 15 | 16 | // Expects the URL to contain intro. 17 | await expect(page).toHaveURL(/.*intro/); 18 | }); 19 | -------------------------------------------------------------------------------- /src/components/layout/README.md: -------------------------------------------------------------------------------- 1 | # src/components/layout 2 | 3 | Components relating to layouts should be placed in this directory. 4 | 5 | Consider using additional subdirectories with the same format (component + story + unit test) for subcomponents to aid in organization. 6 | 7 | Example: 8 | 9 | - layout/Sidebar/ 10 | - SidebarItem/ 11 | - SidebarItem.tsx 12 | - SidebarItem.stories.tsx 13 | - SidebarItem.test.tsx 14 | - SidebarSection/ 15 | - SidebarSection.tsx 16 | - SidebarSection.stories.tsx 17 | - SidebarSection.test.tsx 18 | - Sidebar/ 19 | - Sidebar.tsx 20 | - Sidebar.stories.tsx 21 | - Sidebar.test.tsx 22 | - index.ts 23 | -------------------------------------------------------------------------------- /src/pages/Home.tsx: -------------------------------------------------------------------------------- 1 | import { useTranslation } from "react-i18next"; 2 | import type { FunctionComponent } from "../common/types"; 3 | 4 | export const Home = (): FunctionComponent => { 5 | const { t, i18n } = useTranslation(); 6 | 7 | const onTranslateButtonClick = async (): Promise => { 8 | if (i18n.resolvedLanguage === "en") { 9 | await i18n.changeLanguage("es"); 10 | } else { 11 | await i18n.changeLanguage("en"); 12 | } 13 | }; 14 | 15 | return ( 16 |
17 |

{t("home.greeting")}

18 | 25 |
26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | "esModuleInterop": true, 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "react-jsx", 17 | 18 | /* Linting */ 19 | "strict": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "noFallthroughCasesInSwitch": true, 23 | "noImplicitOverride": true, 24 | "noImplicitReturns": true, 25 | "noPropertyAccessFromIndexSignature": true, 26 | "noUncheckedIndexedAccess": true 27 | }, 28 | "include": ["src", "e2e"], 29 | "references": [{ "path": "./tsconfig.node.json" }] 30 | } 31 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { TanStackRouterVite } from "@tanstack/router-plugin/vite"; 2 | import react from "@vitejs/plugin-react-swc"; 3 | import path from "node:path"; 4 | import { normalizePath } from "vite"; 5 | import { viteStaticCopy } from "vite-plugin-static-copy"; 6 | import { defineConfig } from "vitest/config"; 7 | import tailwindcss from "@tailwindcss/vite"; 8 | 9 | // https://vitejs.dev/config/ 10 | export default defineConfig({ 11 | plugins: [ 12 | react(), 13 | tailwindcss(), 14 | TanStackRouterVite(), 15 | viteStaticCopy({ 16 | targets: [ 17 | { 18 | src: normalizePath(path.resolve("./src/assets/locales")), 19 | dest: normalizePath(path.resolve("./dist")), 20 | }, 21 | ], 22 | }), 23 | ], 24 | server: { 25 | host: true, 26 | strictPort: true, 27 | }, 28 | test: { 29 | environment: "jsdom", 30 | setupFiles: ["./vitest.setup.ts"], 31 | css: true, 32 | }, 33 | }); 34 | -------------------------------------------------------------------------------- /.storybook/preview.ts: -------------------------------------------------------------------------------- 1 | import { withThemeByClassName } from "@storybook/addon-themes"; 2 | import type { Preview } from "@storybook/react-vite"; 3 | 4 | /* TODO: update import to your tailwind styles file. If you're using Angular, inject this through your angular.json config instead */ 5 | import "../src/styles/tailwind.css"; 6 | 7 | const preview: Preview = { 8 | parameters: { 9 | actions: { argTypesRegex: "^on[A-Z].*" }, 10 | controls: { 11 | matchers: { 12 | color: /(background|color)$/i, 13 | date: /Date$/, 14 | }, 15 | }, 16 | }, 17 | 18 | decorators: [ 19 | // Adds theme switching support. 20 | // NOTE: requires setting "darkMode" to "class" in your tailwind config 21 | withThemeByClassName({ 22 | themes: { 23 | light: "light", 24 | dark: "dark", 25 | }, 26 | defaultTheme: "light", 27 | }), 28 | ], 29 | 30 | tags: ["autodocs"], 31 | }; 32 | 33 | export default preview; 34 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import { createRouter } from "@tanstack/react-router"; 2 | import React from "react"; 3 | import ReactDOM from "react-dom/client"; 4 | import App from "./App.tsx"; 5 | import { routeTree } from "./routeTree.gen.ts"; 6 | import "./styles/tailwind.css"; 7 | import "./common/i18n"; 8 | 9 | const router = createRouter({ routeTree }); 10 | 11 | export type TanstackRouter = typeof router; 12 | 13 | declare module "@tanstack/react-router" { 14 | interface Register { 15 | // This infers the type of our router and registers it across your entire project 16 | router: TanstackRouter; 17 | } 18 | } 19 | 20 | const rootElement = document.querySelector("#root") as Element; 21 | if (!rootElement.innerHTML) { 22 | const root = ReactDOM.createRoot(rootElement); 23 | root.render( 24 | 25 | 26 | 27 | 28 | 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 2 | import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; 3 | import { RouterProvider } from "@tanstack/react-router"; 4 | import type { FunctionComponent } from "./common/types"; 5 | import type { TanstackRouter } from "./main"; 6 | import { TanStackRouterDevelopmentTools } from "./components/utils/development-tools/TanStackRouterDevelopmentTools"; 7 | 8 | const queryClient = new QueryClient(); 9 | 10 | type AppProps = { router: TanstackRouter }; 11 | 12 | const App = ({ router }: AppProps): FunctionComponent => { 13 | return ( 14 | 15 | 16 | 21 | 22 | 23 | ); 24 | }; 25 | 26 | export default App; 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 RicardoValdovinos 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/common/i18n.ts: -------------------------------------------------------------------------------- 1 | import i18n, { type InitOptions } from "i18next"; 2 | import LanguageDetector from "i18next-browser-languagedetector"; 3 | import Backend, { type HttpBackendOptions } from "i18next-http-backend"; 4 | import { initReactI18next } from "react-i18next"; 5 | import translationEN from "../assets/locales/en/translations.json"; 6 | import translationES from "../assets/locales/es/translations.json"; 7 | import { isProduction } from "./utils"; 8 | 9 | export const defaultNS = "translations"; 10 | export const resources = { 11 | en: { translations: translationEN }, 12 | es: { translations: translationES }, 13 | } as const; 14 | 15 | const i18nOptions: InitOptions = { 16 | defaultNS, 17 | ns: [defaultNS], 18 | debug: !isProduction, 19 | fallbackLng: "en", 20 | interpolation: { 21 | escapeValue: false, // not needed for react as it escapes by default 22 | }, 23 | backend: { 24 | loadPath: isProduction 25 | ? "locales/{{lng}}/translations.json" 26 | : "src/assets/locales/{{lng}}/translations.json", 27 | }, 28 | }; 29 | 30 | void i18n 31 | .use(initReactI18next) 32 | .use(LanguageDetector) 33 | .use(Backend) 34 | .init(i18nOptions); 35 | -------------------------------------------------------------------------------- /src/routeTree.gen.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | // @ts-nocheck 4 | 5 | // noinspection JSUnusedGlobalSymbols 6 | 7 | // This file was automatically generated by TanStack Router. 8 | // You should NOT make any changes in this file as it will be overwritten. 9 | // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. 10 | 11 | import { Route as rootRouteImport } from './routes/__root' 12 | import { Route as IndexRouteImport } from './routes/index' 13 | 14 | const IndexRoute = IndexRouteImport.update({ 15 | id: '/', 16 | path: '/', 17 | getParentRoute: () => rootRouteImport, 18 | } as any) 19 | 20 | export interface FileRoutesByFullPath { 21 | '/': typeof IndexRoute 22 | } 23 | export interface FileRoutesByTo { 24 | '/': typeof IndexRoute 25 | } 26 | export interface FileRoutesById { 27 | __root__: typeof rootRouteImport 28 | '/': typeof IndexRoute 29 | } 30 | export interface FileRouteTypes { 31 | fileRoutesByFullPath: FileRoutesByFullPath 32 | fullPaths: '/' 33 | fileRoutesByTo: FileRoutesByTo 34 | to: '/' 35 | id: '__root__' | '/' 36 | fileRoutesById: FileRoutesById 37 | } 38 | export interface RootRouteChildren { 39 | IndexRoute: typeof IndexRoute 40 | } 41 | 42 | declare module '@tanstack/react-router' { 43 | interface FileRoutesByPath { 44 | '/': { 45 | id: '/' 46 | path: '/' 47 | fullPath: '/' 48 | preLoaderRoute: typeof IndexRouteImport 49 | parentRoute: typeof rootRouteImport 50 | } 51 | } 52 | } 53 | 54 | const rootRouteChildren: RootRouteChildren = { 55 | IndexRoute: IndexRoute, 56 | } 57 | export const routeTree = rootRouteImport 58 | ._addFileChildren(rootRouteChildren) 59 | ._addFileTypes() 60 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, devices } from "@playwright/test"; 2 | 3 | /** 4 | * Read environment variables from file. 5 | * https://github.com/motdotla/dotenv 6 | */ 7 | // require('dotenv').config(); 8 | 9 | /** 10 | * See https://playwright.dev/docs/test-configuration. 11 | */ 12 | export default defineConfig({ 13 | testDir: "./e2e", 14 | /* Run tests in files in parallel */ 15 | fullyParallel: true, 16 | /* Fail the build on CI if you accidentally left test.only in the source code. */ 17 | forbidOnly: !!process.env.CI, 18 | /* Retry on CI only */ 19 | retries: process.env.CI ? 2 : 0, 20 | /* Opt out of parallel tests on CI. */ 21 | workers: process.env.CI ? 1 : undefined, 22 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 23 | reporter: "html", 24 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 25 | use: { 26 | /* Base URL to use in actions like `await page.goto('/')`. */ 27 | // baseURL: 'http://127.0.0.1:3000', 28 | 29 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 30 | trace: "on-first-retry", 31 | }, 32 | 33 | /* Configure projects for major browsers */ 34 | projects: [ 35 | { 36 | name: "chromium", 37 | use: { ...devices["Desktop Chrome"] }, 38 | }, 39 | 40 | { 41 | name: "firefox", 42 | use: { ...devices["Desktop Firefox"] }, 43 | }, 44 | 45 | { 46 | name: "webkit", 47 | use: { ...devices["Desktop Safari"] }, 48 | }, 49 | 50 | /* Test against mobile viewports. */ 51 | // { 52 | // name: 'Mobile Chrome', 53 | // use: { ...devices['Pixel 5'] }, 54 | // }, 55 | // { 56 | // name: 'Mobile Safari', 57 | // use: { ...devices['iPhone 12'] }, 58 | // }, 59 | 60 | /* Test against branded browsers. */ 61 | // { 62 | // name: 'Microsoft Edge', 63 | // use: { ...devices['Desktop Edge'], channel: 'msedge' }, 64 | // }, 65 | // { 66 | // name: 'Google Chrome', 67 | // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, 68 | // }, 69 | ], 70 | 71 | /* Run your local dev server before starting the tests */ 72 | // webServer: { 73 | // command: 'npm run start', 74 | // url: 'http://127.0.0.1:3000', 75 | // reuseExistingServer: !process.env.CI, 76 | // }, 77 | }); 78 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-react-boilerplate", 3 | "author": "Ricardo Valdovinos ", 4 | "description": "A production ready, batteries included starter template for Vite + React projects", 5 | "keywords": [ 6 | "vite", 7 | "react", 8 | "boilerplate", 9 | "starter", 10 | "template" 11 | ], 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/RicardoValdovinos/vite-react-boilerplate" 15 | }, 16 | "license": "MIT", 17 | "version": "1.0.0", 18 | "type": "module", 19 | "scripts": { 20 | "setup": "git init && npx husky init && npx playwright install && shx rm .husky/pre-commit", 21 | "format": "prettier \"src/**/*.{ts,tsx}\" --write", 22 | "lint": "eslint --max-warnings 0", 23 | "lint:fix": "eslint --max-warnings 0 --fix", 24 | "dev": "vite", 25 | "storybook": "storybook dev -p 6006", 26 | "storybook:setup": "node node_modules/@storybook/addon-styling/bin/postinstall.js", 27 | "storybook:build": "storybook build", 28 | "test": "vitest run src/ && playwright test", 29 | "test:unit": "vitest src/", 30 | "test:unit:coverage": "vitest --coverage src/", 31 | "test:e2e": "playwright test", 32 | "test:e2e:report": "playwright show-report", 33 | "build": "tsc && vite build", 34 | "preview": "vite preview", 35 | "commitlint": "commitlint --edit", 36 | "commitizen": "exec < /dev/tty && git cz --hook || true", 37 | "prepare": "husky" 38 | }, 39 | "dependencies": { 40 | "@hookform/resolvers": "^5.2.2", 41 | "@nivo/bar": "^0.99.0", 42 | "@nivo/core": "^0.99.0", 43 | "@nivo/line": "^0.99.0", 44 | "@nivo/pie": "^0.99.0", 45 | "@storybook/addon-docs": "^10.1.4", 46 | "@tanstack/react-query": "^5.90.12", 47 | "@tanstack/react-router": "^1.139.14", 48 | "@tanstack/react-table": "^8.21.3", 49 | "dayjs": "^1.11.19", 50 | "i18next": "^25.7.1", 51 | "i18next-browser-languagedetector": "^8.2.0", 52 | "i18next-http-backend": "^3.0.2", 53 | "react": "^19.2.1", 54 | "react-dom": "^19.2.1", 55 | "react-hook-form": "^7.68.0", 56 | "react-i18next": "^16.4.0", 57 | "zod": "^4.1.13", 58 | "zustand": "^5.0.9" 59 | }, 60 | "devDependencies": { 61 | "@commitlint/cli": "^20.2.0", 62 | "@commitlint/config-conventional": "^20.2.0", 63 | "@eslint/compat": "^2.0.0", 64 | "@eslint/js": "^9.39.1", 65 | "@faker-js/faker": "^10.1.0", 66 | "@headlessui/react": "^2.2.9", 67 | "@heroicons/react": "^2.2.0", 68 | "@hookform/devtools": "^4.4.0", 69 | "@playwright/test": "^1.57.0", 70 | "@storybook/addon-links": "^10.1.4", 71 | "@storybook/addon-themes": "^10.1.4", 72 | "@storybook/react-vite": "^10.1.4", 73 | "@tailwindcss/vite": "^4.1.17", 74 | "@tanstack/react-query-devtools": "^5.91.1", 75 | "@tanstack/react-table-devtools": "^8.21.3", 76 | "@tanstack/router-devtools": "^1.139.15", 77 | "@tanstack/router-plugin": "^1.139.14", 78 | "@testing-library/jest-dom": "^6.9.1", 79 | "@testing-library/react": "^16.3.0", 80 | "@testing-library/user-event": "^14.6.1", 81 | "@total-typescript/ts-reset": "^0.6.1", 82 | "@types/node": "^24.10.1", 83 | "@types/react": "^19.2.7", 84 | "@types/react-dom": "^19.2.3", 85 | "@typescript-eslint/eslint-plugin": "^8.48.1", 86 | "@typescript-eslint/parser": "^8.48.1", 87 | "@vitejs/plugin-react-swc": "^4.2.2", 88 | "@vitest/coverage-v8": "4.0.15", 89 | "autoprefixer": "^10.4.22", 90 | "commitizen": "^4.3.1", 91 | "cz-conventional-changelog": "^3.3.0", 92 | "eslint": "^9.39.1", 93 | "eslint-config-prettier": "^10.1.8", 94 | "eslint-plugin-import": "^2.32.0", 95 | "eslint-plugin-jsx-a11y": "^6.10.2", 96 | "eslint-plugin-react": "^7.37.5", 97 | "eslint-plugin-react-hooks": "^7.0.1", 98 | "eslint-plugin-react-refresh": "^0.4.24", 99 | "eslint-plugin-storybook": "^10.1.4", 100 | "eslint-plugin-unicorn": "^62.0.0", 101 | "globals": "^16.5.0", 102 | "husky": "^9.1.7", 103 | "jsdom": "^27.2.0", 104 | "prettier": "^3.7.4", 105 | "prop-types": "^15.8.1", 106 | "shx": "^0.4.0", 107 | "storybook": "^10.1.4", 108 | "tailwindcss": "^4.1.17", 109 | "typescript": "^5.9.3", 110 | "typescript-eslint": "^8.48.1", 111 | "vite": "7.2.6", 112 | "vite-plugin-static-copy": "^3.1.4", 113 | "vitest": "4.0.15" 114 | }, 115 | "config": { 116 | "commitizen": { 117 | "path": "./node_modules/cz-conventional-changelog" 118 | } 119 | } 120 | } -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import { fixupPluginRules } from '@eslint/compat'; 2 | import eslintJS from "@eslint/js" 3 | import tsParser from '@typescript-eslint/parser'; 4 | //import eslintPluginStorybook from "eslint-plugin-storybook" // does not support eslint v9 5 | import eslintConfigPrettier from "eslint-config-prettier" 6 | import eslintPluginImport from 'eslint-plugin-import' 7 | import jsxA11yPlugin from 'eslint-plugin-jsx-a11y' 8 | import eslintPluginReact from "eslint-plugin-react" 9 | import eslintPluginReactHooks from "eslint-plugin-react-hooks" 10 | import eslintPluginReactRefresh from "eslint-plugin-react-refresh" 11 | import eslintPluginUnicorn from "eslint-plugin-unicorn" 12 | import globals from "globals" 13 | import typescriptEslint from 'typescript-eslint'; 14 | 15 | const patchedReactHooksPlugin = fixupPluginRules(eslintPluginReactHooks) 16 | const patchedImportPlugin = fixupPluginRules(eslintPluginImport) 17 | 18 | const baseESLintConfig = { 19 | name: "eslint", 20 | extends: [ 21 | eslintJS.configs.recommended, 22 | ], 23 | rules: { 24 | "no-await-in-loop": "error", 25 | "no-constant-binary-expression": "error", 26 | "no-duplicate-imports": "error", 27 | "no-new-native-nonconstructor": "error", 28 | "no-promise-executor-return": "error", 29 | "no-self-compare": "error", 30 | "no-template-curly-in-string": "error", 31 | "no-unmodified-loop-condition": "error", 32 | "no-unreachable-loop": "error", 33 | "no-unused-private-class-members": "error", 34 | "no-use-before-define": "error", 35 | "require-atomic-updates": "error", 36 | "camelcase": "error", 37 | } 38 | } 39 | 40 | const typescriptConfig = { 41 | name: "typescript", 42 | extends: [ 43 | ...typescriptEslint.configs.recommendedTypeChecked, 44 | ], 45 | languageOptions: { 46 | parser: tsParser, 47 | parserOptions: { 48 | ecmaFeatures: { modules: true }, 49 | ecmaVersion: "latest", 50 | project: "./tsconfig.json", 51 | }, 52 | globals: { 53 | ...globals.builtin, 54 | ...globals.browser, 55 | ...globals.es2025 56 | }, 57 | }, 58 | linterOptions: { 59 | reportUnusedDisableDirectives: "error" 60 | }, 61 | plugins: { 62 | import: patchedImportPlugin 63 | }, 64 | rules: { 65 | "@typescript-eslint/adjacent-overload-signatures": "error", 66 | "@typescript-eslint/array-type": ["error", { "default": "generic" }], 67 | "@typescript-eslint/consistent-type-exports": "error", 68 | "@typescript-eslint/consistent-type-imports": "error", 69 | "@typescript-eslint/explicit-function-return-type": "error", 70 | "@typescript-eslint/explicit-member-accessibility": "error", 71 | "@typescript-eslint/explicit-module-boundary-types": "error", 72 | "@typescript-eslint/no-confusing-void-expression": "error", 73 | "@typescript-eslint/no-import-type-side-effects": "error", 74 | "@typescript-eslint/no-require-imports": "error", 75 | "@typescript-eslint/no-unused-vars": "error", 76 | "@typescript-eslint/no-useless-empty-export": "error", 77 | "@typescript-eslint/prefer-enum-initializers": "error", 78 | "@typescript-eslint/prefer-readonly": "error", 79 | "no-return-await": "off", 80 | "@typescript-eslint/return-await": "error", 81 | "@typescript-eslint/no-misused-promises": [ 82 | "error", 83 | { 84 | "checksVoidReturn": { 85 | "attributes": false 86 | } 87 | } 88 | ] 89 | }, 90 | settings: { 91 | 'import/resolver': { 92 | typescript: { 93 | alwaysTryTypes: true, 94 | project: './tsconfig.json' 95 | } 96 | } 97 | } 98 | } 99 | 100 | const reactConfig = { 101 | name: "react", 102 | extends: [ 103 | eslintPluginReact.configs.flat["jsx-runtime"], 104 | ], 105 | plugins: { 106 | "react-hooks": patchedReactHooksPlugin, 107 | "react-refresh": eslintPluginReactRefresh, 108 | }, 109 | rules: { 110 | "import/no-anonymous-default-export": "error", 111 | "react/jsx-boolean-value": "error", 112 | "react/jsx-filename-extension": [ 113 | 2, 114 | { extensions: ['.js', '.jsx', '.ts', '.tsx'] } 115 | ], 116 | "react/jsx-no-target-blank": "off", 117 | "react/jsx-max-props-per-line": "off", 118 | "react/jsx-sort-props": [ 119 | "error", 120 | { 121 | callbacksLast: true, 122 | shorthandFirst: true, 123 | reservedFirst: true, 124 | multiline: "last", 125 | }, 126 | ], 127 | "react/no-unknown-property": "off", 128 | "react/prop-types": "off", 129 | "react/react-in-jsx-scope": "off", 130 | "react-hooks/exhaustive-deps": "error", 131 | ...patchedReactHooksPlugin.configs.recommended.rules, 132 | "react-refresh/only-export-components": [ 133 | "warn", 134 | { "allowConstantExport": true } 135 | ], 136 | }, 137 | } 138 | 139 | const jsxA11yConfig = { 140 | name: "jsxA11y", 141 | ...jsxA11yPlugin.flatConfigs.recommended, 142 | plugins: { 143 | "jsx-a11y": jsxA11yPlugin, 144 | }, 145 | rules: { 146 | "jsx-a11y/alt-text": [ 147 | "error", 148 | { elements: ["img"], img: ["Image"] }, 149 | ], 150 | "jsx-a11y/aria-props": "error", 151 | "jsx-a11y/aria-proptypes": "error", 152 | "jsx-a11y/aria-unsupported-elements": "error", 153 | "jsx-a11y/role-has-required-aria-props": "error", 154 | "jsx-a11y/role-supports-aria-props": "error", 155 | } 156 | } 157 | 158 | const unicornConfig = { 159 | name: "unicorn", 160 | plugins: { 161 | unicorn: eslintPluginUnicorn, 162 | }, 163 | rules: { 164 | "unicorn/custom-error-definition": "error", 165 | "unicorn/empty-brace-spaces": "error", 166 | "unicorn/no-array-for-each": "off", 167 | "unicorn/no-array-reduce": "off", 168 | "unicorn/no-console-spaces": "error", 169 | "unicorn/no-null": "off", 170 | "unicorn/filename-case": "off", 171 | "unicorn/prevent-abbreviations": [ 172 | "error", 173 | { 174 | "replacements": { 175 | "db": false, 176 | "arg": false, 177 | "args": false, 178 | "env": false, 179 | "fn": false, 180 | "func": { 181 | "fn": true, 182 | "function": false 183 | }, 184 | "prop": false, 185 | "props": false, 186 | "ref": false, 187 | "refs": false 188 | }, 189 | "ignore": ["semVer", "SemVer"] 190 | } 191 | ] 192 | } 193 | } 194 | 195 | const eslintConfig = typescriptEslint.config( 196 | baseESLintConfig, 197 | typescriptConfig, 198 | eslintConfigPrettier, 199 | reactConfig, 200 | jsxA11yConfig, 201 | unicornConfig 202 | ) 203 | 204 | eslintConfig.map((config) => { 205 | config.files = ["src/**/*.ts", "src/**/*.tsx"] 206 | }) 207 | 208 | export default eslintConfig 209 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vite React Boilerplate 2 | 3 | ![](/public/vite-react-boilerplate.png) 4 | 5 | Everything you need to kick off your next Vite + React web app! 6 | 7 | ## Table of Contents 8 | 9 | - [Overview](#overview) 10 | - [Requirements](#requirements) 11 | - [Getting Started](#getting-started) 12 | - [Important Note](#important-note) 13 | - [Testing](#testing) 14 | - [Preparing for Deployment](#preparing-for-deployment) 15 | - [DevTools](#devtools) 16 | - [Installed Packages](#installed-packages) 17 | 18 | ## Overview 19 | 20 | Built with type safety, scalability, and developer experience in mind. A batteries included Vite + React template. 21 | 22 | - [pnpm](https://pnpm.io) - A strict and efficient alternative to npm with up to 3x faster performance 23 | - [TypeScript](https://www.typescriptlang.org) - A typed superset of JavaScript designed with large scale applications in mind 24 | - [ESLint](https://eslint.org) - Static code analysis to help find problems within a codebase 25 | - [Prettier](https://prettier.io) - An opinionated code formatter 26 | - [Vite](https://vitejs.dev) - Feature rich and highly optimized frontend tooling with TypeScript support out of the box 27 | - [React](https://react.dev) - A modern front-end JavaScript library for building user interfaces based on components 28 | - [Tailwind CSS](https://tailwindcss.com) - A utility-first CSS framework packed with classes to build any web design imaginable 29 | - [Storybook](https://storybook.js.org) - A frontend workshop for building UI components and pages in isolation 30 | - [TanStack Router](https://tanstack.com/router/v1) - Fully typesafe, modern and scalable routing for React applications 31 | - [TanStack Query](https://tanstack.com/query/latest) - Declarative, always-up-to-date auto-managed queries and mutations 32 | - [TanStack Table](https://tanstack.com/table/v8) - Headless UI for building powerful tables & datagrids 33 | - [Zustand](https://zustand-demo.pmnd.rs) - An unopinionated, small, fast and scalable bearbones state-management solution 34 | - [React Hook Form](https://react-hook-form.com) - Performant, flexible and extensible forms with easy-to-use validation 35 | - [Zod](https://zod.dev) - TypeScript-first schema validation with static type inference 36 | - [React Testing Library](https://testing-library.com) - A very light-weight, best practice first, solution for testing React components 37 | - [Vitest](https://vitest.dev) - A blazing fast unit test framework powered by Vite 38 | - [Playwright](https://playwright.dev) - Enables reliable end-to-end testing for modern web apps 39 | - [Nivo](https://nivo.rocks) - A rich set of data visualization components, built on top of D3 and React 40 | - [react-i18next](https://react.i18next.com/) - A powerful internationalization framework for React/React Native based on i18next 41 | - [Faker](https://fakerjs.dev/) - Generate massive amounts of fake (but realistic) data for testing and development 42 | - [Dayjs](https://day.js.org/en/) - A minimalist JavaScript library that parses, validates, manipulates, and displays dates and times for modern browsers 43 | - [Husky](https://github.com/typicode/husky#readme) + [Commitizen](https://github.com/commitizen/cz-cli#readme) + [Commitlint](https://github.com/conventional-changelog/commitlint#readme) - Git hooks and commit linting to ensure use of descriptive and practical commit messages 44 | - [ts-reset](https://github.com/total-typescript/ts-reset#readme) - Improvements for TypeScripts built-in typings for use in applications 45 | - [Docker](https://www.docker.com) - Containerization tool for deploying your vite-react-boilerplate app 46 | 47 | A more detailed list of the included packages can be found in the [Installed Packages](#installed-packages) section. Packages not shown above include Devtools, ui helper libraries, and eslint plugins/configs. 48 | 49 | ## Requirements 50 | 51 | - [NodeJS 18+](https://nodejs.org/en) 52 | - [pnpm](https://pnpm.io) (or equivalent) 53 | 54 | If you'd like to use the included Dockerfile then [Docker](https://www.docker.com) is required as well: 55 | 56 | ## Getting Started 57 | 58 | Getting started is a simple as cloning the repository 59 | 60 | ``` 61 | git clone git@github.com:RicardoValdovinos/vite-react-boilerplate.git 62 | ``` 63 | 64 | Changing into the new directory 65 | 66 | ``` 67 | cd vite-react-boilerplate 68 | ``` 69 | 70 | Removing the .git folder (and any additional files, folders or dependencies you may not need) 71 | 72 | ``` 73 | rm -rf .git 74 | ``` 75 | 76 | Installing dependencies 77 | 78 | ``` 79 | pnpm install 80 | ``` 81 | 82 | And running the setup script (initializes git repository and husky and installs playwright) 83 | 84 | ``` 85 | pnpm run setup 86 | ``` 87 | 88 | Congrats! You're ready to starting working on that new project! 89 | 90 | If you'd rather run the commands above in one go, check out the command below: 91 | 92 | ``` 93 | git clone git@github.com:RicardoValdovinos/vite-react-boilerplate.git &&\ 94 | cd vite-react-boilerplate &&\ 95 | rm -rf .git &&\ 96 | pnpm install &&\ 97 | pnpm run setup 98 | ``` 99 | 100 | **Note**: This project comes with two git hooks added by [husky](https://typicode.github.io/husky/). A prepare-commit-msg hook to run the [Commitizen](https://github.com/commitizen/cz-cli#readme) cli for those nice commit messages and a commit-msg hook to run [Commitlint](https://commitlint.js.org/#/) on the message itself. Commitlint will ensure the commit message follows the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) (it will if you used commitizen). 101 | 102 | If you wish to remove any hooks, simply delete the corresponding file in the .husky directory. 103 | 104 | ## Important Note 105 | 106 | 1. This boilerplate project does not include a demo. At most, a few utilities (types, devtools, initial home page routes) are included. There is no glue to get in your way when trying to modify the template. 107 | 108 | 2. Due to empty directories not being included in git commits, placeholder README files have been added to these empty directories. These README files contain simple descriptions about how the different directories in the accompanying folder structure may be used. As an example check out the [recommended component organizational structure](src/components/README.md) as well as the [recommended folder structure](src/features/README.md). 109 | 110 | 3. [Faker](https://fakerjs.dev/) is included to encourage more isolated testing and allow for rapid development of demos and MVPs. However, please make note that, [due to a bug](https://github.com/faker-js/faker/issues/1791), importing Faker from the main package (without a locale) will result in the entire Faker lib being imported causing bundle sizes to increase up to 2+ MB. Instead prefer [localized imports](https://fakerjs.dev/guide/localization.html#individual-localized-packages) as shown below. 111 | 112 | ``` 113 | // import { faker } from '@faker-js/faker'; 114 | import { faker } from '@faker-js/faker/locale/en'; // prefer localization when possible 115 | ``` 116 | 117 | The imported lib will instead be around 600 KB. Nonetheless, Faker should **NOT** be used in production and instead be limited to testing and demos. 118 | 119 | 4. Starting May 2025, this project and its dependencies are set to be updated once a month or as issues/PR's come in. 120 | 121 | ## Testing 122 | 123 | Unit testing is handled by React Testing Library and Vitest while End-to-End (E2E) Testing is conducted by Playwright. 124 | 125 | If you'd like to run all tests, Unit and E2E alike, execute the following command: 126 | 127 | ``` 128 | pnpm run test 129 | ``` 130 | 131 | ### Unit Testing 132 | 133 | When running unit test scripts, it is assumed that unit tests will be colocated with the source files. Take a look at the placeholder README file in `src/components` for [an example](src/components/README.md). 134 | 135 | If you'd like to execute unit tests specifically, the below command will execute vitest: 136 | 137 | ``` 138 | pnpm run test:unit 139 | ``` 140 | 141 | If instead you are interested in coverage reporting, run: 142 | 143 | ``` 144 | pnpm run test:unit:coverage 145 | ``` 146 | 147 | All unit tests run in watch mode by default. If you'd like to disable watch mode, change the package.json test scripts with the following 148 | 149 | before: 150 | 151 | ``` 152 | "scripts": { 153 | "test:unit": "vitest src/", 154 | "test:unit:coverage": "vitest --coverage src/" 155 | } 156 | ``` 157 | 158 | After: 159 | 160 | ``` 161 | "scripts": { 162 | "test:unit": "vitest run src/", 163 | "test:unit:coverage": "vitest run --coverage src/" 164 | } 165 | ``` 166 | 167 | **Note**: Faker is included to provide mock data. See the [Important Note](#important-note) section for crucial details regarding this package. Specifically, point 3. 168 | 169 | ### End-to-End (E2E) Testing 170 | 171 | Running E2E tests use a similar syntax to running unit tests: 172 | 173 | ``` 174 | pnpm run test:e2e 175 | ``` 176 | 177 | If you wish to see the reports, run: 178 | 179 | ``` 180 | pnpm run test:e2e:report 181 | ``` 182 | 183 | ## Preparing for Deployment 184 | 185 | Instructions are provided for deploying both with and without Docker. Both options still require a platform to host the application. 186 | 187 | ### Without Docker 188 | 189 | Deploying is as easy as running 190 | 191 | ``` 192 | pnpm run build 193 | ``` 194 | 195 | and pointing your web server to the generated `index.html` file found at `dist/index.html` 196 | 197 | ### With Docker 198 | 199 | A Dockerfile with an [NGINX](https://www.nginx.com) base image is also provided for quick and easy deployments. Simply execute the following commands: 200 | 201 | 1. `pnpm run build` 202 | 2. `docker build . -t ` 203 | - Example: `docker build . -t todo-app` 204 | 3. `docker run -p :80 ` 205 | - Example: `docker run -p 8080:80 todo-app` 206 | 207 | ### Continuous Integration 208 | 209 | Due to the vast array of tools, opinions, requirements and preferences a CI template is not included in this project. 210 | 211 | ## Devtools 212 | 213 | This project includes a set of Devtools. Some are additional package dependencies whereas others come built-in to the packages themselves. 214 | 215 | ### Devtool dependencies: 216 | 217 | - [@tanstack/react-query-devtools](https://tanstack.com/query/v4/docs/react/devtools) - Dedicated dev tools to help visualize the inner workings of React Query 218 | - [@tanstack/router-devtools](https://tanstack.com/router/v1/docs/devtools) - Dedicated dev tools to help visualize the inner workings of TanStack Router 219 | - [@tanstack/react-table-devtools](https://www.npmjs.com/package/@tanstack/react-table-devtools) - Dedicated dev tools to help visualize the inner workings of TanStack Table 220 | - [@hookform/DevTools](https://react-hook-form.com/dev-tools) - React Hook Form Devtools to help debug forms with validation 221 | 222 | A set of utility components are provided in `src/components/utils/development-tools/`. These [wrapper components](https://tanstack.com/router/v1/docs/devtools#only-importing-and-using-devtools-in-developmentgit) check whether the application is running in development or production mode and render the component or null respectively. In other words, you can confidently use them during development without having to worry about them showing up for end users in production. 223 | 224 | **TanStack Query Devtools** are ready to go out of the box. The development vs. production rendering mechanism is built into the devtools. If you do wish to [render the devtools in production](https://tanstack.com/query/latest/docs/react/devtools) you can freely do so by following the TanStack Query Devtools documentation. The devtools component can be found in `src/App.tsx`. 225 | 226 | When running the application in development mode you can find the TanStack Query Devtools icon in the bottom left corner of the page sporting the [React Query Logo](https://img.stackshare.io/service/25599/default_c6db7125f2c663e452ba211df91b2ced3bb7f0ff.png). 227 | 228 | **TanStack Router Devtools**, however, utilizes its respective utility component in this project. The initial setup has been taken care of but if you wish to modify or remove the component, have a look in `src/App.tsx`. 229 | 230 | The TanStack Router Devtools icon can be found in the bottom right corner of the page denoted by the vertically stacked "TANSTACK ROUTER" logo. 231 | 232 | The above components, along with their imports, are commented out to start. 233 | 234 | **TanStack Table Devtools** Documentation is, at the time of writing this, non-existent. Having said that, usage is similar to the other TanStack devtools. A utility component restricting the devtools to development builds has been provided. The difference in comparison to the other TanStack devtools is the lack of floating mode. Instead, the Devtools are rendered as a component within the actual TanStack Table you define. An additional caveat being that the DevTools component (built-in and provided utility alike) require a table prop from the `useReactTable()` hook. In other words, if you have multiple tables, each table must have its own Devtools component. Check the simplified code below. 235 | 236 | ``` 237 | function Table(): FunctionComponent { 238 | /* some code */ 239 | 240 | const table = useReactTable({ 241 | data, 242 | columns, 243 | getCoreRowModel: getCoreRowModel(), 244 | }); 245 | 246 | return ( 247 | <> 248 | {/* table code */} 249 | 250 | 251 | ) 252 | } 253 | ``` 254 | 255 | **React Hook Form DevTools** icon can be recognized in the top right corner of the page by the pink React Hook Form clipboard logo. A utility component has also provided. Like the TanStack Table Devtools component above, a prop must be passed from a specific hook. In this case, it is the control prop from the `useForm()` hook. Similar to TanStack Table, use of React Hook Form DevTools requires the component be added to each unique form. More information can be found in the [React Hook Form DevTools documentation](https://react-hook-form.com/dev-tools). 256 | 257 | To reiterate, if you wish to restrict the Devtools to development builds use the provided components found at `src/components/utils/development-tools` instead of the built-in components from their respective modules. 258 | 259 | ### Built-in Devtools: 260 | 261 | - Zustand 262 | 263 | **Zustand** provides a built-in [devtools middleware](https://github.com/pmndrs/zustand#redux-devtools) for use with [Redux DevTools](https://github.com/reduxjs/redux-devtools#redux-devtools). 264 | 265 | ## Installed Packages 266 | 267 | A simplified list can be found in the [Overview](#overview) section. 268 | 269 | ### Base 270 | 271 | - [TypeScript](https://www.typescriptlang.org) 272 | - [Vite](https://vitejs.dev) 273 | - [React](https://react.dev) 274 | 275 | ### Routing 276 | 277 | - [TanStack Router](https://tanstack.com/router/v1) 278 | 279 | ### Linting & Formatting 280 | 281 | - [ESLint](https://eslint.org) 282 | - [typescript-eslint](https://typescript-eslint.io) 283 | - [eslint-config-prettier](https://github.com/prettier/eslint-config-prettier#readme) 284 | - [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react#readme) 285 | - [eslint-plugin-react-hooks](https://www.npmjs.com/package/eslint-plugin-react-hooks) 286 | - [eslint-plugin-react-refresh](https://github.com/ArnaudBarre/eslint-plugin-react-refresh) 287 | - [eslint-plugin-unicorn](https://github.com/sindresorhus/eslint-plugin-unicorn#readme) 288 | - [eslint-plugin-storybook](https://github.com/storybookjs/eslint-plugin-storybook#readme) 289 | - [Prettier](https://prettier.io) 290 | 291 | ### State Management 292 | 293 | - [TanStack Query (React Query)](https://tanstack.com/query/latest) 294 | - [Zustand](https://zustand-demo.pmnd.rs) 295 | 296 | ### UI 297 | 298 | - [Tailwind CSS](https://tailwindcss.com) 299 | - [HeadlessUI](https://headlessui.com) 300 | - [heroicons](https://heroicons.com) 301 | - [TanStack Table](https://tanstack.com/table/v8) 302 | - [Storybook](https://storybook.js.org) 303 | 304 | ### Forms 305 | 306 | - [React Hook Form](https://react-hook-form.com) 307 | - [Zod](https://zod.dev) 308 | 309 | ### Data Visualization 310 | 311 | - [Nivo](https://nivo.rocks) 312 | - [Line](https://nivo.rocks/line/) 313 | - [Bar](https://nivo.rocks/bar/) 314 | - [Pie](https://nivo.rocks/pie/) 315 | 316 | ### Testing 317 | 318 | - [Vitest](https://vitest.dev) 319 | - [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/) 320 | - [Playwright](https://playwright.dev) 321 | 322 | ### Development Tools 323 | 324 | - [TanStack Query Devtools](https://tanstack.com/query/latest/docs/react/devtools?from=reactQueryV3&original=https%3A%2F%2Ftanstack.com%2Fquery%2Fv3%2Fdocs%2Fdevtools) 325 | - [TanStack Router Devtools](https://tanstack.com/router/v1/docs/devtools) 326 | - [TanStack Table Devtools](https://www.npmjs.com/package/@tanstack/react-table-devtools) 327 | - [React Hook Form Devtools](https://react-hook-form.com/dev-tools) 328 | 329 | ### Git 330 | 331 | - [Husky](https://github.com/typicode/husky#readme) 332 | - [Commitizen](https://github.com/commitizen/cz-cli#readme) 333 | - [Commitlint](https://github.com/conventional-changelog/commitlint#readme) 334 | 335 | ### Other 336 | 337 | - [i18next-browser-languageDetector](https://github.com/i18next/i18next-browser-languageDetector) 338 | - [i18next](https://www.i18next.com/) 339 | - [react-i18next](https://react.i18next.com/) 340 | - [ts-reset](https://github.com/total-typescript/ts-reset#readme) 341 | - [Faker](https://fakerjs.dev/) 342 | - [Dayjs](https://day.js.org/en/) 343 | --------------------------------------------------------------------------------