@rubichandrap/create-poyo-app
v0.0.3
Published
A minimal React + .NET 10 Multi-Page Application (MPA) starter framework. Features server-side routing, type-safe API integration, and zero-config HMR.
Downloads
277
Maintainers
Readme
Poyo
A minimal React + .NET 10 Multi-Page Application (MPA) starter framework
Poyo is an ultra-lightweight framework for building server-rendered React applications with .NET. It provides the essential scaffolding for MPA architecture without imposing opinions on authentication, database, or business logic.
🎯 The Problem Poyo Solves
ASP.NET MVC can only serve static files from wwwroot/
When building React apps with .NET MVC, you face a critical challenge:
❌ THE PROBLEM:
- ASP.NET MVC only serves static files from wwwroot/
- React source code lives in a separate client project
- You can't directly reference React components from Razor views
- You need to build React → copy to wwwroot → reference in views
- Manual process, breaks hot reload, painful developer experienceTraditional Workarounds:
- Separate deployments - React SPA + .NET API (loses MPA benefits)
- Manual copying - Build React, copy to wwwroot (tedious, error-prone)
- Complex build scripts - Custom tooling (hard to maintain)
✨ How Poyo Solves It
Poyo provides a complete integration between React (Vite) and .NET MVC:
✅ THE SOLUTION:
1. React source code in poyo.client/ (separate project)
2. Vite compiles React → wwwroot/generated/ (automatic)
3. Manifest generation maps hashed files → Razor partials
4. Hot reload works in development (Vite dev server)
5. Production builds automatically update references
6. Zero manual intervention required!Development Mode:
User → .NET MVC → Razor View → Vite Dev Server (localhost:5173)
↓
React Hot Reload ✨Production Mode:
npm run build
↓
Vite compiles React → wwwroot/generated/index-[hash].js
↓
generate-manifest.js creates _ReactAssets.cshtml
↓
.NET MVC serves from wwwroot/ with correct hashed filenames ✅Key Features:
- 🔥 Hot Module Replacement - React changes reload instantly in dev
- 📦 Automatic Asset Management - Hashed filenames handled automatically
- 🚀 Server-Side Rendering - SEO-friendly, fast initial load
- 🎯 Type-Safe Integration - TypeScript types from OpenAPI
- 🔐 Server Data Injection - Pass data to React without API calls
- 🛠️ Route Management - Sync routes between server and client
✨ Features
- 🚀 Multi-Page Architecture - Server-side routing with React hydration for SEO-friendly pages
- 🔐 Demo Authentication - Simple cookie-based auth example (replace with your own)
- 📦 Server Data Injection - Pass data from server to client without API calls
- ✨ Modern Stack - React 19, TypeScript, Tailwind CSS v4, TanStack Query
- 🎯 Type-Safe APIs - Auto-generated TypeScript types from OpenAPI
- 🛠️ Route Management - CLI tools for managing routes between server and client
🏗️ Architecture
Poyo/
├── Poyo.Server/ # .NET 10 Server
│ ├── Controllers/ # MVC + API Controllers
│ ├── Middleware/ # Auth, Error handling
│ ├── Models/ # DTOs
│ ├── Services/ # Business logic
│ └── Views/ # Razor views
│
└── poyo.client/ # React Client
├── src/
│ ├── pages/ # React pages
│ ├── hooks/ # Custom hooks (usePage, etc.)
│ ├── hooks-api/ # TanStack Query hooks
│ ├── services/ # API services
│ └── providers/ # Context providers
└── scripts/ # Code generation tools🚀 Quick Start
Prerequisites
- .NET 10 SDK
- Node.js 20+
Installation
# Create a new project
npx @rubichandrap/create-poyo-app MyApp
# Navigate to project
cd MyApp
# Install dependencies (React + .NET)
npm install
npm run restore
# Run development servers
npm run devDemo Credentials
- Username:
demo - Password:
password
📚 Core Concepts
1. Server Data Injection (Server-Driven UI)
Pass data from server to client without API calls - like Laravel Livewire!
Poyo allows you to inject server-side data directly into your React components, eliminating the need for initial API calls and enabling server-driven UI patterns.
Server (C#):
[Authorize]
public IActionResult Dashboard()
{
// Prepare data on the server
var data = new
{
message = "Hello from server!",
timestamp = DateTime.UtcNow,
user = User.Identity?.Name,
notifications = GetUserNotifications(),
settings = GetUserSettings()
};
// Inject into ViewBag
ViewBag.ServerData = JsonSerializer.Serialize(data);
return View();
}View (Razor):
@{
ViewBag.ServerData = JsonSerializer.Serialize(new {
message = "Data from view!",
userId = User.FindFirst("sub")?.Value
});
}
<div id="react-root" data-page-name="Dashboard"></div>Client (TypeScript):
interface DashboardData {
message: string;
timestamp: string;
user: string;
notifications: Notification[];
settings: UserSettings;
}
export default function DashboardPage() {
// Access server data immediately - no loading state needed!
// Validates that data is a non-null object
const data = usePage<DashboardData>();
if (!data) return <div>No data available</div>;
return (
<div>
<h1>{data.message}</h1>
<p>Server time: {data.timestamp}</p>
<p>User: {data.user}</p>
{/* Data is already here - no spinner, no API call! */}
</div>
);
}How it works:
- Server renders Razor view with data in
ViewBag.ServerData _Layout.cshtmlinjects it aswindow.SERVER_DATA- React hydrates and
usePage()reads fromwindow.SERVER_DATA - Validation:
usePage()ensures data is a valid object (returnsnullotherwise). - Zero API calls for initial page load!
Benefits:
- ✅ Faster initial render - No loading spinners
- ✅ SEO-friendly - Data is in HTML
- ✅ Type-safe - TypeScript knows the shape
- ✅ Server-driven - Like Livewire/Inertia.js
- ✅ Secure - Data prepared server-side with auth context
Use Cases:
- User profile data
- Dashboard statistics
- Notification counts
- User preferences
- Any data needed on page load
2. Route Management (Enhanced)
Routes are defined in routes.json and can now support Custom Controllers and Flexible SEO:
{
"path": "/Dashboard",
"name": "Dashboard",
"files": {
"react": "src/pages/Dashboard/index.page.tsx",
"view": "Views/Dashboard/Index.cshtml"
},
"isPublic": false,
"controller": "DashboardController", // Optional: Use custom controller
"action": "Index", // Optional: Custom action
"seo": { // Optional: SEO Metadata
"title": "My Dashboard",
"description": "View your stats",
"meta": {
"og:image": "https://..."
},
"jsonld": {
"@type": "WebPage"
}
}
}CLI Commands:
# Basic Add
npm run route:add YourPage
# Add with Custom Controller & Action
node scripts/manage-routes.js add /Admin --controller AdminController --action Index
# Skip View Generation (if controller handles it)
node scripts/manage-routes.js add /API/Proxy --controller ApiController --action Proxy --no-view3. Flexible SEO System
Poyo now supports a data-driven SEO system. You don't need to touch .cshtml files for metadata.
- Title/Description: Set in
routes.json. - Meta Tags: Dictionary in
routes.json(supports OpenGraph). - JSON-LD: Inject structured data scripts automatically.
All metadata is injected server-side into _Layout.cshtml before the React app even loads, ensuring perfect SEO.
3. Authentication Strategy
Poyo uses a hybrid approach to balance security and usability:
Web (Browser): HttpOnly Cookies
- Why? Protected against XSS (JavaScript can't read them). Browsers send them automatically.
- How? Server sets an
AspNetCore.Cookiescookie on login.
Mobile (Native Apps): JWT (Bearer Token)
- Why? flexible for native HTTP clients where cookies are clumsy.
- How? Login API returns a token. Mobile apps send it in
Authorization: Bearer <token>.
Client UI: "UI Token"
- What? A non-sensitive flag/token stored in
localStorage. - Why? Instant UI updates. React knows to show "Logout" instead of "Login" immediately without waiting for a server roundtrip.
- Security: This is NOT used for access control. The Server validates the Cookie (or Bearer token). If the cookie is missing/invalid, the request fails even if the UI token exists.
- What? A non-sensitive flag/token stored in
[GuestOnly] // Redirects authenticated users
public IActionResult Login() => View();
[Authorize] // Requires authentication
public IActionResult Dashboard() => View();🛠️ Tech Stack
Server
- .NET 10
- ASP.NET Core MVC
- Cookie Authentication
Client
- React 19
- TypeScript
- Vite (Rolldown)
- TanStack Query
- React Hook Form + Zod
- Tailwind CSS v4
- Axios
📖 Project Structure
Key Files
routes.json- Route definitionsPoyo.Server/Program.cs- Server configurationpoyo.client/src/app.tsx- Client entry pointpoyo.client/src/hooks/use-page.ts- Server data hook
Important Directories
Poyo.Server/Controllers/- MVC controllersPoyo.Server/Controllers/Api/- API controllersPoyo.Server/Middleware/Auth/- Auth attributespoyo.client/src/pages/- React pagespoyo.client/scripts/- Code generation
🎯 What's Included
Server Components
- Cookie authentication
- Demo auth service (replace with your own)
- MVC routing
- Server data injection (
[ServerData]attribute) - Guest-only pages (
[GuestOnly]attribute) - Error handling
- JSend response wrapper
Client Components
- React 19 + TypeScript
- Form validation (React Hook Form + Zod)
- Data fetching (TanStack Query)
- Server data hook (
usePage<T>()) - Route management CLI
- Tailwind CSS v4
🔧 Customization
Replace Demo Auth
The framework includes hardcoded demo auth. Replace AuthService.cs with your own implementation:
// Poyo.Server/Services/Auth/AuthService.cs
public class AuthService : IAuthService
{
// Replace with real authentication
// - ASP.NET Core Identity
// - JWT tokens
// - OAuth/OIDC
// - Your custom solution
}Add Database
The framework doesn't include database access. Add your own:
# Entity Framework Core
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
# Or Dapper
dotnet add package DapperCustomize Styling
Update Tailwind configuration in poyo.client/src/index.css:
@theme {
--font-sans: YourFont, system-ui, sans-serif;
/* Add your theme variables */
}🤖 Code Generation
Poyo includes powerful code generation tools to keep your client and server in sync.
1. DTO Generation
Generates TypeScript types from OpenAPI specification
npm run generate:dtos- Fetches OpenAPI spec from server
- Generates TypeScript types using
openapi-typescript - Outputs to
src/schemas/dtos.generated.ts - Requires: Server running +
VITE_OPENAPI_URLin.env
2. Validation Schema Generation
Generates Zod validation schemas from TypeScript DTOs
npm run generate:schemas- Reads generated DTOs
- Creates Zod schemas using
ts-to-zod - Outputs to
src/schemas/validations.generated.ts - Use in forms with
zodResolver
3. Manifest Generation ⚠️ CRITICAL FOR PRODUCTION
Generates production asset manifest for server-side rendering
npm run generate:manifestWhy this is CRITICAL:
In development, Vite serves assets directly:
<!-- Dev mode - Vite dev server -->
<script type="module" src="http://localhost:5173/src/main.tsx"></script>In production, Vite builds assets with hashed filenames:
dist/generated/
├── index-C2LBw7bc.css ← Hash changes every build!
├── index-COWd0_qB.js ← Hash changes every build!
└── vendor-SQrKxH4E.js ← Hash changes every build!The Problem: Your Razor views need to reference these files, but the filenames change with every build!
The Solution:
generate-manifest.js reads Vite's manifest and generates _ReactAssets.cshtml:
<!-- Auto-generated - DO NOT EDIT -->
<link rel="stylesheet" href="/generated/index-C2LBw7bc.css" />
<script type="module" src="/generated/index-COWd0_qB.js"></script>
<script type="module" src="/generated/vendor-SQrKxH4E.js"></script>
<!-- Hashes updated automatically on every build! -->How it works:
npm run buildcompiles React app- Vite creates
.vite/manifest.jsonwith file mappings generate-manifest.jsreads manifest- Generates
_ReactAssets.cshtmlwith correct hashed filenames _Layout.cshtmlincludes this partial in production- Your app loads with correct assets!
What happens if you forget:
❌ 404 errors - Assets not found
❌ Old cached assets loaded
❌ Broken production deployment
❌ White screen of deathWhen it runs:
- ✅ Automatically after
npm run build(via postbuild script) - ✅ Manually with
npm run generate:manifest
Files involved:
- Input:
poyo.client/dist/.vite/manifest.json(Vite output) - Output:
Poyo.Server/Views/Shared/_ReactAssets.cshtml(Razor partial) - Used by:
Poyo.Server/Views/Shared/_Layout.cshtml(in production)
4. Route Management
Add new route:
npm run route:add User/Profile
# OR (with flags)
npm run route:add -- /Register --guestWhat this command does:
- Updates
routes.json: Adds entry mapping/User/Profileto the React page and Razor view. - Scaffolds React Page: Creates
poyo.client/src/pages/User/Profile/index.page.tsx.- Optionally use
--flatforsrc/pages/User/profile.page.tsxstyle.
- Optionally use
- Scaffolds Razor View: Creates
Poyo.Server/Views/User/Profile/Index.cshtml.- Sets up the
#rootdiv withdata-page-name="User/Profile"for hydration.
- Sets up the
Remove route:
npm run route:remove User/Profile- Safe Deletion: Prompts to optionally delete both the React page and MVC View (and empty folders).
Sync routes:
npm run route:sync- Forward Sync: Checks for missing files and offers Rescaffold/Prune.
- Reverse Sync: Checks for "untracked" files (React pages not in
routes.json) and offers to Add/Delete them.
📝 Scripts
Client (poyo.client/)
npm run dev # Start dev server
npm run build # Build for production
npm run generate # Generate DTOs + schemas
npm run generate:dtos # Generate TypeScript types from OpenAPI
npm run generate:schemas # Generate Zod schemas from DTOs
npm run generate:dtos # Generate TypeScript types from OpenAPI
npm run generate:schemas # Generate Zod schemas from DTOs
npm run route:add # Add new route (supports --controller, --action, --no-view)
npm run route:sync # Sync routes with files
npm run lint # Run linter
npm run format # Check formattingServer (Poyo.Server/)
npm run dev # Start server (dotnet run)
npm run build # Build project (dotnet build)
npm run format # Check C# formatting
npm run format:fix # Fix C# formatting
npm run watch # Watch mode (dotnet watch)
npm run publish # Publish for production🤝 Contributing
This is a starter framework - fork it and make it your own!
📄 License
MIT License - Use freely for any purpose
🎉 What Poyo Is NOT
- ❌ Not a full-featured CMS
- ❌ Not opinionated about database
- ❌ Not opinionated about authentication
- ❌ Not a replacement for Next.js/Remix (different architecture)
✅ What Poyo IS
- ✅ A minimal MPA starter
- ✅ A foundation to build upon
- ✅ A showcase of React + .NET integration
- ✅ A learning resource for MPA architecture
Built with ❤️ for developers who want control over their stack
