buildfast
v1.0.9
Published

Downloads
226
Readme

BuildFast | AI-Driven Boilerplate for Faster Launch and Scale of Frontend SPAs
BuildFast is a production-ready boilerplate that accelerates the development of modern React applications. Built with TypeScript, Vite, and industry best practices, it allows you to focus on building unique features while the boilerplate handles architecture, configuration, and development tooling.
Table of Contents
- About the Project
- Quick Start
- Directory Organization
- Screaming Architecture
- Testing
- Code Conventions
- Environment Variables
- Available Scripts
- Deployment
- Best Practices
- Troubleshooting
1. About the Project
BuildFast is a production-ready boilerplate designed to accelerate the development of modern frontend applications. It implements a scalable architecture based on the Screaming Architecture pattern, where the project structure clearly reflects business capabilities rather than technical types.
🎯 Key Features
- TypeScript First: Static typing for greater reliability and better DX
- Feature-based Architecture: Self-contained and scalable modules
- Integrated Testing: Vitest + Testing Library configured and ready
- Quality Gates: Husky, ESLint, Prettier, and Commitlint preconfigured
- Auth Ready: Mocked authentication screens ready for UI iteration
- Modern UI: Semantic HTML ready for custom styling
- API Management: React Query for data caching and synchronization
🛠️ Tech Stack
Core
- React 18: UI library with latest features
- TypeScript: Static typing for JavaScript
- Vite: Ultra-fast build tool with HMR
Routing & Navigation
- React Router Dom v6: Declarative routing with data loaders
State & Data
- React Query (TanStack Query): Server state management
- Axios: HTTP client with interceptors
UI
- shadcn/ui: Component source pattern
- Tailwind CSS: Utility-first styling
- Framer Motion: Declarative animations
- Tabler Icons: Consistent icon set
Forms & Validation
- React Hook Form: Efficient form handling
- Yup: Schema validation
Backend
- Stripe: Integrated payment processing
Testing
- Vitest: Vite-compatible testing framework
- Testing Library: User-centric testing
Code Quality
- ESLint: Code linting
- Prettier: Automatic formatting
- Husky: Git hooks for quality
- Commitlint: Commit message validation
Code Generation
2. Quick Start
Prerequisites
- Node.js: >= 18.x
- npm or yarn or pnpm
- Git: For version control
- Stripe account (optional, for payments)
Installation
1. Clone or use the boilerplate
# Option 1: Clone the repository
git clone https://github.com/nappalm/buildfast.git my-project
cd my-project
# Option 2: Use as template on GitHub
# Click "Use this template" on GitHub
# Option 3: Use npx (if published)
npx buildfast create my-project2. Install dependencies
npm install3. Configure environment variables
Copy the example files and configure your credentials:
# The project uses separate files per environment
# .env.development - For local development
# .env.production - For productionFile .env.development:
# Stripe (optional - for payment processing)
VITE_APP_STRIPE_PUBLISHABLE_KEY=pk_test_...
# Regional configuration
locale=enUSNote: All environment variables for the frontend must start with
VITE_APP_to be accessible in the code.
4. Start the development server
npm run devThe application will be available at http://localhost:5173
Basic Project Structure
buildfast/
├── src/
│ ├── app/ # Global configuration (providers, router)
│ ├── features/ # Business modules (auth, home, settings)
│ ├── lib/ # External library configuration
│ └── shared/ # Shared code (components, hooks, utils)
├── public/ # Static assets
└── .husky/ # Git hooks3. Directory Organization
The directory structure follows the principle of organization by domain, not by technical type. This means everything related to a business capability (feature) lives together.
Complete Structure
buildfast/
├── src/
│ ├── app/ # 🎯 Global application configuration
│ │ ├── main.tsx # Entry point - Mounts React to DOM
│ │ ├── providers.tsx # Global providers composition
│ │ └── router.tsx # Main application router
│ │
│ ├── features/ # 🎨 Self-contained domain modules
│ │ ├── auth/ # Authentication feature
│ │ │ ├── components/ # AuthProvider, ProtectedRoute, SignInForm, etc.
│ │ │ ├── hooks/ # useAuth, useAuthenticatedUser
│ │ │ ├── pages/ # SignInPage, SignUpPage
│ │ │ ├── router/ # Auth routes and paths
│ │ │ ├── utils/ # Auth-specific helpers
│ │ │ └── index.ts # ⭐ Public API - Only exports
│ │ │
│ │ └── home/ # Home/landing feature
│ │
│ ├── lib/ # 🔧 Third-party library configuration
│ │ ├── axios/ # Axios configuration and interceptors
│ │ ├── react-query/ # React Query client
│ │ ├── shadcn/ # shadcn/ui components and Tailwind v4 setup
│ │ ├── stripe/ # Stripe configuration
│ │ └── test/ # Testing setup (Vitest)
│ │
│ └── shared/ # 🌐 Generic shared code
│ ├── components/ # Button, Modal, Layout - base components
│ ├── constants/ # Global constants
│ ├── hooks/ # useLocalStorage, useDebounce, etc.
│ ├── services/ # Shared services (non-feature-specific)
│ ├── utils/ # Generic helpers
│ └── index.ts # Shared public API
│
├── public/ # Static assets (favicon, images, etc.)
├── .husky/ # Git hooks
└── scripts/ # Utility scripts (build, deploy, etc.)Anatomy of a Feature
Each feature is a self-contained vertical module that includes everything needed to function:
src/features/products/
├── components/ # 🧩 Feature UI components
│ ├── ProductCard.tsx
│ ├── ProductList.tsx
│ ├── ProductForm.tsx
│ └── ProductFilters.tsx
│
├── hooks/ # 🪝 Custom hooks (orchestration layer)
│ ├── useProducts.ts # Fetching and caching with React Query
│ ├── useProduct.ts # Single product
│ ├── useCreateProduct.ts # Mutation to create
│ └── useUpdateProduct.ts # Mutation to update
│
├── pages/ # 📄 Feature pages/views
│ ├── ProductsListPage.tsx
│ ├── ProductDetailPage.tsx
│ ├── CreateProductPage.tsx
│ └── EditProductPage.tsx
│
├── router/ # 🛤️ Route configuration
│ ├── index.ts # Re-export of paths and router
│ ├── paths.ts # Route constants
│ │ export const PRODUCTS_PATHS = {
│ │ ROOT: '/products',
│ │ DETAIL: '/products/:id',
│ │ CREATE: '/products/new',
│ │ EDIT: '/products/:id/edit',
│ │ }
│ └── router.tsx # React Router configuration
│
├── services/ # 🔌 API calls (async functions)
│ └── products.service.ts
│ - getProducts()
│ - getProduct(id)
│ - createProduct(data)
│ - updateProduct(id, data)
│ - deleteProduct(id)
│
├── utils/ # 🛠️ Feature-specific helpers
│ └── product.utils.ts # formatPrice(), calculateDiscount(), etc.
│
└── index.ts # ⭐ PUBLIC API
export { useProducts, useProduct } from './hooks'
export { productsRouter } from './router'
export { PRODUCTS_PATHS } from './router'
// ❌ DO NOT export: services, internal components, utilssrc/lib/ Directory
Configuration and setup of third-party libraries:
src/lib/
├── axios/
│ └── client.ts # Axios instance + interceptors
│
├── chakra/
│ ├── theme.ts # Theme customization
│ ├── colors.ts # Color palette
│ └── components/ # Component overrides
│
├── react-query/
│ ├── client.ts # Configured QueryClient
│ └── constants.ts # Query keys, stale time, etc.
│
├── stripe/
│ └── client.ts # Stripe configuration
│
└── test/
├── setup.ts # Global test setup
└── utils.tsx # Testing utilities, custom render, etc.src/shared/ Directory
Generic and reusable code that is NOT tied to any specific feature:
src/shared/
├── components/
│ ├── Button/ # Base button component
│ ├── Modal/ # Reusable modal
│ ├── Layout/ # Generic layouts
│ └── ...
│
├── hooks/
│ ├── useLocalStorage.ts # localStorage hook
│ ├── useDebounce.ts # Generic debounce
│ ├── useDisclosure.ts # Open/close state management
│ └── ...
│
├── utils/
│ ├── format.ts # formatDate, formatCurrency
│ ├── validation.ts # Generic validators
│ └── ...
│
└── constants/
├── routes.ts # Global routes
└── config.ts # Global configurationOrganization Rules
✅ DO
- Keep everything related to a feature within its folder
- Export only what's necessary in each feature's
index.ts - Place truly generic code in
shared/ - Use absolute imports with the
@/alias (configured in Vite)
❌ DON'T
- DO NOT import internal files from other features directly
- DO NOT place business logic in
shared/ - DO NOT export services or internal components in the public API
- DO NOT create circular dependencies between features
4. Screaming Architecture
BuildFast implements the Screaming Architecture pattern created by Robert C. Martin (Uncle Bob). This approach makes the project structure "scream" its business purpose rather than the technical frameworks or tools used.
Fundamental Principles
1. 🎯 Organize by Domain, Not by Technical Type
❌ Traditional Approach (by technical type):
src/
├── components/ # All components together
├── hooks/ # All hooks together
├── pages/ # All pages together
└── services/ # All services together✅ BuildFast Approach (by domain):
src/
└── features/
├── products/ # Everything about products
├── auth/ # Everything about authentication
└── orders/ # Everything about ordersWhen you see the features/ folder, you immediately understand WHAT the
application does, not what technologies it's built with.
2. 🏢 Features as Self-Contained Vertical Modules
Each feature is like an independent mini-application with everything it needs:
// ✅ A feature contains everything necessary
src/features/products/
├── components/ # Product-specific UI
├── hooks/ # Product logic
├── services/ # Product API calls
├── pages/ # Product pages
└── router/ # Product routesBenefits:
- Scalability: Add features without affecting others
- Maintainability: Everything related is together
- Testability: Test features in isolation
- Teamwork: Different developers can work on different features without conflicts
3. 🔒 Every Feature Has a Public API (index.ts)
Communication between features or from the application to a feature MUST occur
exclusively through its index.ts file:
// ✅ CORRECT - Import from public API
import { useProducts, PRODUCTS_PATHS } from "@/features/products";
// ❌ INCORRECT - Import internal files directly
import { useProducts } from "@/features/products/hooks/useProducts";
import { ProductCard } from "@/features/products/components/ProductCard";The index.ts acts as a facade or public contract:
// src/features/products/index.ts
// ✅ Expose data hooks
export { useProducts, useProduct } from "./hooks";
// ✅ Expose routes and paths
export { productsRouter } from "./router";
export { PRODUCTS_PATHS } from "./router/paths";
// ✅ Expose "widget" components designed to be shared
export { ProductWidget } from "./components/ProductWidget";
// ❌ DO NOT expose services
// export * from './services';
// ❌ DO NOT expose internal components
// export { ProductCard } from './components/ProductCard';
// ❌ DO NOT expose internal utils
// export * from './utils';4. 📦 The Public API is Minimal and Intentional
By default, only export:
| Export | Example | Reason |
| ---------------------- | ---------------- | --------------------------------------- |
| ✅ Data hooks | useProducts | To consume feature data |
| ✅ Router | productsRouter | To mount routes in app router |
| ✅ Path constants | PRODUCTS_PATHS | For navigation from other parts |
| ✅ Widget components | ProductWidget | Components explicitly designed to share |
| ❌ Services | getProducts | Implementation details (private) |
| ❌ Internal components | ProductCard | Only for internal feature use |
| ❌ Utils | formatPrice | Private helpers |
5. 🔐 Implementation Details are Private
Internal components, service functions, and utilities should NOT be exported:
// ❌ NEVER do this
// src/features/products/index.ts
export { getProducts } from "./services/products.service";
export { ProductCard } from "./components/ProductCard";
export { formatPrice } from "./utils/product.utils";
// ✅ Instead, encapsulate them in hooks
// src/features/products/hooks/useProducts.ts
import { useQuery } from "@tanstack/react-query";
import { getProducts } from "../services/products.service";
export const useProducts = () => {
return useQuery({
queryKey: ["products"],
queryFn: getProducts,
});
};6. 🌐 Shared Code is for Generic Logic
src/shared/ contains truly reusable code that is NOT tied to any business
logic:
// ✅ CORRECT - Generic utilities in shared
// src/shared/utils/format.ts
export const formatCurrency = (amount: number) => {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(amount);
};
// ❌ INCORRECT - Business logic in shared
// src/shared/utils/product.ts
export const calculateProductDiscount = (product: Product) => {
// This logic is product-specific, shouldn't be in shared
};7. ➡️ Dependencies are Primarily Unidirectional
┌─────────────┐
│ Feature │ ←── Can import
└──────┬──────┘
│
↓
┌─────────────┐
│ Shared │ ←── Should NOT import feature details
└──────┬──────┘
│
↓
┌─────────────┐
│ Lib │ ←── Library configuration
└─────────────┘General rule:
- ✅ Features can import from
@/shared - ✅ Shared can import from
@/lib - ❌ Shared should NOT import feature internal details
- ✅ Exception: Shared can use the Public API of features
Controlled exception:
High-level structural components in @/shared (like MainLayout) CAN consume
hooks exposed in feature Public APIs:
// ✅ ALLOWED - Use feature Public API in shared
// src/shared/components/Layout/MainLayout.tsx
import { useAuth } from "@/features/auth"; // Public API
export const MainLayout = () => {
const { user, logout } = useAuth();
// ...
};Recommended Data Flow
┌──────────┐
│ Page │ Page component
└────┬─────┘
│ uses
↓
┌──────────┐
│ Hook │ Custom hook (orchestration)
└────┬─────┘
│ calls
↓
┌──────────┐
│ Service │ Async function that fetches
└────┬─────┘
│ uses
↓
┌──────────┐
│ Axios │ Configured HTTP client
└──────────┘Advantages of This Architecture
- Scalability: Add features without breaking others
- Clarity: Structure reveals business purpose
- Maintainability: Everything related is together
- Testability: Features can be tested in isolation
- Parallel Work: Teams work on features without conflicts
- Onboarding: New developers understand structure quickly
5. Testing
BuildFast comes with Vitest and Testing Library preconfigured for a modern and fast testing experience.
Configuration
Testing is configured in vite.config.js:
export default defineConfig({
// ...
test: {
globals: true,
setupFiles: "./src/lib/test/setup.ts",
environment: "jsdom",
coverage: {
reporter: ["text", "json", "html"],
},
},
});Run Tests
# Run all tests
npm run test
# Run in watch mode
npm run test:watch
# Run with coverage
npm run test:coverage
# Run tests for a specific feature
npm run test -- features/productsTest Structure
Tests should be placed near the code they test:
src/features/products/
├── components/
│ ├── ProductCard.tsx
│ └── ProductCard.test.tsx # Component test
├── hooks/
│ ├── useProducts.ts
│ └── useProducts.test.ts # Hook test
└── utils/
├── product.utils.ts
└── product.utils.test.ts # Utility testExample: Component Test
// src/features/products/components/ProductCard.test.tsx
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { ProductCard } from './ProductCard';
describe('ProductCard', () => {
const mockProduct = {
id: '1',
name: 'Product Test',
price: 99.99,
};
it('renders the product name', () => {
render(<ProductCard product={mockProduct} />);
expect(screen.getByText('Product Test')).toBeInTheDocument();
});
it('renders the formatted price', () => {
render(<ProductCard product={mockProduct} />);
expect(screen.getByText('$99.99')).toBeInTheDocument();
});
});Example: Hook Test with React Query
// src/features/products/hooks/useProducts.test.ts
import { renderHook, waitFor } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useProducts } from './useProducts';
import * as productsService from '../services/products.service';
// Mock the service
vi.mock('../services/products.service');
// Wrapper for React Query
const createWrapper = () => {
const queryClient = new QueryClient({
defaultOptions: {
queries: { retry: false },
},
});
return ({ children }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);
};
describe('useProducts', () => {
it('fetches products successfully', async () => {
const mockProducts = [
{ id: '1', name: 'Product 1' },
{ id: '2', name: 'Product 2' },
];
vi.mocked(productsService.getProducts).mockResolvedValue(mockProducts);
const { result } = renderHook(() => useProducts(), {
wrapper: createWrapper(),
});
expect(result.current.isLoading).toBe(true);
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});
expect(result.current.data).toEqual(mockProducts);
});
});Example: Utility Test
// src/shared/utils/format.test.ts
import { describe, it, expect } from "vitest";
import { formatCurrency, formatDate } from "./format";
describe("formatCurrency", () => {
it("formats positive numbers correctly", () => {
expect(formatCurrency(1234.56)).toBe("$1,234.56");
});
it("formats zero correctly", () => {
expect(formatCurrency(0)).toBe("$0.00");
});
});Testing Protected Routes
// src/features/auth/components/ProtectedRoute.test.tsx
import { render, screen } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import { MemoryRouter } from 'react-router-dom';
import { ProtectedRoute } from './ProtectedRoute';
import * as authHooks from '../hooks/useAuth';
vi.mock('../hooks/useAuth');
describe('ProtectedRoute', () => {
it('renders children if user is authenticated', () => {
vi.mocked(authHooks.useAuth).mockReturnValue({
user: { id: '1', email: '[email protected]' },
isLoading: false,
});
render(
<MemoryRouter>
<ProtectedRoute>
<div>Protected Content</div>
</ProtectedRoute>
</MemoryRouter>
);
expect(screen.getByText('Protected Content')).toBeInTheDocument();
});
it('redirects if user is not authenticated', () => {
vi.mocked(authHooks.useAuth).mockReturnValue({
user: null,
isLoading: false,
});
render(
<MemoryRouter>
<ProtectedRoute>
<div>Protected Content</div>
</ProtectedRoute>
</MemoryRouter>
);
expect(screen.queryByText('Protected Content')).not.toBeInTheDocument();
});
});Testing Utilities
Create custom utilities in src/lib/test/utils.tsx:
// src/lib/test/utils.tsx
import { render } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { BrowserRouter } from 'react-router-dom';
export const createTestQueryClient = () =>
new QueryClient({
defaultOptions: {
queries: { retry: false },
mutations: { retry: false },
},
});
export const renderWithProviders = (ui: React.ReactElement) => {
const queryClient = createTestQueryClient();
return render(
<QueryClientProvider client={queryClient}>
<BrowserRouter>{ui}</BrowserRouter>
</QueryClientProvider>
);
};Testing Best Practices
- Test behavior, not implementation: Focus on how the user interacts
- Use data-testid only when necessary: Prefer queries by text or role
- Mock external services: Don't make real API calls in tests
- Unit tests for complex logic: Hooks, utils, and pure functions
- Integration tests for critical flows: Login, checkout, etc.
6. Code Conventions
BuildFast implements strict conventions to maintain code quality and consistency.
Git Hooks (Husky)
Git hooks run automatically:
Pre-commit
Runs before each commit:
# .husky/pre-commit
npm run lint # Check ESLint errors
npm run format # Format with PrettierCommit-msg
Validates commit message with Commitlint:
# .husky/commit-msg
npx commitlint --edit $1Pre-push
Runs before push (optional):
# .husky/pre-push
npm run test # Run all tests
npm run build # Verify build worksCommit Format (Conventional Commits)
BuildFast uses Conventional Commits for standardized messages:
<type>(<scope>): <description>
[optional body]
[optional footer]Allowed Types
| Type | Description | Example |
| ---------- | ------------------------------------ | ----------------------------------------- |
| feat | New feature | feat(products): add product filtering |
| fix | Bug fix | fix(auth): resolve login redirect issue |
| docs | Documentation changes | docs(readme): update installation steps |
| style | Format changes (don't affect code) | style: format with prettier |
| refactor | Refactoring (not bug fix or feature) | refactor(api): simplify error handling |
| perf | Performance improvement | perf(images): lazy load product images |
| test | Add or modify tests | test(hooks): add tests for useProducts |
| chore | Build, deps changes, etc | chore: update dependencies |
| ci | CI/CD changes | ci: add github actions workflow |
Commit Examples
# New feature
git commit -m "feat(products): add search functionality"
# Bug fix
git commit -m "fix(cart): prevent duplicate items"
# Refactoring
git commit -m "refactor(auth): extract validation logic to utils"
# Breaking change
git commit -m "feat(api)!: migrate to new API version
BREAKING CHANGE: API endpoints now use /v2/ prefix"
# With scope and detailed description
git commit -m "feat(checkout): add payment processing
- Integrate Stripe payment
- Add loading states
- Handle payment errors
- Add success confirmation"ESLint
Configuration in eslint.config.js:
export default {
extends: ["@typescript-eslint/recommended", "prettier"],
rules: {
"@typescript-eslint/no-unused-vars": "warn",
"@typescript-eslint/no-explicit-any": "error",
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
},
};Run ESLint
# Check errors
npm run lint
# Auto fix
npm run lint -- --fixPrettier
Configuration in prettier.config.cjs:
module.exports = {
semi: true,
singleQuote: true,
tabWidth: 2,
trailingComma: "es5",
printWidth: 80,
arrowParens: "avoid",
};Run Prettier
# Format all
npm run format
# Check without changing
npm run format -- --checkNaming Conventions
Files
PascalCase: ComponentName.tsx, ProductCard.tsx
camelCase: useProducts.ts, productService.ts
kebab-case: product-list.css (if using CSS modules)TypeScript Code
// ✅ Interfaces and Types - PascalCase
interface Product {
id: string;
name: string;
}
type ProductStatus = "active" | "inactive";
// ✅ Variables and functions - camelCase
const productName = "Product";
const getProduct = (id: string) => {};
// ✅ Global constants - SCREAMING_SNAKE_CASE
const API_BASE_URL = "https://api.example.com";
const MAX_RETRIES = 3;
// ✅ React components - PascalCase
const ProductCard = () => {};
// ✅ Custom Hooks - camelCase with "use" prefix
const useProducts = () => {};
// ✅ Service files - camelCase with ".service" suffix
// product.service.ts
export const getProducts = () => {};
// ✅ Route paths - SCREAMING_SNAKE_CASE in object
export const PRODUCTS_PATHS = {
ROOT: "/products",
DETAIL: "/products/:id",
};Import Structure
Order imports this way:
// 1. React and external library imports
import { useState, useEffect } from "react";
import { useQuery } from "@tanstack/react-query";
// 2. Feature imports (using public API)
import { useAuth } from "@/features/auth";
// 3. Shared imports
import { formatDate } from "@/shared/utils";
import { Button as CustomButton } from "@/shared/components";
// 4. Relative imports (only within same feature)
import { useProducts } from "../hooks/useProducts";
import { ProductCard } from "../components/ProductCard";
// 5. Type imports
import type { Product } from "../types";
// 6. Asset imports
import logo from "./logo.svg";TypeScript Best Practices
// ✅ Use explicit types for props
interface ProductCardProps {
product: Product;
onSelect: (id: string) => void;
}
// ✅ Avoid "any" - use "unknown" if needed
const parseJSON = (json: string): unknown => {
return JSON.parse(json);
};
// ✅ Use explicit return types
const getProductName = (product: Product): string => {
return product.name;
};
// ✅ Use generics when appropriate
const createState = <T>(initialValue: T): [T, (value: T) => void] => {
// ...
};
// ✅ Mark optional properties correctly
interface User {
id: string;
name: string;
email?: string; // Optional
}7. Environment Variables
BuildFast uses separate environment files per environment.
Environment Files
.env.development # Variables for local development
.env.production # Variables for production
.env.local # Local variables (don't commit)Required Variables
Stripe (Optional)
# For development, use test keys: https://dashboard.stripe.com/test/apikeys
VITE_APP_STRIPE_PUBLISHABLE_KEY=pk_test_...Regional Configuration
# Locale for dates and formats
locale=enUS # Options: enUS, esES, etc.Access Environment Variables
// ✅ Access in code
const stripeKey = import.meta.env.VITE_APP_STRIPE_PUBLISHABLE_KEY;
// ✅ With default values
const apiUrl = import.meta.env.VITE_APP_API_URL || "http://localhost:3000";
// ⚠️ Variables must start with VITE_APP_ to be accessible
// ❌ BAD: API_KEY=xxx
// ✅ GOOD: VITE_APP_API_KEY=xxxVariable Validation
Create a file to validate required variables:
// src/lib/config/env.ts
const requiredEnvVars = [
"VITE_APP_SUPABASE_URL",
"VITE_APP_SUPABASE_ANON_KEY",
] as const;
export const validateEnv = () => {
const missing = requiredEnvVars.filter((key) => !import.meta.env[key]);
if (missing.length > 0) {
throw new Error(
`Missing required environment variables: ${missing.join(", ")}`,
);
}
};
// Call in main.tsx
import { validateEnv } from "@/lib/config/env";
validateEnv();Best Practices
- Never commit
.env.local: Add to.gitignore - Document all variables: Keep this README updated
- Use variables for secrets: Don't hardcode API keys
- Validate at startup: Fail fast if critical variables are missing
- Different values per environment: Use
.env.developmentand.env.production
8. Available Scripts
Development
# Start development server (port 5173)
npm run dev
# Start with specific port
npm run dev -- --port 3000
# Start with specific host (for external access)
npm run dev -- --hostBuild
# Build for production
npm run build
# Preview production build
npm run preview
# Build with bundle analysis
npm run build -- --analyzeTesting
# Run all tests
npm run test
# Watch mode
npm run test:watch
# With coverage
npm run test:coverage
# Tests for specific feature
npm run test -- src/features/productsLinting & Formatting
# Check ESLint errors
npm run lint
# Auto fix ESLint
npm run lint -- --fix
# Format with Prettier
npm run format
# Check format without changing
npm run format -- --checkGit Hooks
# Reinstall Husky hooks
npm run prepare
# Run pre-commit hook manually
npm run pre-commit
# Run pre-push hook manually
npm run pre-push9. Deployment
Build for Production
# 1. Make sure you have the correct environment variables
# Edit .env.production with your production credentials
# 2. Run the build
npm run build
# 3. Ready files will be in /distDeployment on Vercel
Option 1: From GitHub
- Push your code to GitHub
- Go to vercel.com and connect your repository
- Configure environment variables:
VITE_APP_SUPABASE_URLVITE_APP_SUPABASE_ANON_KEYVITE_APP_STRIPE_PUBLISHABLE_KEY(optional)
- Automatic deployment with each push to
main
Option 2: Vercel CLI
# Install Vercel CLI
npm i -g vercel
# Login
vercel login
# Deploy
vercel
# Deploy to production
vercel --prodDeployment on Netlify
From GitHub
- Connect your repository at netlify.com
- Configuration:
- Build command:
npm run build - Publish directory:
dist
- Build command:
- Add environment variables in Site Settings > Environment
With CLI
# Install Netlify CLI
npm i -g netlify-cli
# Login
netlify login
# Deploy
netlify deploy
# Deploy to production
netlify deploy --prodDeployment on GitHub Pages
# 1. Install gh-pages
npm install --save-dev gh-pages
# 2. Add scripts in package.json
{
"scripts": {
"predeploy": "npm run build",
"deploy": "gh-pages -d dist"
}
}
# 3. Configure base in vite.config.js
export default defineConfig({
base: '/repo-name/',
// ...
})
# 4. Deploy
npm run deployEnvironment Variables in Production
Make sure to configure these variables on your platform:
VITE_APP_STRIPE_PUBLISHABLE_KEY=pk_live_... # Production keyDeployment Checklist
- [ ] Environment variables configured correctly
- [ ] Build runs without errors (
npm run build) - [ ] Tests pass (
npm run test) - [ ] Linting passes (
npm run lint) - [ ] Stripe URLs are production ones
- [ ] Sitemap generated (if applicable)
- [ ] Analytics configured (Google Analytics, Mixpanel, etc.)
- [ ] SEO optimized (meta tags, Open Graph)
- [ ] Performance verified (Lighthouse)
10. Best Practices
Architecture
- Follow Screaming Architecture: Organize by features, not technical types
- Respect the Public API: Only import from
index.tsof features - Avoid circular dependencies: Features shouldn't import from each other
- Keep features small: If a feature grows too large, split it
React & Hooks
- Use TypeScript: Always type props, states, and returns
- Small components: One responsibility per component
- Custom hooks for shared logic: Reuse logic between components
- Memoization when necessary:
useMemo,useCallbackto optimize
// ✅ Typed component
interface ButtonProps {
label: string;
onClick: () => void;
variant?: 'primary' | 'secondary';
}
export const Button: React.FC<ButtonProps> = ({
label,
onClick,
variant = 'primary',
}) => {
return <button onClick={onClick}>{label}</button>;
};State Management
- React Query for server state: API data
- useState for UI state: Simple local states
- Context for limited global state: Theme, Auth
- URL as source of truth: Filters, pages, searches
// ✅ Server state with React Query
const { data: products } = useQuery({
queryKey: ["products", filters],
queryFn: () => getProducts(filters),
});
// ✅ Local UI state
const [isOpen, setIsOpen] = useState(false);
// ✅ State in URL
const [searchParams] = useSearchParams();
const page = searchParams.get("page") || "1";Performance
- Lazy load routes: Use
React.lazyfor code splitting - Lazy load images: Use
loading="lazy"in<img>tags - Virtualize long lists: Use
react-windowor similar - Optimize bundle: Analyze with
npm run build -- --analyze
// ✅ Lazy loading routes
const ProductsPage = lazy(() => import('./pages/ProductsPage'));
<Suspense fallback={<Loading />}>
<ProductsPage />
</Suspense>;Security
- Never expose secrets in frontend: Use environment variables
- Validate inputs: Use Yup or Zod for validation
- Sanitize HTML: Use DOMPurify if rendering dynamic HTML
- Use HTTPS in production: Always
- Configure CORS correctly: On your backend
API Calls
- Centralize Axios configuration: In
src/lib/axios - Use interceptors: For auth tokens, error handling
- Handle errors globally: With React Query or interceptors
- Implement retry logic: For critical calls
// ✅ Centralized Axios configuration
// src/lib/axios/client.ts
import axios from "axios";
export const apiClient = axios.create({
baseURL: import.meta.env.VITE_APP_API_URL,
timeout: 10000,
});
// Interceptor for auth token
apiClient.interceptors.request.use((config) => {
const token = localStorage.getItem("token");
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// Interceptor for errors
apiClient.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
// Redirect to login
window.location.href = "/login";
}
return Promise.reject(error);
},
);Accessibility
- Use semantic HTML: Prefer native controls
- Add labels to inputs: Always
- Use landmarks:
<button>,<nav>,<main>, etc. - Test with keyboard: Navigation should work without mouse
- Adequate contrast: Use tools like Lighthouse
SEO
- Appropriate meta tags: Title, description, Open Graph
- Sitemap: Generate automatically
- Robots.txt: Configure correctly
- Semantic URLs:
/products/gaming-laptopnot/products?id=123
11. Troubleshooting
Common Issues
1. Error: "Cannot find module '@/features/...'"
Cause: Path aliases not configured correctly
Solution:
// Check vite.config.js
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
}
// Check tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}2. Tests fail with "Cannot find module"
Cause: Vitest doesn't recognize path aliases
Solution:
// vite.config.js
import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({
plugins: [react(), tsconfigPaths()],
// ...
});3. Error: "Hooks can only be called inside the body of a function component"
Cause: Calling hooks outside components or custom hooks
Solution:
// ❌ BAD
const data = useQuery(...); // Outside component
export const Component = () => {
// ...
}
// ✅ GOOD
export const Component = () => {
const data = useQuery(...); // Inside component
// ...
}4. Build fails with "out of memory"
Cause: Bundle too large or insufficient memory
Solution:
# Increase Node memory
NODE_OPTIONS=--max_old_space_size=4096 npm run build
# Or in package.json
{
"scripts": {
"build": "NODE_OPTIONS=--max_old_space_size=4096 vite build"
}
}5. Environment variables not read
Cause: Variables don't start with VITE_APP_
Solution:
# ❌ BAD
SUPABASE_URL=xxx
# ✅ GOOD
VITE_APP_SUPABASE_URL=xxx6. "Cannot read property of undefined" in tests
Cause: Missing wrapper with providers in tests
Solution:
// Use custom render with providers
import { renderWithProviders } from '@/lib/test/utils';
renderWithProviders(<Component />);7. Husky hooks don't execute
Cause: Hooks not installed correctly
Solution:
# Reinstall hooks
npm run prepare
# Check permissions (on Unix)
chmod +x .husky/*Get Help
- Search in Issues: GitHub Issues
- Create an Issue: If you can't find your problem
- Check documentation: Of specific libraries
- Discord/Slack: If the project has a community
Useful Resources
Developed with ❤️ to accelerate your frontend development.
BuildFast - From idea to production in record time.
