A full-stack web application template featuring a .NET 10 backend with React/Vite frontend, using OIDC authentication with Microsoft Entra ID.
- Backend: .NET 10 Web API with ASP.NET Core
- Frontend: React 19 with Vite, TypeScript, and TanStack Router/Query/Table
- Authentication: OIDC with Microsoft Entra ID (Azure AD)
- Styling: Tailwind CSS
- Development: Hot reload for both frontend and backend
- Development Integration: ASP.NET Core
SpaProxylaunches Vite for Visual Studio users, while Vite proxies API and auth routes back to ASP.NET Core during development
-
Clone the repository
git clone https://github.com/ucdavis/web-app-template/ cd web-app-template -
Open In DevContainer
- Open the project folder in Visual Studio Code.
- Click the prompt to open in container (or manually select from the command palette).
Using the DevContainer is optional, but it will get you the right version of dotnet + node, and install all dependencies for you
-
Start the application
Inside DevContainer: The application starts automatically via
postStartCommand— no manual steps required.Outside DevContainer (command line):
Prerequisites:
- .NET 10 SDK
- Node.js 22+ (includes npm)
Install dependencies and start the app:
npm install cd client && npm install && cd .. npm start
npm startstarts the .NET backend on port5165with a CLI-specific launch profile, waits for health check, and then starts the Vite dev server on port5173which opens the browser.Visual Studio (Windows):
Prerequisites:
- Visual Studio 2026 version 18.0 or later (for
net10.0support) - Node.js 22+ (includes npm)
Install dependencies:
npm install cd client && npm install && cd ..
Then open
app.sln, set theserverproject as the startup project, and pressF5.SpaProxystarts Vite if needed and redirects the browser to the frontend dev server.Visual Studio Code:
Prerequisites:
- .NET 10 SDK
- Node.js 22+ (includes npm)
Install dependencies:
npm install cd client && npm install && cd ..
Then open the repo root in VS Code, install the recommended extensions when prompted (at minimum the Microsoft C# extension), choose
Full Stack: VS Codein Run and Debug, and pressF5. VS Code builds and launches the backend with thehttp-clilaunch profile, starts Vite after the backend health check passes, and opens the app in your default external browser athttp://localhost:5173. For backend-only debugging, chooseBackend: ASP.NET Core + Swagger. -
Access the application
In development, the frontend runs from http://localhost:5173 and proxies backend requests to ASP.NET Core on http://localhost:5165.
- Main App: http://localhost:5173
- Backend API: http://localhost:5165/api/*
- API Documentation (Swagger): http://localhost:5165/swagger
- Health Check: http://localhost:5165/health
- Visual Studio F5: launches through the backend profile, then redirects to the Vite dev server on
:5173
The app uses OIDC with Microsoft Entra ID (Azure AD). The default settings in appsettings.*.json are enough for local template development.
For a new application registration, redirect URIs, and app-specific auth settings, follow the customization guide.
This template includes GA4 wiring:
- GA bootstrap script is in
client/index.html - Route-change page view tracking is in
client/src/shared/analytics/AnalyticsListener.tsx
This app is configured with the GA4 measurement ID:
G-W46GLZE85V
The measurement ID appears in client/index.html in both places:
https://www.googletagmanager.com/gtag/js?id=...gtag('config', '...')
The health check endpoint (/health) is configured to return the status of the application.
The template includes generic Azure App Service deployment scaffolding in infrastructure/azure/ and GitHub Actions workflows in .github/workflows/.
Cloud deployments are intentionally limited to test and prod. Before the first cloud deployment, replace placeholder names such as webapp, rg-webapp-test, and rg-webapp-prod with names for your application.
For GitHub Environments, the one-time OIDC bootstrap, required variables/secrets, local deploy scripts, and first-deploy caveats, see Azure Deployment Setup. For the hosting flow and key deployment files, see Development Architecture.
In development, ASP.NET Core runs on port 5165, Vite serves the frontend on port 5173, and Vite proxies backend routes to ASP.NET Core. Visual Studio uses SpaProxy to start Vite and redirect the browser to it.
For request-flow diagrams, production behavior, and key file responsibilities, see Development Architecture.
The backend is configured with hot reload via dotnet watch. Any changes to C# files automatically restart the server. Visual Studio users can also run the server project directly with SpaProxy.
The frontend uses Vite's hot module replacement (HMR). Changes to React components, TypeScript files, and CSS are reflected immediately by the Vite dev server.
The repository includes .vscode/launch.json and .vscode/tasks.json so the standard VS Code workflow works out of the box:
Full Stack: VS Codelaunches the backend debugger, starts the Vite dev server, and opens the frontend in your default external browser.Backend: ASP.NET Core + Swaggerlaunches only the backend and opens Swagger when Kestrel is ready.
The VS Code flow intentionally uses the http-cli launch profile instead of the SpaProxy profile so terminal and editor-driven debugging both avoid the duplicate browser-launch behavior from the ASP.NET Core side.
- Frontend routes requiring authentication redirect to the backend's login endpoint
- Backend handles OIDC flow with Microsoft Entra ID
- Upon successful authentication, a same-site cookie is set
- Frontend API calls automatically include the authentication cookie
- Backend validates the cookie for protected endpoints
- Run
cd client && npm testto execute the Vitest suite once. - Use
npm run test:watchinsideclient/for red/green feedback while you work. - Tests run against a jsdom environment with Testing Library so you do not need the backend running.
- Run
dotnet testfrom the repository root to execute the .NET test project included inapp.sln. - Alternatively, target the project directly with
dotnet test tests/server.tests/server.tests.csproj. - The tests do not require an external database.
- JavaScript/TypeScript packages: run
npm outdatedat the repository root and insideclient/to see what can be updated. Usenpm updatein each location for compatible updates, ornpm install <package>@latestwhen you need to jump to a new major version. - After updating Node packages, reinstall if needed (
npm install,cd client && npm install) and rerun key checks likenpm run lint,cd client && npm test, anddotnet test.
.Net is a bit more complicated, but we're going to use the dotnet-outdated tool to help.
Run the following command from the repository root:
dotnet-outdated
and it'll show you a nice table of what can be updated. Be careful when updating major versions, especially with packages that are pinned to the .net version.
You can update individual packages or you can use the --upgrade flag to update all at once. Here's a nice way to do it and only update minor/patch versions:
dotnet-outdated --upgrade --version-lock Major
If you update a package that a tool depends on, update the matching tool version as well so local development stays consistent.
And as always, after updating dependencies, make sure to run dotnet build and dotnet test to verify everything is working.
.
├── client/ # React frontend
│ ├── src/
│ │ ├── routes/ # TanStack Router routes
│ │ ├── queries/ # TanStack Query hooks
│ │ ├── lib/ # API client and utilities
│ │ └── shared/ # Shared components
│ ├── package.json
│ └── vite.config.ts
├── server/ # .NET backend
│ ├── Controllers/ # API controllers
│ ├── Helpers/ # Utility classes
│ ├── Properties/ # Launch settings
│ ├── Program.cs # Application entry point
│ └── server.csproj # SpaProxy + publish integration
├── infrastructure/azure/ # Azure Bicep templates and local deployment scripts
├── .github/workflows/ # CI/CD and reusable Azure App Service deployment workflow
├── package.json # Root dev orchestration scripts
└── app.sln # Visual Studio solution file
npm start- Starts both backend and frontend with hot reloadnpm run start:server- Starts only the ASP.NET Core backendnpm run start:client- Starts only the Vite dev server
npm run dev- Start Vite development servernpm run dev:open- Start Vite development server and open the browsernpm run build- Build for productionnpm run lint- Run ESLintnpm run preview- Preview production buildnpm test- Run tests
dotnet run- Start the .NET applicationdotnet watch- Start with hot reloaddotnet build- Build the applicationdotnet test- Run tests