5 Projektstruktur und Bootstrapping mit Vite

Ein React-Projekt ist mehr als Code. Es ist eine Sammlung von Dateien, Konfigurationen und Build-Tools, die zusammenarbeiten, um eine lauffähige Anwendung zu erzeugen. Die Struktur dieser Dateien – das Scaffolding – bestimmt, wie wartbar, erweiterbar und verständlich ein Projekt ist.

Moderne React-Projekte nutzen Build-Tools wie Vite, die nicht nur Code bündeln, sondern den gesamten Entwicklungsprozess orchestrieren. Vite hat sich als Standard etabliert, weil es schnell ist, minimalen Overhead hat und mit ES Modules nativ arbeitet statt alles zu einem großen Bundle zu verklumpen.

Dieses Kapitel erklärt die Anatomie eines typischen Vite-React-Projekts: Welche Dateien existieren, was sie tun, wie der Bootstrap-Prozess funktioniert, und wie man die Struktur sinnvoll erweitert.

5.1 Die Anatomie eines Vite-Projekts

Ein frisches Vite-React-Projekt hat eine schlanke, aber durchdachte Struktur. Jede Datei hat einen klaren Zweck.

my-react-app/
├── node_modules/
├── public/
│   └── vite.svg
├── src/
│   ├── assets/
│   │   └── react.svg
│   ├── App.css
│   ├── App.tsx
│   ├── index.css
│   └── main.tsx
├── index.html
├── package.json
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts

Die Struktur folgt einem klaren Muster: HTML als Einstieg, TypeScript als Logik, CSS als Styling, Konfigurationsdateien für Tooling.

5.2 Der Einstiegspunkt: index.html

Anders als bei Webpack oder Create React App ist index.html bei Vite keine Template-Datei, sondern der tatsächliche Einstiegspunkt. Eine statische HTML-Datei, die Vite direkt ausliefert.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + React + TS</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

Zwei wichtige Elemente:

  1. <div id="root"></div> – Der Mount-Point. React wird hier einhängen.
  2. <script type="module" src="/src/main.tsx"></script> – Der JavaScript-Einstieg als ES Module.

Kein Template-Syntax, keine Platzhalter. Vite lädt die HTML-Datei direkt und ersetzt im Dev-Modus das <script>-Tag durch Hot-Module-Replacement-Code. Im Production-Build wird der Script-Pfad zum gebündelten Output angepasst.

Das ist ein fundamentaler Unterschied zu Webpack, wo index.html oft durch Plugins generiert wird. Bei Vite ist HTML der Ausgangspunkt, nicht das Produkt.

5.3 Der Bootstrap-Prozess: main.tsx

main.tsx ist der technische Einstiegspunkt der React-Anwendung. Hier passiert das Bootstrapping – React verbindet sich mit dem DOM.

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
import './index.css';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

Die Sequenz:

  1. Imports – React, ReactDOM, die Root-Komponente, globale Styles
  2. createRoot – Erstellt eine Root-Instanz für Concurrent Rendering (React 18+)
  3. render – Rendert die App-Komponente in das #root-Element
  4. StrictMode – Aktiviert zusätzliche Entwicklungs-Checks

document.getElementById('root')! – Das ! ist TypeScript-Syntax: “Ich weiß, dass dieses Element existiert.” Wenn Sie sich nicht sicher sind, verwenden Sie optionales Chaining:

const rootElement = document.getElementById('root');
if (!rootElement) throw new Error('Root element not found');

ReactDOM.createRoot(rootElement).render(<App />);

Der gesamte Bootstrap-Prozess ist synchron. Sobald main.tsx ausgeführt wird, rendert React sofort.

5.4 Die Root-Komponente: App.tsx

App.tsx ist die oberste React-Komponente. Hier beginnt die UI-Hierarchie.

import { useState } from 'react';
import './App.css';
import reactLogo from './assets/react.svg';
import viteLogo from '/vite.svg';

function App() {
  const [count, setCount] = useState(0);

  return (
    <>
      <div>
        <a href="https://vite.dev" target="_blank">
          <img src={viteLogo} className="logo" alt="Vite logo" />
        </a>
        <a href="https://react.dev" target="_blank">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <h1>Vite + React</h1>
      <div className="card">
        <button onClick={() => setCount((count) => count + 1)}>
          count is {count}
        </button>
      </div>
    </>
  );
}

export default App;

Ein typisches Starter-Template: Logos, ein Counter, ein paar Styles. Nichts Besonderes, aber ein funktionierendes Beispiel.

Die Struktur ist flach. Für größere Projekte extrahiert man Komponenten in separate Dateien:

src/
├── components/
│   ├── Header/
│   │   ├── Header.tsx
│   │   └── Header.css
│   ├── Counter/
│   │   ├── Counter.tsx
│   │   └── Counter.css
│   └── Footer/
│       ├── Footer.tsx
│       └── Footer.css
├── App.tsx
└── main.tsx

Feature-basierte Struktur ist ebenfalls beliebt:

src/
├── features/
│   ├── auth/
│   │   ├── Login.tsx
│   │   ├── Signup.tsx
│   │   └── authSlice.ts
│   ├── todos/
│   │   ├── TodoList.tsx
│   │   ├── TodoItem.tsx
│   │   └── todosSlice.ts
├── App.tsx
└── main.tsx

Die initiale Struktur ist bewusst simpel. Sie erweitern sie, wie das Projekt wächst.

5.5 Styling: index.css und App.css

Vite unterstützt CSS nativ. Importieren Sie CSS-Dateien direkt in JavaScript/TypeScript – Vite verarbeitet sie automatisch.

index.css – Globale Styles, die für die gesamte App gelten.

:root {
  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
  line-height: 1.5;
  font-weight: 400;
  
  color-scheme: light dark;
  color: rgba(255, 255, 255, 0.87);
  background-color: #242424;
}

body {
  margin: 0;
  display: flex;
  place-items: center;
  min-width: 320px;
  min-height: 100vh;
}

Importiert in main.tsx:

import './index.css';

Lädt beim Bootstrap, gilt global.

App.css – Komponenten-spezifische Styles.

.logo {
  height: 6em;
  padding: 1.5em;
  will-change: filter;
  transition: filter 300ms;
}

.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}

Importiert in App.tsx:

import './App.css';

Technisch sind diese Styles auch global – CSS hat keine echte Scoping-Mechanik. Wenn Sie echtes Scoping wollen, verwenden Sie CSS Modules:

// App.module.css
.logo { /* ... */ }

// App.tsx
import styles from './App.module.css';
<img src={logo} className={styles.logo} />

Vite erkennt .module.css-Dateien automatisch und verarbeitet sie als Modules.

Alternativen: Tailwind CSS, styled-components, Emotion. Vite unterstützt alle durch Plugins.

5.6 Assets: public vs. src/assets

Vite unterscheidet zwischen zwei Asset-Verzeichnissen:

public/ – Statische Assets, die direkt kopiert werden ohne Verarbeitung.

public/
└── vite.svg

Zugriff über absoluten Pfad:

<img src="/vite.svg" />

Vite kopiert alles aus public/ 1:1 ins Build-Output. Keine Transformation, kein Hashing. Verwenden Sie public/ für Assets, die von externen Services referenziert werden (z.B. robots.txt, favicon.ico).

src/assets/ – Assets, die durch Vite verarbeitet werden.

src/assets/
└── react.svg

Zugriff via Import:

import reactLogo from './assets/react.svg';
<img src={reactLogo} />

Vite verarbeitet diese Assets – kleine Dateien werden inline als Data-URLs, große bekommen Content-Hashes für Caching.

// Development
reactLogo = '/src/assets/react.svg'

// Production
reactLogo = '/assets/react-a3b2c1d4.svg'  // Hash für Cache-Busting

Faustregel: public/ für statische Assets, die sich nie ändern. src/assets/ für alles andere.

5.7 Konfiguration: vite.config.ts

Die zentrale Steuerung für Vite. Minimal per Default, erweiterbar nach Bedarf.

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

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

Das React-Plugin aktiviert Fast Refresh (Hot Module Replacement für React-Komponenten).

Erweiterte Konfiguration:

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

export default defineConfig({
  plugins: [react()],
  
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
      '@components': path.resolve(__dirname, './src/components')
    }
  },
  
  server: {
    port: 3000,
    open: true
  },
  
  build: {
    outDir: 'dist',
    sourcemap: true
  }
});

Aliase erlauben cleane Imports:

// Statt
import Button from '../../../components/Button';

// Mit Alias
import Button from '@components/Button';

Server-Konfiguration steuert den Dev-Server. Build-Konfiguration das Production-Output.

5.8 TypeScript-Konfiguration: tsconfig.json

Zwei Konfigurationsdateien für TypeScript:

tsconfig.json – Für Ihren App-Code.

{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,
    
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
    
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true
  },
  "include": ["src"]
}

Wichtige Optionen:

tsconfig.node.json – Für Build-Tools (Vite-Konfiguration).

{
  "compilerOptions": {
    "composite": true,
    "skipLibCheck": true,
    "module": "ESNext",
    "moduleResolution": "bundler",
    "allowSyntheticDefaultImports": true
  },
  "include": ["vite.config.ts"]
}

Separate Konfiguration, weil Build-Tools in Node.js laufen, nicht im Browser.

5.9 Der Unterschied zu Webpack: ESM vs. Bundle

Vite’s Architektur unterscheidet sich fundamental von Webpack.

Webpack – Baut alles zu einem (oder mehreren) Bundles, auch im Dev-Modus.

Webpack muss alles transformieren, bündeln, minimieren. Bei großen Projekten wird der Dev-Server langsam. Jede Änderung triggert einen Rebuild des gesamten Bundles (oder zumindest großer Teile).

Vite (Dev) – Serviert ES Modules direkt, ohne Bundling.

Vite lädt Module on-demand. Nur was der Browser anfragt, wird transformiert. Instant startup, auch bei großen Projekten.

Vite (Production) – Baut mit Rollup, optimiert für Production.

Production-Builds sind optimiert, gebündelt, minimiert. Aber im Dev-Modus keine Wartezeit.

Aspekt Webpack (Dev) Vite (Dev) Vite (Production)
Startup-Zeit Langsam (>10s bei großen Apps) Instant (<1s) N/A
Hot Reload Mittel (ganzes Bundle?) Instant (nur geänderte Module) N/A
Output Bundle(s) Keine Bundles, native ESM Optimierte Bundles
Browser-Support Alle (transpiliert) Moderne (ESM) Alle (transpiliert)

Vite ist schnell, weil es im Dev-Modus das Bundling überspringt. Moderne Browser können ES Modules nativ laden – Vite nutzt das.

5.10 Best Practices für Projektstruktur

1. Komponenten organisieren nach Feature oder Typ

Feature-basiert (empfohlen für große Apps):

src/
├── features/
│   ├── auth/
│   ├── products/
│   └── cart/
└── shared/
    ├── components/
    └── utils/

Typ-basiert (empfohlen für kleinere Apps):

src/
├── components/
├── hooks/
├── utils/
└── types/

2. Aliase verwenden für cleane Imports

// vite.config.ts
resolve: {
  alias: {
    '@': path.resolve(__dirname, './src'),
    '@components': path.resolve(__dirname, './src/components')
  }
}

// Komponente
import Button from '@components/Button';

3. Lazy Loading für große Komponenten

import { lazy, Suspense } from 'react';

const Dashboard = lazy(() => import('./features/dashboard/Dashboard'));

function App() {
  return (
    <Suspense fallback={<Loading />}>
      <Dashboard />
    </Suspense>
  );
}

Vite splittet automatisch lazy-geladene Komponenten in separate Chunks.

4. Environment Variables

Vite unterstützt .env-Dateien:

# .env
VITE_API_URL=https://api.example.com
VITE_APP_TITLE=My App

Zugriff via import.meta.env:

const apiUrl = import.meta.env.VITE_API_URL;

Nur Variablen mit VITE_-Präfix werden exponiert. Das verhindert versehentliches Leaken von Secrets.

5. Public Assets sparsam nutzen

public/ ist für echte statische Assets. Alles andere in src/assets/, damit Vite es optimieren kann.

5.11 Ein vollständiges Beispiel

Ein realistisches Projekt-Setup:

my-app/
├── public/
│   ├── favicon.ico
│   └── robots.txt
├── src/
│   ├── assets/
│   │   ├── logo.svg
│   │   └── images/
│   ├── components/
│   │   ├── Button/
│   │   │   ├── Button.tsx
│   │   │   ├── Button.module.css
│   │   │   └── Button.test.tsx
│   │   └── Header/
│   ├── features/
│   │   ├── auth/
│   │   │   ├── Login.tsx
│   │   │   ├── authSlice.ts
│   │   │   └── authAPI.ts
│   │   └── products/
│   ├── hooks/
│   │   ├── useAuth.ts
│   │   └── useLocalStorage.ts
│   ├── utils/
│   │   ├── formatDate.ts
│   │   └── api.ts
│   ├── types/
│   │   └── index.ts
│   ├── App.tsx
│   ├── App.css
│   ├── main.tsx
│   └── index.css
├── .env
├── .env.production
├── index.html
├── package.json
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts

Klar strukturiert, erweiterbar, wartbar. Komponenten sind nach Typ organisiert (components/), Features nach Domäne (features/), wiederverwendbare Logik in hooks/ und utils/.

Vite’s Scaffolding ist minimal, aber durchdacht. Es gibt genug Struktur für den Start, ohne zu viel vorzuschreiben. Sie erweitern es, wie Ihr Projekt wächst. Das ist das Gegenteil von monolithischen Frameworks, die von Anfang an alles vorgeben. Vite gibt Ihnen Freiheit – und die Werkzeuge, um diese Freiheit sinnvoll zu nutzen.