Skip to content

Commit 87770c2

Browse files
committed
feat: implement SPA routing for GitHub Pages deployment with verification script
1 parent 4f1058b commit 87770c2

File tree

5 files changed

+190
-5
lines changed

5 files changed

+190
-5
lines changed

.github/workflows/deploy.yml

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ on:
55
branches: [ main ]
66
workflow_dispatch:
77

8+
# This workflow builds a Flutter web app with SPA routing support for GitHub Pages
9+
# It uses a custom 404.html file and redirect scripts to handle client-side routing
10+
811
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
912
permissions:
1013
contents: read
@@ -37,13 +40,31 @@ jobs:
3740
- name: Copy shaders to assets
3841
run: dart scripts/copy_shaders.dart
3942

40-
- name: Build web
41-
run: flutter build web --release --base-href /shaders_gallery/
43+
- name: Build for GitHub Pages (with SPA routing)
44+
run: dart scripts/build_github_pages.dart
4245

43-
- name: Create empty shaders/index.html
46+
- name: Verify SPA routing files
4447
run: |
45-
mkdir -p build/web/shader
46-
echo "<!DOCTYPE html><html><head></head><body></body></html>" > build/web/shader/index.html
48+
echo "📋 Verifying GitHub Pages SPA setup..."
49+
if [ -f "build/web/404.html" ]; then
50+
echo "✅ 404.html found"
51+
else
52+
echo "❌ 404.html missing"
53+
exit 1
54+
fi
55+
if [ -f "build/web/.nojekyll" ]; then
56+
echo "✅ .nojekyll found"
57+
else
58+
echo "❌ .nojekyll missing"
59+
exit 1
60+
fi
61+
if grep -q "/shaders_gallery/" build/web/index.html; then
62+
echo "✅ Base href correctly set"
63+
else
64+
echo "❌ Base href not found in index.html"
65+
exit 1
66+
fi
67+
echo "🎉 All SPA routing files verified!"
4768
4869
- name: Setup Pages
4970
uses: actions/configure-pages@v5

scripts/build_for_github_pages.sh

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/bin/bash
2+
3+
# Flutter Web Build Script for GitHub Pages
4+
# This script builds the Flutter web app and sets up the necessary files for SPA routing on GitHub Pages
5+
6+
echo "Building Flutter web app for GitHub Pages..."
7+
8+
# Build the Flutter web app with the correct base href
9+
flutter build web --base-href="/shaders_gallery/"
10+
11+
# Copy the 404.html file to the build directory
12+
echo "Copying 404.html to build directory..."
13+
cp web/404.html build/web/404.html
14+
15+
# Create .nojekyll file to disable Jekyll processing on GitHub Pages
16+
echo "Creating .nojekyll file..."
17+
touch build/web/.nojekyll
18+
19+
echo "Build complete! The files in build/web/ are ready for deployment to GitHub Pages."
20+
echo ""
21+
echo "To deploy:"
22+
echo "1. Copy all files from build/web/ to your GitHub Pages repository"
23+
echo "2. Make sure your repository settings point to the correct branch/folder"
24+
echo "3. Your site will be available at: https://roszkowski.dev/shaders_gallery/"

scripts/build_github_pages.dart

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#!/usr/bin/env dart
2+
3+
import 'dart:io';
4+
5+
/// Build script for deploying Flutter web app to GitHub Pages
6+
/// This handles the SPA routing configuration needed for GitHub Pages
7+
void main(List<String> args) async {
8+
const baseHref = '/shaders_gallery/';
9+
10+
// Check if we should build in release mode (default for CI)
11+
final isRelease = args.contains('--release') ||
12+
Platform.environment['CI'] == 'true' ||
13+
args.isEmpty; // Default to release if no args provided
14+
15+
print('🚀 Building Flutter web app for GitHub Pages...');
16+
print('📍 Base href: $baseHref');
17+
print('🏗️ Build mode: ${isRelease ? 'release' : 'debug'}');
18+
19+
// Build the Flutter web app
20+
final buildArgs = [
21+
'build',
22+
'web',
23+
'--base-href=$baseHref',
24+
if (isRelease) '--release',
25+
];
26+
27+
final buildResult = await Process.run(
28+
'flutter',
29+
buildArgs,
30+
workingDirectory: Directory.current.path,
31+
);
32+
33+
if (buildResult.exitCode != 0) {
34+
print('❌ Flutter build failed:');
35+
print(buildResult.stderr);
36+
exit(1);
37+
}
38+
39+
print('✅ Flutter build completed successfully');
40+
41+
// Copy 404.html to build directory
42+
print('📄 Copying 404.html to build directory...');
43+
final source404 = File('web/404.html');
44+
final dest404 = File('build/web/404.html');
45+
46+
if (await source404.exists()) {
47+
await source404.copy(dest404.path);
48+
print('✅ 404.html copied successfully');
49+
} else {
50+
print('⚠️ Warning: web/404.html not found');
51+
}
52+
53+
// Create .nojekyll file
54+
print('📄 Creating .nojekyll file...');
55+
final nojekyll = File('build/web/.nojekyll');
56+
await nojekyll.writeAsString('');
57+
print('✅ .nojekyll file created');
58+
59+
print('');
60+
print('🎉 Build complete! Files in build/web/ are ready for deployment.');
61+
print('');
62+
print('📋 Deployment instructions:');
63+
print('1. Copy all files from build/web/ to your GitHub Pages repository');
64+
print('2. Ensure repository settings point to the correct branch/folder');
65+
print('3. Your site will be available at: https://roszkowski.dev$baseHref');
66+
print('');
67+
print('🔗 Test URLs that should work after deployment:');
68+
print(' • https://roszkowski.dev$baseHref');
69+
print(' • https://roszkowski.dev${baseHref}shader/noise-overlay-shader');
70+
print(' • https://roszkowski.dev${baseHref}shader/crt-shader');
71+
}

web/404.html

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Shader Gallery</title>
6+
<script type="text/javascript">
7+
// Single Page Apps for GitHub Pages
8+
// MIT License
9+
// https://github.com/rafgraph/spa-github-pages
10+
// This script takes the current url and converts the path and query
11+
// string into just a query string, and then redirects the browser
12+
// to the new url with only a query string and hash fragment,
13+
// e.g. https://www.foo.tld/one/two?a=b&c=d#qwe, becomes
14+
// https://www.foo.tld/?/one/two&a=b~and~c=d#qwe
15+
// Note: this 404.html file must be at least 512 bytes for it to work
16+
// with Internet Explorer (it is currently > 512 bytes)
17+
18+
// If you're creating a Project Pages site and NOT using a custom domain,
19+
// then set pathSegmentsToKeep to 1 (enterprise users may need to set it to > 1).
20+
// This way the code will only replace the route part and not the real folder.
21+
// For example, if your site's address is username.github.io/repo-name,
22+
// then you should set pathSegmentsToKeep to 1, so that paths like
23+
// username.github.io/repo-name/one/two will redirect to
24+
// username.github.io/repo-name/?/one/two instead of
25+
// username.github.io/?/repo-name/one/two
26+
var pathSegmentsToKeep = 1;
27+
28+
var l = window.location;
29+
l.replace(
30+
l.protocol + '//' + l.hostname + (l.port ? ':' + l.port : '') +
31+
l.pathname.split('/').slice(0, 1 + pathSegmentsToKeep).join('/') + '/?/' +
32+
l.pathname.slice(1).split('/').slice(pathSegmentsToKeep).join('/').replace(/&/g, '~and~') +
33+
(l.search ? '&' + l.search.slice(1).replace(/&/g, '~and~') : '') +
34+
l.hash
35+
);
36+
37+
</script>
38+
</head>
39+
<body>
40+
<noscript>
41+
<h1>Shader Gallery</h1>
42+
<p>This site requires JavaScript to be enabled.</p>
43+
<p><a href="/">Return to Home</a></p>
44+
</noscript>
45+
</body>
46+
</html>

web/index.html

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,29 @@
2020
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
2121
<meta name="description" content="A new Flutter project.">
2222

23+
<script type="text/javascript">
24+
// Single Page Apps for GitHub Pages
25+
// MIT License
26+
// https://github.com/rafgraph/spa-github-pages
27+
// This script checks to see if a redirect is present in the query string,
28+
// converts it back into the correct url and adds it to the
29+
// browser's history using window.history.replaceState(...),
30+
// which won't cause the browser to attempt to load the new url.
31+
// When the single page app is loaded further down in this file,
32+
// the correct url will be waiting in the browser's history for
33+
// the single page app to route accordingly.
34+
(function(l) {
35+
if (l.search[1] === '/' ) {
36+
var decoded = l.search.slice(1).split('&').map(function(s) {
37+
return s.replace(/~and~/g, '&')
38+
}).join('?');
39+
window.history.replaceState(null, null,
40+
l.pathname.slice(0, -1) + decoded + l.hash
41+
);
42+
}
43+
}(window.location))
44+
</script>
45+
2346
<!-- iOS meta tags & icons -->
2447
<meta name="mobile-web-app-capable" content="yes">
2548
<meta name="apple-mobile-web-app-status-bar-style" content="black">

0 commit comments

Comments
 (0)