|
| 1 | +# GitHub Pages Deployment Plan |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +This document describes the plan for automatically building the **RFP Response Creator** Blazor WebAssembly application and deploying a live demo to GitHub Pages whenever changes are pushed to the `main` branch. |
| 6 | + |
| 7 | +The deployed demo will be publicly accessible at: |
| 8 | + |
| 9 | +``` |
| 10 | +https://blazordata-net.github.io/RFPAPP/ |
| 11 | +``` |
| 12 | + |
| 13 | +A link to this page will be added to the repository's `README.md` for easy discovery. |
| 14 | + |
| 15 | +--- |
| 16 | + |
| 17 | +## Architecture |
| 18 | + |
| 19 | +The repository contains two relevant projects for this deployment: |
| 20 | + |
| 21 | +| Project | SDK | Role | |
| 22 | +|---|---|---| |
| 23 | +| `RFPResponsePOC/RFPResponsePOC.Client` | `Microsoft.NET.Sdk.BlazorWebAssembly` | Client-side Blazor WASM app — the static artifact that can be hosted on GitHub Pages | |
| 24 | +| `RFPResponsePOC/RFPResponsePOC` | `Microsoft.NET.Sdk.Web` | ASP.NET Core host — **not** deployed to Pages (requires a server) | |
| 25 | + |
| 26 | +Only the **client** project is deployed to Pages, because GitHub Pages only hosts static files. |
| 27 | + |
| 28 | +```mermaid |
| 29 | +graph TD |
| 30 | + A[Developer pushes to main] --> B[GitHub Actions Workflow triggered] |
| 31 | + B --> C[Checkout source code] |
| 32 | + C --> D[Setup .NET 9 SDK] |
| 33 | + D --> E[dotnet publish RFPResponseAPP.Client<br/>with --base-path /RFPAPP] |
| 34 | + E --> F[Copy publish output to staging dir] |
| 35 | + F --> G[Add .nojekyll file] |
| 36 | + G --> H[Add 404.html SPA redirect] |
| 37 | + H --> I[Deploy to gh-pages branch] |
| 38 | + I --> J[GitHub Pages serves static files] |
| 39 | + J --> K[Live at blazordata-net.github.io/RFPAPP/] |
| 40 | +``` |
| 41 | + |
| 42 | +--- |
| 43 | + |
| 44 | +## Key Considerations |
| 45 | + |
| 46 | +### Base Path |
| 47 | + |
| 48 | +Because GitHub Pages serves the app under a sub-path (`/RFPAPP/`), the Blazor app must be published with a matching base path. This is done by passing `--base-path /RFPAPP` to `dotnet publish`, which sets the `<base href>` in `index.html` automatically. |
| 49 | + |
| 50 | +### SPA Client-Side Routing |
| 51 | + |
| 52 | +GitHub Pages does not understand Blazor's client-side routing. If a user navigates directly to a deep link (e.g., `/RFPAPP/counter`), GitHub Pages returns a 404. The standard workaround is: |
| 53 | + |
| 54 | +1. Place a `404.html` in the root that contains JavaScript to redirect back to `index.html`, preserving the intended path in the query string. |
| 55 | +2. Place a snippet in `index.html` to read the redirect path from the query string and restore `history.pushState`. |
| 56 | + |
| 57 | +This is the same technique used by [spa-github-pages](https://github.com/rafgraph/spa-github-pages). |
| 58 | + |
| 59 | +### `.nojekyll` File |
| 60 | + |
| 61 | +GitHub Pages runs Jekyll processing by default, which ignores files and folders prefixed with `_` (such as Blazor's `_framework/` directory). Adding a `.nojekyll` file at the repository root of the `gh-pages` branch disables Jekyll and ensures all assets are served correctly. |
| 62 | + |
| 63 | +--- |
| 64 | + |
| 65 | +## Workflow Design |
| 66 | + |
| 67 | +```mermaid |
| 68 | +flowchart LR |
| 69 | + subgraph Trigger |
| 70 | + T1[push to main] |
| 71 | + T2[workflow_dispatch] |
| 72 | + end |
| 73 | +
|
| 74 | + subgraph Build Job |
| 75 | + B1[actions/checkout@v4] |
| 76 | + B2[actions/setup-dotnet@v4 .NET 9] |
| 77 | + B3[dotnet publish --configuration Release --base-path /RFPAPP] |
| 78 | + B4[Copy wwwroot output to ./release] |
| 79 | + B5[Touch .nojekyll] |
| 80 | + B6[Copy 404.html] |
| 81 | + end |
| 82 | +
|
| 83 | + subgraph Deploy Job |
| 84 | + D1[peaceiris/actions-gh-pages@v4<br/>publish_dir: ./release<br/>branch: gh-pages] |
| 85 | + end |
| 86 | +
|
| 87 | + T1 --> B1 |
| 88 | + T2 --> B1 |
| 89 | + B1 --> B2 --> B3 --> B4 --> B5 --> B6 --> D1 |
| 90 | +``` |
| 91 | + |
| 92 | +--- |
| 93 | + |
| 94 | +## File Changes Required |
| 95 | + |
| 96 | +### 1. New Workflow: `.github/workflows/deploy-ghpages.yml` |
| 97 | + |
| 98 | +A new GitHub Actions workflow file that: |
| 99 | + |
| 100 | +- Triggers on every push to `main` and supports manual dispatch. |
| 101 | +- Builds the Blazor WASM client with the correct base path. |
| 102 | +- Patches `index.html` for SPA routing support. |
| 103 | +- Deploys the static output to the `gh-pages` branch using `peaceiris/actions-gh-pages`. |
| 104 | + |
| 105 | +```yaml |
| 106 | +name: Deploy to GitHub Pages |
| 107 | + |
| 108 | +on: |
| 109 | + push: |
| 110 | + branches: |
| 111 | + - main |
| 112 | + workflow_dispatch: |
| 113 | + |
| 114 | +permissions: |
| 115 | + contents: write |
| 116 | + |
| 117 | +jobs: |
| 118 | + deploy: |
| 119 | + runs-on: ubuntu-latest |
| 120 | + |
| 121 | + steps: |
| 122 | + - name: Checkout |
| 123 | + uses: actions/checkout@v4 |
| 124 | + |
| 125 | + - name: Setup .NET |
| 126 | + uses: actions/setup-dotnet@v4 |
| 127 | + with: |
| 128 | + dotnet-version: '9.x' |
| 129 | + |
| 130 | + - name: Publish Blazor WASM client |
| 131 | + run: | |
| 132 | + dotnet publish RFPResponsePOC/RFPResponsePOC.Client/RFPResponseAPP.Client.csproj \ |
| 133 | + --configuration Release \ |
| 134 | + --output ./release-output |
| 135 | +
|
| 136 | + - name: Prepare gh-pages directory |
| 137 | + run: | |
| 138 | + mkdir -p ./release |
| 139 | + cp -r ./release-output/wwwroot/. ./release/ |
| 140 | +
|
| 141 | + # Disable Jekyll so _framework assets are served |
| 142 | + touch ./release/.nojekyll |
| 143 | +
|
| 144 | + # Fix index.html base href for GitHub Pages sub-path |
| 145 | + sed -i 's|<base href="/" />|<base href="/RFPAPP/" />|g' ./release/index.html |
| 146 | +
|
| 147 | + # Add 404.html for SPA client-side routing fallback |
| 148 | + cp ./release/index.html ./release/404.html |
| 149 | +
|
| 150 | + - name: Deploy to GitHub Pages |
| 151 | + uses: peaceiris/actions-gh-pages@v4 |
| 152 | + with: |
| 153 | + github_token: ${{ secrets.GITHUB_TOKEN }} |
| 154 | + publish_dir: ./release |
| 155 | + publish_branch: gh-pages |
| 156 | +``` |
| 157 | +
|
| 158 | +> **Note:** The `--base-path` flag was introduced in .NET 8. If `dotnet publish` does not automatically rewrite the `<base href>`, the `sed` step ensures the tag is patched correctly. |
| 159 | + |
| 160 | +### 2. Update `README.md` |
| 161 | + |
| 162 | +Add a GitHub Pages badge and live-demo link near the top of `README.md`, next to the existing badges: |
| 163 | + |
| 164 | +```markdown |
| 165 | +[](https://blazordata-net.github.io/RFPAPP/) |
| 166 | +``` |
| 167 | + |
| 168 | +And a dedicated section: |
| 169 | + |
| 170 | +```markdown |
| 171 | +### 🌐 Live Demo (GitHub Pages) |
| 172 | +[https://blazordata-net.github.io/RFPAPP/](https://blazordata-net.github.io/RFPAPP/) |
| 173 | +``` |
| 174 | + |
| 175 | +--- |
| 176 | + |
| 177 | +## GitHub Repository Settings |
| 178 | + |
| 179 | +After the first workflow run creates the `gh-pages` branch, the following setting must be configured once in the GitHub UI (or via the API): |
| 180 | + |
| 181 | +1. Go to **Settings → Pages**. |
| 182 | +2. Under **Source**, select **Deploy from a branch**. |
| 183 | +3. Choose branch: `gh-pages`, folder: `/ (root)`. |
| 184 | +4. Save. |
| 185 | + |
| 186 | +GitHub will then serve the contents of the `gh-pages` branch at `https://blazordata-net.github.io/RFPAPP/`. |
| 187 | + |
| 188 | +```mermaid |
| 189 | +sequenceDiagram |
| 190 | + participant Dev as Developer |
| 191 | + participant GH as GitHub Actions |
| 192 | + participant Pages as GitHub Pages CDN |
| 193 | +
|
| 194 | + Dev->>GH: git push origin main |
| 195 | + GH->>GH: Build & publish Blazor WASM |
| 196 | + GH->>GH: Prepare static output (index.html, 404.html, .nojekyll) |
| 197 | + GH->>Pages: Force-push to gh-pages branch |
| 198 | + Pages-->>Dev: Site live at blazordata-net.github.io/RFPAPP/ |
| 199 | +``` |
| 200 | + |
| 201 | +--- |
| 202 | + |
| 203 | +## SPA Routing: 404.html Redirect Pattern |
| 204 | + |
| 205 | +The `404.html` file uses a small JavaScript snippet to redirect the browser back to `index.html` while preserving the original path: |
| 206 | + |
| 207 | +```html |
| 208 | +<!DOCTYPE html> |
| 209 | +<html> |
| 210 | + <head> |
| 211 | + <meta charset="utf-8" /> |
| 212 | + <title>RFP Response Creator</title> |
| 213 | + <script> |
| 214 | + // GitHub Pages SPA redirect |
| 215 | + // Stores the current path in sessionStorage and redirects to / |
| 216 | + var pathSegmentsToKeep = 1; // number of path segments in the base path (/RFPAPP) |
| 217 | + var l = window.location; |
| 218 | + l.replace( |
| 219 | + l.protocol + '//' + l.hostname + (l.port ? ':' + l.port : '') + |
| 220 | + l.pathname.split('/').slice(0, 1 + pathSegmentsToKeep).join('/') + '/?/' + |
| 221 | + l.pathname.slice(1).split('/').slice(pathSegmentsToKeep).join('/').replace(/&/g, '~and~') + |
| 222 | + (l.search ? '&' + l.search.slice(1).replace(/&/g, '~and~') : '') + |
| 223 | + l.hash |
| 224 | + ); |
| 225 | + </script> |
| 226 | + </head> |
| 227 | + <body></body> |
| 228 | +</html> |
| 229 | +``` |
| 230 | + |
| 231 | +And in `index.html`, before `</body>`: |
| 232 | + |
| 233 | +```html |
| 234 | +<script> |
| 235 | + // GitHub Pages SPA redirect handler |
| 236 | + (function(l) { |
| 237 | + if (l.search[1] === '/') { |
| 238 | + var decoded = l.search.slice(1).split('&').map(function(s) { |
| 239 | + return s.replace(/~and~/g, '&'); |
| 240 | + }).join('?'); |
| 241 | + window.history.replaceState(null, null, |
| 242 | + l.pathname.slice(0, -1) + decoded + l.hash |
| 243 | + ); |
| 244 | + } |
| 245 | + }(window.location)); |
| 246 | +</script> |
| 247 | +``` |
| 248 | + |
| 249 | +--- |
| 250 | + |
| 251 | +## Summary Checklist |
| 252 | + |
| 253 | +- [x] Understand project structure (Blazor WASM client + ASP.NET Core host) |
| 254 | +- [ ] Create `.github/workflows/deploy-ghpages.yml` |
| 255 | +- [ ] Verify `.nojekyll`, `404.html`, and base-href rewriting in workflow |
| 256 | +- [ ] Update `README.md` with GitHub Pages badge and link |
| 257 | +- [ ] Enable GitHub Pages in repository **Settings → Pages** (branch: `gh-pages`) |
| 258 | +- [ ] Confirm live URL: `https://blazordata-net.github.io/RFPAPP/` |
0 commit comments