A production-ready full-stack starter template combining React 19 with TypeScript on the frontend and Nitro for the backend API. Built with Vite for blazing-fast development and optimized builds.
⭐ Don't forget to star this repo if you find it useful!
- ⚡ React 19 with TypeScript and Vite
- 🎨 Tailwind CSS 4 + shadcn/ui components
- 🗂️ File-based routing with
vite-plugin-pages - 🔄 Auto-imports for React hooks and components
- 🖼️ SVG as React components with
vite-plugin-svgr - 🔤 Google Fonts integration
- 📦 Path aliases (
@/components, etc.)
- 🚀 Nitro 3 server with H3 handler
- 🛣️ File-based API routing in
/routes - ⚡ Fast development with hot module replacement
- 🔧 TypeScript support out of the box
- ✅ ESLint + Prettier configured
- 🪝 Husky pre-commit hooks
- 🐳 Docker setup included
- 🤖 Dependabot for dependency updates
- 📝 Workspace settings for team collaboration
# Install dependencies
npm install
# Start development server (frontend + backend)
npm run dev
# Build for production
npm run build
# Preview production build
npm run preview
# Lint code
npm run lintThe dev server runs on:
- Frontend: http://localhost:5000
- API: http://localhost:5000/api/\*
Routes are automatically generated from files in src/pages/. Each .tsx file becomes a route.
Documentation: vite-plugin-pages
src/pages/
├── index.tsx → /
├── about.tsx → /about
├── users/
│ ├── index.tsx → /users
│ ├── [id].tsx → /users/:id (dynamic route)
│ └── profile.tsx → /users/profile
└── [...all].tsx → /* (catch-all/404)
All page components must use default exports:
// src/pages/about.tsx
const About = () => {
return (
<div>
<h1>About Page</h1>
</div>
);
};
export default About;Use square brackets for dynamic segments:
// src/pages/users/[id].tsx
const UserDetail = () => {
const { id } = useParams(); // auto-imported from react-router
return (
<div>
<h1>User ID: {id}</h1>
</div>
);
};
export default UserDetail;Use [...all].tsx for 404 pages or catch-all routes:
// src/pages/[...all].tsx or NotFound.tsx
const NotFound = () => {
return (
<div>
<h1>404 - Page Not Found</h1>
</div>
);
};
export default NotFound;Use React Router hooks (auto-imported):
const MyComponent = () => {
const navigate = useNavigate();
const location = useLocation();
return <button onClick={() => navigate("/about")}>Go to About</button>;
};API routes are automatically generated from files in routes/. Powered by Nitro and H3.
Documentation:
routes/
├── api/
│ ├── hello.ts → GET/POST /api/hello
│ ├── users/
│ │ ├── index.ts → GET/POST /api/users
│ │ └── [id].ts → GET/POST /api/users/:id
│ └── auth/
│ ├── login.ts → POST /api/auth/login
│ └── logout.ts → POST /api/auth/logout
└── health.ts → GET /health
Use defineEventHandler from H3:
// routes/api/hello.ts
export default defineEventHandler((event) => {
return {
message: "Hello from API!",
timestamp: new Date().toISOString(),
};
});Handle different HTTP methods:
// routes/api/users/index.ts
export default defineEventHandler(async (event) => {
const method = event.method;
if (method === "GET") {
return { users: [] };
}
if (method === "POST") {
const body = await readBody(event);
return { created: true, user: body };
}
return { error: "Method not allowed" };
});Or use method-specific handlers:
// routes/api/users/index.get.ts
export default defineEventHandler(() => {
return { users: [] };
});
// routes/api/users/index.post.ts
export default defineEventHandler(async (event) => {
const body = await readBody(event);
return { created: true, user: body };
});Use square brackets for dynamic parameters:
// routes/api/users/[id].ts
export default defineEventHandler((event) => {
const id = getRouterParam(event, "id");
return {
user: {
id,
name: "John Doe",
},
};
});Common H3 utilities:
import {
readBody, // Parse request body
getQuery, // Get query parameters
getRouterParam, // Get route parameters
getCookie, // Get cookies
setCookie, // Set cookies
getHeader, // Get headers
setResponseStatus, // Set response status
sendRedirect, // Send redirect
} from "h3";
export default defineEventHandler(async (event) => {
// Get query params: /api/search?q=test
const query = getQuery(event);
console.log(query.q); // 'test'
// Get route params: /api/users/123
const id = getRouterParam(event, "id");
// Parse JSON body
const body = await readBody(event);
// Get headers
const auth = getHeader(event, "authorization");
// Set response status
setResponseStatus(event, 201);
return { success: true };
});export default defineEventHandler((event) => {
const id = getRouterParam(event, "id");
if (!id) {
throw createError({
statusCode: 400,
statusMessage: "ID is required",
});
}
// Your logic here
return { id };
});Create middleware in routes/ with .ts extension:
// routes/middleware/auth.ts
export default defineEventHandler((event) => {
const token = getHeader(event, "authorization");
if (!token) {
throw createError({
statusCode: 401,
statusMessage: "Unauthorized",
});
}
// Add user to context
event.context.user = { name: "John" };
});Import SVGs as React components by adding ?react query:
import Logo from "@/assets/react.svg?react";
export const App = () => {
return (
<div>
<Logo />
</div>
);
};Configure Google Fonts in configs/fonts.config.ts:
export const fonts = [
{
name: "Inter",
styles: "wght@300;400;500;600;700",
},
{
name: "Space Grotesk",
styles: "wght@300;400;500;700",
},
];Automatically imports React hooks and React Router hooks. No need to import useState, useEffect, useNavigate, etc.
// No imports needed!
export function Counter() {
const [count, setCount] = useState(0);
const navigate = useNavigate();
return (
<div>
<Button onClick={() => setCount(count + 1)}>Count: {count}</Button>
</div>
);
}To enable auto-import for shadcn/ui components, uncomment in vite.config.ts:
AutoImport({
imports: ["react", "react-router"],
dirs: ["./src/components/ui"], // Uncomment this line
});.
├── src/
│ ├── assets/ # Static assets (images, SVGs)
│ ├── components/ # React components
│ │ └── ui/ # shadcn/ui components
│ ├── pages/ # Frontend routes (file-based)
│ ├── hooks/ # Custom React hooks
│ ├── utils/ # Utility functions
│ ├── types/ # TypeScript types
│ ├── constants/ # App constants
│ ├── data/ # Static data
│ ├── store/ # State management
│ └── main.tsx # App entry point
├── routes/ # Backend API routes (file-based)
│ └── api/ # API endpoints
├── configs/ # Configuration files
│ └── fonts.config.ts
├── vite.config.ts # Vite configuration
├── tsconfig.json # TypeScript config
└── package.json
Use @/ to import from src/:
import { Button } from "@/components/ui/button";
import { cn } from "@/utils/cn";
import type { User } from "@/types";# Build and run with Docker
docker build -t react-ts-starter .
docker run -p 5000:5000 react-ts-starterFor production deployment on a VPS with nginx, PM2, and SSL configuration, see the complete guide:
The guide includes:
- nginx configuration for serving static files and proxying API requests
- PM2 or systemd setup for running the Nitro server
- SSL certificate setup with Let's Encrypt
- Monitoring and troubleshooting tips
- Update and maintenance procedures
- This is a client-side rendered (CSR) application
- For SEO or Server-Side Rendering, consider Next.js, Remix, or Astro
- The Nitro backend is perfect for APIs, serverless functions, and edge deployments
Contributions are welcome! Feel free to open issues or submit pull requests.
MIT