Skip to content

Commit 09471b2

Browse files
committed
feat: enable GitHub Pages preview deployments for all branches/PRs
- Trigger workflow on all branches and PRs (not just main) - Deploy main to root, other branches to preview/<branch>/ subdirectory - Auto-comment preview URL on PRs - Add cleanup workflow to remove previews when PRs close - Add dynamic basePath/assetPrefix support via NEXT_BASE_PATH env var - Switch from deploy-pages API to peaceiris/actions-gh-pages for multi-preview support
1 parent e4ac7d0 commit 09471b2

3 files changed

Lines changed: 133 additions & 37 deletions

File tree

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: 🧹 Cleanup GitHub Pages Preview
2+
3+
on:
4+
pull_request:
5+
types: [closed]
6+
7+
permissions:
8+
contents: write
9+
10+
jobs:
11+
cleanup:
12+
name: 🗑 Remove Preview Deployment
13+
runs-on: ubuntu-latest
14+
15+
steps:
16+
- name: 🔍 Checkout gh-pages branch
17+
uses: actions/checkout@v4
18+
with:
19+
ref: gh-pages
20+
fetch-depth: 1
21+
22+
- name: 🧹 Remove preview directory
23+
run: |
24+
BRANCH="${{ github.head_ref }}"
25+
SAFE_BRANCH=$(echo "$BRANCH" | sed 's/[^a-zA-Z0-9._-]/-/g')
26+
PREVIEW_DIR="preview/${SAFE_BRANCH}"
27+
28+
if [ -d "$PREVIEW_DIR" ]; then
29+
echo "Removing preview directory: $PREVIEW_DIR"
30+
git config user.name "github-actions[bot]"
31+
git config user.email "github-actions[bot]@users.noreply.github.com"
32+
git rm -rf "$PREVIEW_DIR"
33+
git commit -m "🧹 Remove preview for closed PR #${{ github.event.pull_request.number }} ($BRANCH)"
34+
git push
35+
else
36+
echo "No preview directory found at $PREVIEW_DIR, skipping cleanup."
37+
fi

.github/workflows/nextjs-static-gh-pages.yml

Lines changed: 81 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,58 @@ name: 🚀 Deploy Static Next.js to GitHub Pages
22

33
on:
44
push:
5-
branches: ['main'] # Triggers on push to main branch
5+
branches: ['**'] # Triggers on push to any branch
6+
pull_request:
7+
types: [opened, synchronize, reopened]
68
workflow_dispatch: # Allows manual triggering
79

810
permissions:
9-
contents: read
10-
pages: write
11-
id-token: write
11+
contents: write
12+
pull-requests: write
1213

1314
concurrency:
14-
group: 'pages'
15-
cancel-in-progress: false
15+
group: 'pages-${{ github.head_ref || github.ref_name }}'
16+
cancel-in-progress: true
1617

1718
jobs:
18-
build:
19-
name: 🏗 Build Static Site
19+
build-and-deploy:
20+
name: 🏗 Build & Deploy
2021
runs-on: ubuntu-latest
2122

22-
23-
2423
steps:
2524
- name: 🔍 Checkout repository
2625
uses: actions/checkout@v4
2726

27+
- name: 🔧 Set deployment variables
28+
id: vars
29+
run: |
30+
REPO_NAME="${{ github.event.repository.name }}"
31+
32+
# Get the branch name (works for both push and PR events)
33+
if [ "${{ github.event_name }}" = "pull_request" ]; then
34+
BRANCH="${{ github.head_ref }}"
35+
else
36+
BRANCH="${{ github.ref_name }}"
37+
fi
38+
39+
# Sanitize branch name for use in URL paths
40+
SAFE_BRANCH=$(echo "$BRANCH" | sed 's/[^a-zA-Z0-9._-]/-/g')
41+
42+
if [ "$BRANCH" = "main" ]; then
43+
echo "base_path=/${REPO_NAME}" >> $GITHUB_OUTPUT
44+
echo "dest_dir=" >> $GITHUB_OUTPUT
45+
echo "keep_files=false" >> $GITHUB_OUTPUT
46+
echo "preview_url=https://${{ github.repository_owner }}.github.io/${REPO_NAME}/" >> $GITHUB_OUTPUT
47+
else
48+
echo "base_path=/${REPO_NAME}/preview/${SAFE_BRANCH}" >> $GITHUB_OUTPUT
49+
echo "dest_dir=preview/${SAFE_BRANCH}" >> $GITHUB_OUTPUT
50+
echo "keep_files=true" >> $GITHUB_OUTPUT
51+
echo "preview_url=https://${{ github.repository_owner }}.github.io/${REPO_NAME}/preview/${SAFE_BRANCH}/" >> $GITHUB_OUTPUT
52+
fi
53+
54+
echo "branch=$BRANCH" >> $GITHUB_OUTPUT
55+
echo "safe_branch=$SAFE_BRANCH" >> $GITHUB_OUTPUT
56+
2857
- name: 🔎 Detect package manager
2958
id: detect-pm
3059
run: |
@@ -48,12 +77,6 @@ jobs:
4877
node-version: '20'
4978
cache: ${{ env.manager }}
5079

51-
- name: 🌐 Setup GitHub Pages
52-
uses: actions/configure-pages@v5
53-
54-
# NOTE: We do NOT need to create .env or secrets here, as we provide them directly to the build step below.
55-
56-
5780
- name: 🚫 Ephemerally delete server/api files
5881
run: |
5982
echo "Deleting src/app/api, src/server, src/proxy.ts, and src/app/jobs/[id] for static build..."
@@ -66,24 +89,51 @@ jobs:
6689
env:
6790
NEXT_OUTPUT_MODE: export
6891
GITHUB_PAGES: 1
92+
NEXT_BASE_PATH: ${{ steps.vars.outputs.base_path }}
6993
run: |
7094
echo "Building static files for GitHub Pages..."
95+
echo "Base path: $NEXT_BASE_PATH"
96+
echo "Preview URL: ${{ steps.vars.outputs.preview_url }}"
7197
pnpm build
7298
touch out/.nojekyll
7399
74-
- name: 📤 Upload static site
75-
uses: actions/upload-pages-artifact@v3
100+
- name: 🚀 Deploy to GitHub Pages
101+
uses: peaceiris/actions-gh-pages@v4
76102
with:
77-
path: ./out # With 'output: export', Next.js automatically puts files here.
78-
79-
deploy:
80-
name: 🚀 Deploy to GitHub Pages
81-
environment:
82-
name: github-pages
83-
url: ${{ steps.deployment.outputs.page_url }}
84-
runs-on: ubuntu-latest
85-
needs: build
86-
steps:
87-
- name: 🌍 Deploy
88-
id: deployment
89-
uses: actions/deploy-pages@v4
103+
github_token: ${{ secrets.GITHUB_TOKEN }}
104+
publish_dir: ./out
105+
destination_dir: ${{ steps.vars.outputs.dest_dir }}
106+
keep_files: ${{ steps.vars.outputs.keep_files }}
107+
108+
- name: 💬 Comment preview URL on PR
109+
if: github.event_name == 'pull_request'
110+
uses: actions/github-script@v7
111+
with:
112+
script: |
113+
const url = '${{ steps.vars.outputs.preview_url }}';
114+
const sha = context.sha.substring(0, 7);
115+
const body = `🚀 **Preview deployment ready!**\n\n📎 **Preview URL:** ${url}\n\n_Deployed from commit \`${sha}\`_`;
116+
117+
const { data: comments } = await github.rest.issues.listComments({
118+
owner: context.repo.owner,
119+
repo: context.repo.repo,
120+
issue_number: context.issue.number,
121+
});
122+
123+
const botComment = comments.find(c => c.body.includes('Preview deployment ready!'));
124+
125+
if (botComment) {
126+
await github.rest.issues.updateComment({
127+
owner: context.repo.owner,
128+
repo: context.repo.repo,
129+
comment_id: botComment.id,
130+
body,
131+
});
132+
} else {
133+
await github.rest.issues.createComment({
134+
owner: context.repo.owner,
135+
repo: context.repo.repo,
136+
issue_number: context.issue.number,
137+
body,
138+
});
139+
}

next.config.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@ const isStaticExport = process.env.NEXT_OUTPUT_MODE === 'export'
66

77
console.log(`\n Next.js static export mode: ${isStaticExport}`)
88

9-
const repoName = 'codebuilder-frontend';
10-
const isGithubPages = !!process.env.GITHUB_PAGES;
9+
const repoName = 'codebuilder-frontend'
10+
const isGithubPages = !!process.env.GITHUB_PAGES
11+
// Set by the GitHub Pages workflow to support preview deployments in subdirectories.
12+
// e.g. "/codebuilder-frontend" for main, "/codebuilder-frontend/preview/my-branch" for previews.
13+
const ghPagesBasePath = process.env.NEXT_BASE_PATH || ''
1114

1215
const nextConfig = {
1316
/**
@@ -21,10 +24,16 @@ const nextConfig = {
2124

2225
// Conditionally set the output mode for the build.
2326
output: isStaticExport ? 'export' : undefined,
24-
//basePath: //isGithubPages ? `/${repoName}` : '',
25-
//assetPrefix: //isGithubPages ? `/${repoName}/` : '',
27+
basePath: ghPagesBasePath || undefined,
28+
assetPrefix: ghPagesBasePath ? `${ghPagesBasePath}/` : undefined,
2629

27-
allowedDevOrigins: ['https://api.codebuilder.org', 'https://new.codebuilder.org', 'https://new.codebuilder.org:443', 'https://dev.codebuilder.org', 'https://dev.codebuilder.org:443'], // resolves the CORS warning
30+
allowedDevOrigins: [
31+
'https://api.codebuilder.org',
32+
'https://new.codebuilder.org',
33+
'https://new.codebuilder.org:443',
34+
'https://dev.codebuilder.org',
35+
'https://dev.codebuilder.org:443',
36+
], // resolves the CORS warning
2837

2938
// Note: If you are using next/image, you may need to add an
3039
// unoptimized: true flag here if you are not using a custom loader.
@@ -65,7 +74,7 @@ const nextConfig = {
6574

6675
return config
6776
},
68-
} as unknown as NextConfig;
77+
} as unknown as NextConfig
6978

7079
// // Only include page.* files for static export, include route.* for all other builds
7180
// if (isStaticExport) {

0 commit comments

Comments
 (0)