Skip to content

12 - Frontend Project Setup Guide (Interview Edition) ​

When you're given a blank screen in a coding round and told "set up a project from scratch," speed and confidence matter. This is your step-by-step playbook.


Quick Reference — Copy-Paste Commands ​

bash
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install
npm run dev

React + JavaScript (Vite) ​

bash
npm create vite@latest my-app -- --template react
cd my-app
npm install
npm run dev

Next.js + TypeScript ​

bash
npx create-next-app@latest my-app --typescript --tailwind --eslint --app --src-dir
cd my-app
npm run dev

Full Setup Walkthrough (Vite + React + TypeScript) ​

Step 1: Scaffold ​

bash
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install

What you get out of the box:

my-app/
├── index.html           # entry HTML
├── package.json
├── tsconfig.json         # TS config (strict mode ON)
├── tsconfig.app.json     # app-specific TS config
├── tsconfig.node.json    # node-specific TS config (vite config)
├── vite.config.ts        # vite config
├── eslint.config.js      # ESLint flat config
├── src/
│   ├── main.tsx          # entry point (renders <App />)
│   ├── App.tsx           # root component
│   ├── App.css
│   ├── index.css
│   └── vite-env.d.ts     # Vite type declarations
└── public/
    └── vite.svg

Step 2: Clean Up Boilerplate ​

Delete the default demo content so you start clean:

bash
# Remove demo files
rm src/App.css src/assets/react.svg public/vite.svg

Replace src/App.tsx with a clean starting point:

tsx
export default function App() {
  return (
    <main>
      <h1>App</h1>
    </main>
  );
}

Replace src/index.css with a minimal reset:

css
*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  font-family: system-ui, -apple-system, sans-serif;
  line-height: 1.6;
  color: #1a1a1a;
  background: #fff;
}

Adding Common Tools ​

Tailwind CSS ​

bash
npm install -D tailwindcss @tailwindcss/vite

Update vite.config.ts:

ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tailwindcss from '@tailwindcss/vite';

export default defineConfig({
  plugins: [react(), tailwindcss()],
});

Replace src/index.css:

css
@import "tailwindcss";

Verify it works:

tsx
export default function App() {
  return <h1 className="text-3xl font-bold text-blue-600">Working</h1>;
}

React Router ​

bash
npm install react-router

Basic setup in src/main.tsx:

tsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { BrowserRouter, Routes, Route } from 'react-router';
import App from './App';
import './index.css';

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<App />} />
      </Routes>
    </BrowserRouter>
  </StrictMode>
);

Testing (Vitest + React Testing Library) ​

bash
npm install -D vitest jsdom @testing-library/react @testing-library/jest-dom @testing-library/user-event

Add test config to vite.config.ts:

ts
/// <reference types="vitest/config" />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: './src/test-setup.ts',
  },
});

Create src/test-setup.ts:

ts
import '@testing-library/jest-dom/vitest';

Add script to package.json:

json
{
  "scripts": {
    "test": "vitest",
    "test:run": "vitest run"
  }
}

Write your first test src/__tests__/App.test.tsx:

tsx
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import App from '../App';

describe('App', () => {
  it('renders heading', () => {
    render(<App />);
    expect(screen.getByRole('heading', { name: /app/i })).toBeInTheDocument();
  });
});

Run:

bash
npm test

Path Aliases (@ imports) ​

Update tsconfig.app.json:

json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

Update vite.config.ts:

ts
import path from 'path';

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
});

Now you can do:

tsx
import { Button } from '@/components/Button';
import { useAuth } from '@/hooks/useAuth';

Zustand (Lightweight State Management) ​

bash
npm install zustand

Create a store src/store/useStore.ts:

ts
import { create } from 'zustand';

interface AppState {
  count: number;
  increment: () => void;
  reset: () => void;
}

export const useStore = create<AppState>((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  reset: () => set({ count: 0 }),
}));

TanStack Query (Server State) ​

bash
npm install @tanstack/react-query

Wrap app in src/main.tsx:

tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 60_000,
      retry: 1,
    },
  },
});

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <QueryClientProvider client={queryClient}>
      <App />
    </QueryClientProvider>
  </StrictMode>
);

Usage:

tsx
import { useQuery } from '@tanstack/react-query';

function UserList() {
  const { data, isLoading, error } = useQuery({
    queryKey: ['users'],
    queryFn: () => fetch('/api/users').then(res => res.json()),
  });

  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;
  return <ul>{data.map((u: User) => <li key={u.id}>{u.name}</li>)}</ul>;
}

React Hook Form (Forms) ​

bash
npm install react-hook-form
tsx
import { useForm } from 'react-hook-form';

interface FormData {
  email: string;
  amount: number;
}

function PaymentForm() {
  const { register, handleSubmit, formState: { errors } } = useForm<FormData>();

  const onSubmit = (data: FormData) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input
        {...register('email', { required: 'Email is required' })}
        type="email"
        placeholder="Email"
      />
      {errors.email && <span>{errors.email.message}</span>}

      <input
        {...register('amount', { required: true, min: 1 })}
        type="number"
        placeholder="Amount"
      />

      <button type="submit">Pay</button>
    </form>
  );
}

Axios (HTTP Client) — Optional ​

bash
npm install axios
ts
// src/services/api.ts
import axios from 'axios';

const api = axios.create({
  baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000/api',
  headers: { 'Content-Type': 'application/json' },
});

// Request interceptor (attach auth token)
api.interceptors.request.use((config) => {
  const token = localStorage.getItem('token');
  if (token) config.headers.Authorization = `Bearer ${token}`;
  return config;
});

// Response interceptor (handle errors globally)
api.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response?.status === 401) {
      // redirect to login
    }
    return Promise.reject(error);
  }
);

export default api;

For a coding interview, keep it simple. Don't over-architect.

Small project (interview coding round) ​

src/
├── components/          # UI components
│   ├── Button.tsx
│   └── Card.tsx
├── hooks/               # Custom hooks
│   └── usePayment.ts
├── types/               # Shared TypeScript types
│   └── index.ts
├── utils/               # Pure utility functions
│   └── formatCurrency.ts
├── App.tsx
├── main.tsx
└── index.css

Medium project (take-home / real app) ​

src/
├── components/          # Reusable UI components
│   ├── ui/              # Base components (Button, Input, Card)
│   └── layout/          # Layout components (Header, Sidebar)
├── pages/               # Page-level components (one per route)
│   ├── Dashboard.tsx
│   └── Checkout.tsx
├── hooks/               # Custom hooks
├── context/             # React context providers
├── services/            # API client, external services
├── store/               # Zustand stores
├── types/               # TypeScript types/interfaces
├── utils/               # Pure helper functions
├── __tests__/           # Tests (or co-locate with components)
├── App.tsx
├── main.tsx
└── index.css

Environment Variables ​

Create .env at project root:

VITE_API_URL=http://localhost:3000/api
VITE_APP_NAME=MyApp

Access in code:

tsx
const apiUrl = import.meta.env.VITE_API_URL;

Rules:

  • Must be prefixed with VITE_ to be exposed to client code
  • Never put secrets in VITE_ variables (they're bundled into client JS)
  • Add .env to .gitignore

Common Interview Scenarios — What to Install ​

"Build a todo app" ​

bash
npm create vite@latest todo -- --template react-ts && cd todo && npm i

No extra deps needed. useState is enough.

"Build a form with validation" ​

bash
npm create vite@latest form-app -- --template react-ts && cd form-app
npm i react-hook-form

"Build a dashboard that fetches data" ​

bash
npm create vite@latest dashboard -- --template react-ts && cd dashboard
npm i @tanstack/react-query axios
npm i -D tailwindcss @tailwindcss/vite

"Build a multi-page app with routing" ​

bash
npm create vite@latest app -- --template react-ts && cd app
npm i react-router

"Build a checkout flow with state management and tests" ​

bash
npm create vite@latest checkout -- --template react-ts && cd checkout
npm i zustand
npm i -D vitest jsdom @testing-library/react @testing-library/jest-dom @testing-library/user-event
npm i -D tailwindcss @tailwindcss/vite

Speed Tips for the Coding Round ​

  1. Don't waste time on perfect setup. Scaffold → clean boilerplate → start coding. Total setup should be < 3 minutes.

  2. Skip Tailwind if not asked. Inline styles or plain CSS are fine for a coding round. Don't spend 5 minutes configuring Tailwind if the task is about logic.

  3. Skip routing if single page. Don't add React Router for a single-page task.

  4. Start with types. Define your interfaces first — it structures your thinking and shows TypeScript fluency.

  5. Build state layer before UI. Reducer/store first, then components that consume it. Easier to test and discuss.

  6. Co-locate tests. Put test files next to the component (Button.test.tsx next to Button.tsx) — faster to navigate during the interview.

  7. Know the vite.config.ts test config by heart. The vitest setup (globals, jsdom, setupFiles) is always the same. Memorize it.

  8. Use npx if unsure about global installs. npx create-vite@latest works even if vite isn't globally installed.

Frontend interview preparation reference.