Skip to content

Repository files navigation

Vite + React + TypeScript Template

An opinionated starter template for React projects with TypeScript, Vite, and Sass.

What's Included

  • React with wouter (lazy loading, error boundary)
  • TypeScript with strict mode
  • Sass (SCSS) with a minimal reset and CSS custom properties (light/dark via prefers-color-scheme)
  • ESLint flat config with typescript-eslint, react-hooks, and react-refresh plugins
  • PWA via vite-plugin-pwa (offline precache, silent updates, offline toast)
  • pnpm as the enforced package manager

Project Structure

public/                      icons; referenced by the manifest in vite.config.ts
src/
├── Context/
│   ├── ThemeContext.tsx     THEMES is the source of truth for themes
│   ├── ThemeToggle.tsx      passed to <Navbar actions={...} />, not imported by it
│   └── ThemeToggle.scss
├── components/
│   ├── Nav/
│   │   ├── Navbar.tsx       hero/solid modes, isShrunk, actions slot
│   │   ├── NavDrawer.tsx    slide-in drawer, supports nested panels
│   │   ├── nav.config.ts    all nav data + isRouted (edit this)
│   │   └── Navbar.scss      styles for both
│   ├── OfflineToast/        shown when navigator.onLine goes false
│   └── ErrorBoundary.tsx
├── pages/
│   ├── Home/                renders <Navbar isHero />
│   ├── About/               lazy-loaded, as an example of a code-split route
│   └── NotFound/            the <Switch> fallback
├── index.scss               reset, theme palettes, CSS custom properties
└── main.tsx                 routes, providers, ScrollToTop, OfflineToast

The two files you'll touch first are nav.config.ts (navigation is data) and index.scss (the palette). Routes are registered as <Route> children of the <Switch> in main.tsx.

Four tsconfigs: tsconfig.json only holds references, tsconfig.base.json holds the option shared by the other two, and tsconfig.app.json / tsconfig.node.json keep only what's specific to each.

Getting Started

pnpm install
pnpm dev

Scripts

Command Description
pnpm dev Start dev server
pnpm build Lint, type-check, and build
pnpm preview Preview production build locally
pnpm lint Run ESLint
pnpm lint:fix Run ESLint with auto-fix

Customization

  • Styles: Edit src/index.scss to change the colors, fonts, or add variables. You can add individual component-level .scss files as you expand the site.
  • Routing: Add new pages in src/pages/ and register <Route>s inside the <Switch> in main.tsx. Use lazy() + <Suspense> for code-split routes.
  • PWA: Rename the app and repoint the icons under manifest in vite.config.ts, and replace the placeholder icons in public/. Images are cached on first view rather than precached, so only code, styles and markup ship in the initial cache. Or remove the PWA entirely if you don't need offline support.
  • Context providers: Wrap <Switch> in main.tsx with any context providers you need (e.g. theme, auth).

Spacing content below the navbar

The navbar is position: fixed, so it floats above your content and doesn't take up layout space. Content at the top of a page will slide underneath it unless you offset it.

Add a spacer once, wherever you render <Navbar />:

<Navbar />
<div className="nav-spacer" />
// index.scss
.nav-spacer {
    height: calc(56px + 2rem + env(safe-area-inset-top));
}

The height matches the navbar's at-rest size: the 56px bar plus its 1rem top/bottom padding plus the safe-area inset. The bar shrinks on scroll, but since it's fixed the spacer only needs to clear the initial height.

For hero pages (<Navbar isHero={true} />), you can skip the spacer so the transparent bar sits over the hero content.

Removing the PWA

If you don't want offline support, the whole thing comes out in four steps:

pnpm remove vite-plugin-pwa
rm -r src/components/OfflineToast
  1. Delete the VitePWA({ ... }) block and its import from vite.config.ts, leaving plugins: [react()].
  2. Delete the OfflineToast import and <OfflineToast /> from src/main.tsx.
  3. Drop the dev-dist entry from .gitignore.
  4. The plugin was generating /manifest.webmanifest and injecting the <link rel="manifest"> into index.html. Without it there is no manifest at all — the icons in public/ are still served, but the app is no longer installable. If you want the icons without the offline behaviour, add a static public/site.webmanifest and link it yourself.

If you have already deployed once

Deleting the plugin does not remove the service worker from browsers that already have it. A registered worker keeps serving its cached index.html on every visit, and since your new build no longer ships a sw.js to replace it, those visitors can be pinned to the old version indefinitely. There is no way to reach them after the fact.

So if the app has shipped with the PWA enabled, do this first, as its own release:

VitePWA({
    selfDestroying: true,
    // leave the rest of the config as-is
})

That builds a service worker whose only job is to unregister itself and delete its caches. Deploy it, leave it up long enough for returning visitors to pick it up — a week is a reasonable default, longer if your traffic is sparse — and only then remove the plugin using the steps above.

If the PWA has never been deployed anywhere, skip all of this and just delete it.


React + TypeScript + Vite (generated by create-vite)

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.

Currently, two official plugins are available:

React Compiler

The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see this documentation.

Expanding the ESLint configuration

If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:

export default defineConfig([
    globalIgnores(['dist']),
    {
        files: ['**/*.{ts,tsx}'],
        extends: [
            // Other configs...

            // Remove tseslint.configs.recommended and replace with this
            tseslint.configs.recommendedTypeChecked,
            // Alternatively, use this for stricter rules
            tseslint.configs.strictTypeChecked,
            // Optionally, add this for stylistic rules
            tseslint.configs.stylisticTypeChecked,

            // Other configs...
        ],
        languageOptions: {
            parserOptions: {
                project: ['./tsconfig.node.json', './tsconfig.app.json'],
                tsconfigRootDir: import.meta.dirname,
            },
            // other options...
        },
    },
])

You can also install eslint-plugin-react-x and eslint-plugin-react-dom for React-specific lint rules:

// eslint.config.js
import reactX from 'eslint-plugin-react-x';
import reactDom from 'eslint-plugin-react-dom';

export default defineConfig([
    globalIgnores(['dist']),
    {
        files: ['**/*.{ts,tsx}'],
        extends: [
            // Other configs...
            // Enable lint rules for React
            reactX.configs['recommended-typescript'],
            // Enable lint rules for React DOM
            reactDom.configs.recommended,
        ],
        languageOptions: {
            parserOptions: {
                project: ['./tsconfig.node.json', './tsconfig.app.json'],
                tsconfigRootDir: import.meta.dirname,
            },
            // other options...
        },
    },
]);

About

Template for a Vite React Typescript website

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages