26 KiB
26 KiB
Blazor SSR Migration Plan
Project Overview
Migrating a React/TypeScript portfolio website to a minimal, dependency-free .NET Blazor application with static server-side rendering.
Current Stack:
- React 18.2 + TypeScript
- Vite build tool
- Tailwind CSS (to be replaced with vanilla CSS)
- React Router DOM
- Headless UI components (Dialog/Tabs - to be replaced with vanilla HTML/CSS/JS)
- React Icons (to be replaced with SVG icons)
- Azure Static Web Apps hosting
Target Stack:
- .NET 10 Blazor with Static SSR (no WebSockets/SignalR)
- C# Razor components (server-rendered only)
- Vanilla CSS only (no frameworks, no preprocessors)
- Blazor Router (built-in)
- No external component libraries
- No icon dependencies (inline SVG)
- Azure App Service or Azure Container Apps hosting
Migration Philosophy:
- Zero external dependencies beyond .NET runtime
- Simple, maintainable vanilla CSS
- Static server-side rendering (no interactivity circuits)
- Modern CSS features (Grid, Flexbox, CSS Variables, Container Queries)
- Native HTML elements with custom styling
- Zero JavaScript - pure CSS solutions for all interactivity (checkbox hack, :target pseudo-class)
Phase 1: Project Setup
1.1 .NET Project Initialization
- Install .NET 10 SDK
- Create new Blazor Web App project with SSR only:
dotnet new blazor -o src/BlazorApp -int None- Note:
-int Nonedisables interactivity (no SignalR/WebSockets)
- Note:
- Verify project builds:
dotnet build - Review generated files:
Program.cs,App.razor,Components/_Imports.razor,appsettings.json - Confirm
Program.csdoes NOT includeAddInteractiveServerComponents()or SignalR setup
1.2 Solution Structure
- Create solution file:
dotnet new sln -n my-portfolio - Add project to solution:
dotnet sln add src/BlazorApp/BlazorApp.csproj - Configure project properties (nullable, implicit usings)
- Ensure render modes are set to static SSR only
1.3 Git Configuration
- Update
.gitignorefor .NET (bin/, obj/, .vs/) - Archive React project (move to
archive/react-version/or createpre-blazor-migrationtag) - Commit initial Blazor project structure
Phase 2: CSS Architecture & Design System
2.1 CSS Variables Setup
- Create
wwwroot/css/variables.csswith design tokens:- Colors (black: #000, white: #fff, gray shades)
- Spacing scale (0.25rem, 0.5rem, 1rem, 1.5rem, 2rem, etc.)
- Typography (font-family: monospace, font-sizes, line-heights)
- Breakpoints (mobile-first: 640px, 768px, 1024px, 1280px)
- Z-index scale
- Border radius values
- Transition durations
2.2 Base Styles
- Create
wwwroot/css/reset.csswith modern CSS reset - Create
wwwroot/css/base.css:- Body styles (background: black, color: slate-50, font: monospace)
- Typography defaults
- Link styles
- Focus styles for accessibility
- Custom scrollbar styles
- Selection styles
2.3 Layout Utilities
- Create
wwwroot/css/layout.css:- Container classes (max-width, centering)
- Flexbox utilities (flex, flex-col, items-center, justify-between, gap, etc.)
- Grid utilities (grid, grid-cols)
- Spacing utilities (padding, margin classes)
- Responsive utilities
2.4 Component Styles
- Create
wwwroot/css/components.css:- Button styles (primary, secondary, hover, focus, disabled states)
- Input/textarea styles
- Card styles
- Navigation styles
- Footer styles
- Link styles
- Create
wwwroot/css/animations.css:- Fade-in animation
- Hover transitions
- Loading spinner animation
2.5 Compile CSS
- Create
wwwroot/css/app.cssthat imports all CSS files - Reference in
index.html:<link href="css/app.css" rel="stylesheet" /> - Test CSS loads correctly
Phase 3: SVG Icon System
3.1 Extract Icons
- Identify all react-icons used:
- FaBars (hamburger menu)
- FaXmark (close X)
- FaGithub
- FaLinkedin
- FaEnvelope
- FaDatabase
- FaDocker
- FaReact
- SiCsharp
- SiMicrosoftazure
- SiBlazor
- Download SVG paths from icon sources (FontAwesome, Simple Icons)
- Create reusable Icon component:
Components/Icon.razor
3.2 Icon Component
- Implement
Icon.razorwith parameters:Name(string): icon identifierSize(int, default 24): icon size in pixelsCssClass(string): additional CSS classes
- Store SVG paths in C# dictionary or switch statement
- Test all icons render correctly
Phase 4: Core Application Structure
4.1 Routing Setup
- Configure routes in
App.razor:/→ Home page/work→ Work page/about→ About pageNotFound→ 404/Error page
- Test routing and navigation
4.2 Layout Component
- Create
Shared/MainLayout.razor - Implement structure:
- Header with NavBar
- Main content area with
@Body - Footer
- Apply CSS classes (container, flex layout, min-height)
- Add fade-in animation class
- Test layout renders correctly
4.3 Navigation Component
- Create
Shared/NavBar.razor - Implement desktop navigation:
- Logo image
- Navigation links (Home, Work, About)
- Implement mobile navigation:
- Hamburger button (toggle mobile menu)
- Mobile menu overlay with links
- Close button
- Social icons in mobile menu
- Use Blazor's built-in
NavLinkcomponent with custom styling - Implement mobile menu state with C# boolean property
- Style with pure CSS (no Headless UI)
4.4 Footer Component
- Create
Shared/Footer.razor - Port footer content
- Apply styling
Phase 5: Page Components
5.1 Home Page
- Create
Pages/Home.razorwith@page "/" - Port content from
HomePage.tsx - Reference child components (Title, Text, TechIcons)
- Test rendering
5.2 Work Page
- Create
Pages/Work.razorwith@page "/work" - Port content from
WorkPage.tsx - Test rendering
5.3 About Page
- Create
Pages/About.razorwith@page "/about" - Port content from
AboutPage.tsx - Test rendering
5.4 Error/404 Page
- Create
Pages/NotFound.razorwith@page "/404" - Port content from
ErrorPage.tsx - Configure as NotFound in router
- Test 404 handling
Phase 6: Reusable UI Components
6.1 Typography Components (7)
Components/Title.razor- H1 headingComponents/Subtitle.razor- H2 headingComponents/Text.razor- Paragraph with marginComponents/Label.razor- Form labelComponents/List.razor- UL wrapperComponents/ListItem.razor- LI elementComponents/AnchorLink.razor- External link with styling
6.2 Form Components (3)
Components/Button.razor- Button with hover/focus statesComponents/TextInput.razor- Input field with labelComponents/TextAreaInput.razor- Textarea with label
6.3 Display Components (5)
Components/TechIcons.razor- Tech stack grid with iconsComponents/SocialIcons.razor- Social media links with iconsComponents/WorkTimeline.razor- Work experience timelineComponents/Loading.razor- Loading state wrapperComponents/LoadingSpinner.razor- Spinner animation
6.4 Feature Components (2)
Components/ContactMe.razor- Contact formComponents/AboutTabs.razor- Tab interface (vanilla CSS/JS)
Phase 7: Interactive Components (Pure CSS - No JavaScript)
7.1 Mobile Menu (CSS Checkbox Hack)
- Implement in
NavBar.razor:- Add hidden checkbox:
<input type="checkbox" id="mobile-menu-toggle" class="menu-toggle" /> - Add label for hamburger:
<label for="mobile-menu-toggle">☰</label> - Add label for close button inside menu:
<label for="mobile-menu-toggle">✕</label> - Render menu panel as sibling to checkbox
- Add hidden checkbox:
- Style with CSS:
- Hide checkbox:
.menu-toggle { display: none; } - Show/hide menu based on checkbox state:
.menu-toggle:checked ~ .mobile-menu { ... } - Show/hide overlay:
.menu-toggle:checked ~ .overlay { ... } - Slide-in animation using
transform: translateX() - Transparent overlay with backdrop-filter blur
- Z-index layering for proper stacking
- Hide checkbox:
- Test keyboard navigation (checkbox is focusable)
7.2 Tabs Component (CSS :target Pseudo-Class or Radio Buttons)
- Option A - Radio Button Approach (Recommended):
- Create
AboutTabs.razor:- Hidden radio inputs:
<input type="radio" name="tabs" id="tab1" checked /> - Label buttons:
<label for="tab1">Tab 1</label> - Tab panels as siblings to inputs
- Hidden radio inputs:
- Style with CSS:
- Hide radio buttons:
input[type="radio"] { display: none; } - Active tab styling:
#tab1:checked ~ .tabs-labels label[for="tab1"] { ... } - Show panel:
#tab1:checked ~ .tab-panels .panel1 { display: block; } - Hide other panels by default
- Hide radio buttons:
- Create
- Option B - :target Pseudo-Class:
- Use anchor links:
<a href="#tab1">Tab 1</a> - Panel IDs:
<div id="tab1" class="tab-panel">...</div> - Style:
.tab-panel:target { display: block; } - Note: Changes URL hash
- Use anchor links:
- Choose approach and implement
- Add smooth transitions with CSS
7.3 Form Handling (Traditional POST)
- Implement form submission in
ContactMe.razor - Use standard HTML
<form>with POST action - Server-side endpoint to handle form submission
- Redirect after post pattern (PRG)
- Add HTML5 validation attributes
- Style validation states with CSS (
:invalid,:valid)
Phase 8: Tailwind CSS to Vanilla CSS Conversion
8.1 Analyze Tailwind Usage
- Document all Tailwind classes used in current app:
- Layout:
flex,flex-col,items-center,justify-between,max-w-7xl, etc. - Spacing:
px-6,py-8,mt-4,mb-6,space-x-6, etc. - Typography:
text-sm,text-xl,font-semibold,font-mono, etc. - Colors:
bg-black,text-white,text-gray-200,ring-gray-300, etc. - Responsive:
lg:flex,md:order-2,sm:max-w-sm, etc. - Effects:
hover:bg-gray-800,focus-visible:outline, etc.
- Layout:
8.2 Create CSS Equivalents
- Create utility classes in
layout.css:.flex { display: flex; } .flex-col { flex-direction: column; } .items-center { align-items: center; } .justify-between { justify-content: space-between; } .max-w-7xl { max-width: 80rem; margin: 0 auto; } .container { max-width: 1280px; margin: 0 auto; padding: 0 1.5rem; } - Create spacing utilities:
.px-6 { padding-left: 1.5rem; padding-right: 1.5rem; } .py-8 { padding-top: 2rem; padding-bottom: 2rem; } .mt-4 { margin-top: 1rem; } /* etc. */ - Create responsive utilities using media queries:
@media (min-width: 1024px) { .lg\:flex { display: flex; } } - Alternative: Use component-specific classes instead of utilities
8.3 Choose Approach
- Option A (Recommended): Component-specific CSS classes
- More maintainable for small projects
- Better for this portfolio site (20 components)
- Example:
.nav-bar {},.nav-bar__logo {},.nav-bar__menu {}
- Option B: Minimal utility classes
- Create only the most-used utilities (flex, grid, spacing)
- Combine with component classes
- Document chosen approach in README
Phase 9: SEO & Meta Tags
9.1 HTML Head Configuration
- Update
wwwroot/index.htmlwith meta tags:- Charset, viewport
- Description, author
- Open Graph tags (og:title, og:description, og:image, og:url)
- Favicon reference
- Ensure all tags from React version are migrated
- Test with social media preview tools
9.2 Analytics Integration
- Add Google Analytics script to
index.html - Add CookieYes consent script to
index.html - Test analytics on navigation (use
NavigationManager.LocationChangedif needed) - Verify GDPR compliance
Phase 10: Build & Optimization
10.1 Build Configuration
- Configure
.csprojfor optimizations:<PropertyGroup> <PublishReadyToRun>true</PublishReadyToRun> <PublishReadyToRunShowWarnings>true</PublishReadyToRunShowWarnings> </PropertyGroup> - Test production build:
dotnet publish -c Release - Analyze output in
bin/Release/net10.0/publish/ - Verify CSS is minimal and unminified (easy to debug)
- Verify NO JavaScript files present
10.2 CSS Optimization
- Remove unused CSS
- Combine CSS files if beneficial
- Consider minification (optional - Azure handles this)
- Test CSS loads and applies correctly
- Verify CSS-only menu and tabs work in all browsers
10.3 Image Optimization
- Verify
logo.webpis optimized - Add width/height attributes to prevent layout shift
- Test image loading
10.4 Zero JavaScript Verification
- Confirm no
.jsfiles inwwwroot/js/ - Confirm no
<script>tags in layouts/pages - Test site functions without JavaScript enabled in browser
Phase 11: Deployment Configuration
11.1 Azure App Service Configuration
- Create Azure App Service (Linux or Windows)
- Configure environment variables in Azure portal:
ASPNETCORE_ENVIRONMENT=Production
- Enable HTTPS only
- Configure health check endpoint
- Set up Application Insights (optional)
11.2 GitHub Actions Workflow
- Create/update
.github/workflows/azure-deploy.yml:name: Deploy to Azure App Service on: push: branches: [ main ] workflow_dispatch: jobs: build-and-deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup .NET uses: actions/setup-dotnet@v3 with: dotnet-version: '10.0.x' - name: Build run: dotnet build src/BlazorApp/BlazorApp.csproj -c Release - name: Publish run: dotnet publish src/BlazorApp/BlazorApp.csproj -c Release -o ${{env.DOTNET_ROOT}}/myapp - name: Deploy to Azure Web App uses: azure/webapps-deploy@v2 with: app-name: 'your-app-name' publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }} package: ${{env.DOTNET_ROOT}}/myapp - Set up Azure publish profile in GitHub secrets
- Test workflow on branch
11.3 Local Development Setup
- Document commands:
- Run:
dotnet watch(hot reload enabled) - Build:
dotnet build - Publish:
dotnet publish -c Release
- Run:
- Test hot reload functionality
- Configure HTTPS for local dev (trust cert)
- Verify no SignalR scripts loaded (check browser network tab)
- Test CSS-only interactivity (menu, tabs) during development
Phase 12: Testing & Quality Assurance
12.1 Visual Testing
- Compare React site vs Blazor site side-by-side
- Test all pages render identically
- Test all components display correctly
- Test responsive design:
- Mobile (320px, 375px, 414px)
- Tablet (768px, 1024px)
- Desktop (1280px, 1920px)
- Test cross-browser:
- Chrome/Edge (Chromium)
- Firefox
- Safari (if available)
12.2 Functional Testing
- Test navigation between pages (full page reloads)
- Test mobile menu open/close (CSS checkbox)
- Test tab switching in About page (CSS radio/target)
- Test all external links
- Test form submission with POST/redirect
- Test 404 page navigation
- Test with JavaScript disabled in browser
- Verify no JavaScript errors (should be none)
12.3 Accessibility Testing
- Test keyboard navigation (Tab, Enter, Space for checkboxes/radios)
- Test screen reader compatibility (aria-labels, proper label associations)
- Test focus indicators (especially on hidden checkbox/radio controls)
- Test color contrast (black/white theme)
- Verify checkbox and radio button labels are properly associated
- Run Lighthouse accessibility audit
12.4 Performance Testing
- Test server response times for page requests
- Test First Contentful Paint (FCP)
- Test Time to Interactive (TTI)
- Test with throttled network (3G)
- Verify no WebSocket connections in network tab
- Verify no JavaScript downloads
- Run Lighthouse performance audit
- Target: FCP < 1s, TTI < 1.5s (server-rendered, zero JavaScript, instant interactive)
12.5 SEO Testing
- Test meta tags with social media debuggers:
- Facebook Sharing Debugger
- Twitter Card Validator
- LinkedIn Post Inspector
- Test robots.txt accessibility
- Test favicon in browser tabs
- Run Lighthouse SEO audit
Phase 13: Documentation & Cleanup
13.1 Update Documentation
- Update README.md:
# beaufindlay.com My personal portfolio site built with .NET Blazor WebAssembly. ## Tech Stack - .NET 10 Blazor SSR (static server-side rendering) - Pure CSS only (zero JavaScript) - Hosted on Azure App Service ## Development - Run: `dotnet watch` - Build: `dotnet build` - Publish: `dotnet publish -c Release` ## Features - Zero external dependencies (no npm, no frameworks, no JavaScript) - Static server-side rendering (no WebSockets) - Pure CSS interactivity (checkbox hack for menu, radio buttons for tabs) - Mobile-first responsive design - Accessible and SEO-optimized - Works with JavaScript disabled - Document CSS architecture in comments
- Add migration notes (lessons learned, gotchas)
13.2 Clean Up React Files
- Archive React project:
- Option A: Move to
archive/react-version/ - Option B: Create git tag
pre-blazor-migrationand delete - Option C: Keep in separate branch
- Option A: Move to
- Remove Node.js files from root:
- Delete
src/Client/package.json - Delete
src/Client/package-lock.json - Delete
src/Client/node_modules/ - Delete
src/Client/tsconfig*.json - Delete
src/Client/vite.config.ts - Delete
src/Client/tailwind.config.js - Delete
src/Client/postcss.config.js - Delete
src/Client/.eslintrc.cjs
- Delete
- Update
.gitignore(remove Node.js entries)
13.3 Code Quality
- Run
dotnet formaton all files - Add XML documentation comments to public components
- Ensure consistent naming conventions
- Remove unused using statements
- Remove commented-out code
Phase 14: Production Deployment
14.1 Pre-Deployment Checklist
- All components migrated and tested ✓
- All routes working ✓
- Mobile menu functional ✓
- Tabs functional ✓
- Analytics configured ✓
- SEO tags verified ✓
- Performance acceptable ✓
- No console errors ✓
- Cross-browser tested ✓
14.2 Deploy to Production
- Merge
blazor-refactorbranch tomain - Monitor GitHub Actions workflow
- Verify deployment succeeds
- Test production site at beaufindlay.com
14.3 Post-Deployment
- Verify site loads correctly in production
- Test all functionality in production
- Verify analytics tracking
- Monitor for errors (first 24-48 hours)
- Check browser console for any warnings
- Verify SSL certificate
- Test social media sharing previews
Component Migration Checklist
Pages (5)
- Layout (MainLayout.razor)
- Home (Pages/Home.razor)
- Work (Pages/Work.razor)
- About (Pages/About.razor)
- Error/404 (Pages/NotFound.razor)
Typography Components (7)
- Title.razor
- Subtitle.razor
- Text.razor
- Label.razor
- List.razor
- ListItem.razor
- AnchorLink.razor
Form Components (3)
- Button.razor
- TextInput.razor
- TextAreaInput.razor
Display Components (5)
- TechIcons.razor
- SocialIcons.razor
- WorkTimeline.razor
- Loading.razor
- LoadingSpinner.razor
Feature Components (3)
- NavBar.razor (in Shared/)
- Footer.razor (in Shared/)
- ContactMe.razor
- AboutTabs.razor
Shared/Infrastructure (2)
- Icon.razor (new - SVG icon system)
- CssHelper.cs (optional - CSS class utilities)
Total: 25 components
CSS File Structure
wwwroot/
├── css/
│ ├── variables.css # CSS custom properties (design tokens)
│ ├── reset.css # Modern CSS reset
│ ├── base.css # Base typography and body styles
│ ├── layout.css # Layout utilities (flex, grid, container)
│ ├── components.css # Component-specific styles
│ ├── animations.css # Animations and transitions
│ └── app.css # Main file that imports all others
├── logo.webp
├── robots.txt
└── index.html
Key Syntax Conversions
| React/JSX | Blazor/Razor |
|---|---|
className="..." |
class="..." |
{variable} |
@variable |
{condition && <Element />} |
@if (condition) { <Element /> } |
{items.map(item => ...)} |
@foreach (var item in items) { ... } |
const [state, setState] = useState() |
private bool state; + StateHasChanged() |
onClick={handler} |
@onclick="Handler" |
onChange={handler} |
@onchange="Handler" |
<Component prop={value} /> |
<Component Prop="@value" /> |
| Props interface | [Parameter] properties |
import Component from './Component' |
@using or implicit |
Dependencies Eliminated
Removed Libraries
- ❌ Tailwind CSS → ✅ Vanilla CSS
- ❌ PostCSS → ✅ None
- ❌ Autoprefixer → ✅ Modern browsers only
- ❌ React Icons → ✅ Inline SVG
- ❌ Headless UI → ✅ Native HTML + CSS
- ❌ React Router DOM → ✅ Blazor Router (built-in)
- ❌ Vite → ✅ .NET SDK
- ❌ TypeScript → ✅ C#
- ❌ ESLint → ✅ Roslyn analyzers (built-in)
- ❌ Node.js → ✅ None
Added Technology
- ✅ Static server-side rendering (Blazor SSR mode)
- ✅ Pure CSS interactivity techniques (checkbox hack, radio buttons, :target pseudo-class)
Build Dependencies
- Before: Node.js, npm, Vite, TypeScript compiler, Tailwind CLI
- After: .NET SDK only
Hosting Requirements
- Before: Static hosting (Azure Static Web Apps, any CDN)
- After: ASP.NET Core server (Azure App Service, Container Apps, or any host supporting .NET)
Risk Assessment
High Risk (Requires Testing)
- Mobile menu with CSS checkbox hack: CSS-only implementation complexity
- Mitigation: Well-established pattern, test thoroughly across browsers
- Tabs with CSS (radio buttons or :target): CSS-only implementation complexity
- Mitigation: Test both approaches, choose most accessible
- Icon system: 11 icons need SVG paths
- Mitigation: Download from FontAwesome/Simple Icons, test early
- CSS-only accessibility: Ensuring keyboard navigation works properly
- Mitigation: Thorough accessibility testing with screen readers
Medium Risk
- CSS conversion from Tailwind: Large effort
- Mitigation: Component-specific CSS, test each component
- Server hosting: Requires server infrastructure instead of static hosting
- Mitigation: Azure App Service is straightforward to configure
- Browser compatibility for CSS tricks: Checkbox hack and advanced selectors
- Mitigation: Test on all major browsers, provide fallbacks if needed
Low Risk
- Basic component migration: Straightforward
- Routing: Blazor router is similar to React Router
- Static assets: Simple copy operation
Success Criteria
- Zero npm/Node.js dependencies
- Zero CSS framework dependencies
- Zero JavaScript (pure CSS interactivity)
- All pages render identically to React version
- Mobile menu works smoothly (CSS checkbox hack)
- Tabs work smoothly (CSS radio buttons or :target)
- Site loads in < 2 seconds on 4G (server-rendered)
- Site works with JavaScript disabled
- No WebSocket connections present
- No SignalR scripts loaded
- No JavaScript files served
- Analytics functional (server-side or noscript fallback)
- SEO tags correct
- Mobile responsive
- Cross-browser compatible (including CSS-only features)
- Keyboard accessible (Tab/Enter/Space navigation)
- Lighthouse score: 95+ (Performance, Accessibility, SEO)
- Server resource usage minimal (stateless requests only)
Estimated Timeline
- Phase 1-2 (Setup & CSS): 2-3 days
- Phase 3 (Icons): 0.5-1 day
- Phase 4 (Core Structure): 1-2 days
- Phase 5-6 (Pages & Components): 3-4 days
- Phase 7 (Interactive - CSS only): 1-2 days
- Phase 8 (CSS Conversion): 2-3 days
- Phase 9 (SEO & Analytics): 0.5-1 day
- Phase 10-11 (Build & Deploy): 1-2 days (server configuration)
- Phase 12 (Testing): 2-3 days (including CSS-only interactivity testing)
- Phase 13-14 (Docs & Deploy): 1 day
Total: 14-22 days (depending on CSS approach and server setup complexity)
Notes
- This plan prioritizes simplicity and maintainability over feature richness
- No external dependencies means no breaking changes from library updates
- Vanilla CSS may take longer initially but is easier to maintain long-term
- Static SSR provides instant page loads with server-side rendering and excellent SEO
- Zero JavaScript - pure CSS interactivity (checkbox hack, radio buttons) - no framework hydration, no parsing overhead
- The site will be future-proof with minimal maintenance requirements
- Focus on modern CSS features (Grid, Flexbox, Custom Properties) rather than utility classes
- Test frequently throughout migration - don't wait until the end
- Tradeoff: Requires server hosting instead of static hosting, but provides better initial load performance
- Key Benefits:
- No WebSocket connections = lower server resource usage, simpler architecture, no reconnection issues
- No JavaScript = works with JS disabled, faster TTI, smaller payload, privacy-friendly
- Accessible by default (native HTML form controls)