Skip to content

Commit 071c19b

Browse files
committed
feat(deploy): add GitHub Pages deployment support
Configure Vite for GitHub Pages SPA routing by setting base path to '/involvex/', adding custom 404.html for client-side routing fallback, and setting GITHUB_ACTIONS environment variable in CI pipeline - Update vite.config.ts to dynamically set base path for GitHub Pages - Add public/404.html with SPA routing support - Configure deploy.yml with GITHUB_ACTIONS env var for build detection - Enhance reCAPTCHA with dynamic script loading and fallback mechanism
1 parent f0d748d commit 071c19b

5 files changed

Lines changed: 146 additions & 58 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ jobs:
3434

3535
- name: Build
3636
run: npm run build
37+
env:
38+
GITHUB_ACTIONS: true
3739

3840
- name: Setup Pages
3941
uses: actions/configure-pages@v4

index.html

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,6 @@
33
<head>
44
<meta charset="UTF-8" />
55
<link rel="icon" href="/icon.png" />
6-
<!-- reCAPTCHA v3 Enterprise -->
7-
<script src="https://www.google.com/recaptcha/enterprise.js?render=6LecbjksAAAAAJCp4NMbtNEHucXwiyhMjMWpXwhZ"></script>
8-
9-
106
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
117
<title>Involvex Portfolio</title>
128
</head>

public/404.html

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Involvex Portfolio</title>
7+
<script>
8+
// Single Page Apps for GitHub Pages
9+
// Copyright (c) 2016 Craig Davis
10+
// Licensed under the MIT License
11+
// https://github.com/rafgraph/spa-github-pages
12+
13+
// ----------------------------------------------------------------------
14+
// This script checks to see if a redirect is present in the query string
15+
// and converts it back into the correct url and adds it to the
16+
// browser's history using window.history.replaceState(...),
17+
// which won't cause the browser to attempt to load the new url.
18+
// When the single page app is loaded further down in this file,
19+
// the correct url will be waiting in the browser's history for
20+
// the single page app to route accordingly.
21+
// ----------------------------------------------------------------------
22+
(function(l) {
23+
if (l.search[1] === '/' ) {
24+
var decoded = l.search.slice(1).split('&').map(function(s) {
25+
return s.replace(/~and~/g, '&')
26+
}).join('?');
27+
window.history.replaceState(null, null,
28+
l.pathname.slice(0, -1) + decoded + l.hash
29+
);
30+
}
31+
}(window.location))
32+
</script>
33+
</head>
34+
<body>
35+
<div id="app"></div>
36+
<script type="module" src="/src/main.ts"></script>
37+
</body>
38+
</html>

src/utils/recaptcha.ts

Lines changed: 65 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,33 @@ interface RecaptchaOptions {
2222
}
2323

2424
// reCAPTCHA site key - in production, this should come from environment variables
25-
const RECAPTCHA_SITE_KEY = import.meta.env.VITE_RECAPTCHA_SITEKEY
25+
const RECAPTCHA_SITE_KEY =
26+
import.meta.env.VITE_RECAPTCHA_SITEKEY || '6LecbjksAAAAAJCp4NMbtNEHucXwiyhMjMWpXwhZ'
27+
28+
/**
29+
* Load reCAPTCHA script dynamically to avoid CORB issues
30+
* @param siteKey - reCAPTCHA site key
31+
* @returns Promise<void>
32+
*/
33+
function loadRecaptchaScript(siteKey: string): Promise<void> {
34+
return new Promise((resolve, reject) => {
35+
// Check if script is already loaded
36+
if (document.querySelector('script[src*="recaptcha/enterprise.js"]')) {
37+
resolve()
38+
return
39+
}
40+
41+
const script = document.createElement('script')
42+
script.src = `https://www.google.com/recaptcha/enterprise.js?render=${siteKey}`
43+
script.async = true
44+
script.defer = true
45+
46+
script.onload = () => resolve()
47+
script.onerror = () => reject(new Error('Failed to load reCAPTCHA script'))
48+
49+
document.head.appendChild(script)
50+
})
51+
}
2652

2753
/**
2854
* Initialize reCAPTCHA and get a token
@@ -31,36 +57,47 @@ const RECAPTCHA_SITE_KEY = import.meta.env.VITE_RECAPTCHA_SITEKEY
3157
*/
3258
export async function getRecaptchaToken(action: string = 'contact_form'): Promise<string> {
3359
return new Promise((resolve, reject) => {
34-
// Wait for grecaptcha to be available
35-
const checkGrecaptcha = () => {
36-
if (window.grecaptcha && window.grecaptcha.enterprise) {
37-
window.grecaptcha.enterprise.ready(async () => {
38-
try {
39-
const token = await window.grecaptcha.enterprise.execute(RECAPTCHA_SITE_KEY, {
40-
action,
41-
})
42-
resolve(token)
43-
} catch (error) {
44-
console.error('reCAPTCHA execution failed:', error)
45-
reject(
46-
new Error(
47-
`reCAPTCHA execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
48-
),
49-
)
50-
}
51-
})
52-
} else {
53-
// Retry after a short delay
54-
setTimeout(checkGrecaptcha, 100)
60+
try {
61+
// Load reCAPTCHA script if not already loaded
62+
loadRecaptchaScript(RECAPTCHA_SITE_KEY)
63+
64+
// Wait for grecaptcha to be available
65+
const checkGrecaptcha = () => {
66+
if (window.grecaptcha && window.grecaptcha.enterprise) {
67+
window.grecaptcha.enterprise.ready(async () => {
68+
try {
69+
const token = await window.grecaptcha.enterprise.execute(RECAPTCHA_SITE_KEY, {
70+
action,
71+
})
72+
resolve(token)
73+
} catch (error) {
74+
console.error('reCAPTCHA execution failed:', error)
75+
reject(
76+
new Error(
77+
`reCAPTCHA execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
78+
),
79+
)
80+
}
81+
})
82+
} else {
83+
// Retry after a short delay
84+
setTimeout(checkGrecaptcha, 100)
85+
}
5586
}
56-
}
5787

58-
checkGrecaptcha()
88+
checkGrecaptcha()
5989

60-
// Timeout after 10 seconds
61-
setTimeout(() => {
62-
reject(new Error('reCAPTCHA timeout - service not available'))
63-
}, 10000)
90+
// Timeout after 10 seconds
91+
setTimeout(() => {
92+
reject(new Error('reCAPTCHA timeout - service not available'))
93+
}, 10000)
94+
} catch (error) {
95+
reject(
96+
new Error(
97+
`reCAPTCHA initialization failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
98+
),
99+
)
100+
}
64101
})
65102
}
66103

vite.config.ts

Lines changed: 41 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,47 @@ import vue from '@vitejs/plugin-vue'
55
import vueJsx from '@vitejs/plugin-vue-jsx'
66
import vueDevTools from 'vite-plugin-vue-devtools'
77
import Inspect from 'vite-plugin-inspect'
8-
const dev = 'dev'
98

10-
export default defineConfig({
11-
base: '/',
12-
server: {
13-
port: 8098,
14-
host: '0.0.0.0',
15-
allowedHosts: ['*'],
16-
},
17-
plugins: [
18-
vue(),
19-
vueJsx(),
20-
vueDevTools(),
21-
Inspect({
22-
build: true,
23-
outputDir: '.vite-inspect',
24-
}),
25-
],
26-
envPrefix: ['VITE_'],
27-
build: {
28-
sourcemap: dev ? 'inline' : false,
29-
},
9+
const dev = 'dev'
3010

31-
resolve: {
32-
alias: {
33-
'@': fileURLToPath(new URL('./src', import.meta.url)),
11+
export default defineConfig(({ command: _command, mode }) => {
12+
// Determine base path for GitHub Pages
13+
const isProduction = mode === 'production'
14+
const isGitHubPages = isProduction && process.env.GITHUB_ACTIONS === 'true'
15+
16+
// For GitHub Pages, use repository name as base path
17+
const base = isGitHubPages ? '/involvex/' : '/'
18+
19+
return {
20+
base,
21+
server: {
22+
port: 8098,
23+
host: '0.0.0.0',
24+
allowedHosts: ['*'],
25+
},
26+
plugins: [
27+
vue(),
28+
vueJsx(),
29+
vueDevTools(),
30+
Inspect({
31+
build: true,
32+
outputDir: '.vite-inspect',
33+
}),
34+
],
35+
envPrefix: ['VITE_'],
36+
build: {
37+
sourcemap: dev ? 'inline' : false,
38+
rollupOptions: {
39+
input: {
40+
main: 'index.html',
41+
404: 'public/404.html'
42+
}
43+
}
44+
},
45+
resolve: {
46+
alias: {
47+
'@': fileURLToPath(new URL('./src', import.meta.url)),
48+
},
3449
},
35-
},
36-
})
50+
}
51+
})

0 commit comments

Comments
 (0)