Skip to content

Commit a9381fb

Browse files
committed
Use static data snapshots to generate static pages in GH Action builds
1 parent 53edce6 commit a9381fb

11 files changed

Lines changed: 17788 additions & 36 deletions

File tree

.github/workflows/build-container.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,16 @@ jobs:
7575
working-directory: ./TechStacks.Client
7676
run: npm install
7777

78+
# Generate static JSON data files for build-time SSG
79+
# This fetches posts, technologies, and stacks from the API and saves them
80+
# to src/data/*.json so the Next.js build doesn't need to call APIs
81+
# - name: Generate static data
82+
# if: steps.check_client.outputs.client_exists == 'true'
83+
# working-directory: ./TechStacks.Client
84+
# env:
85+
# API_URL: https://react.techstacks.io
86+
# run: npm run generate-data
87+
7888
- name: Install x tool
7989
run: dotnet tool install -g x
8090

TechStacks.Client/.gitattributes

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Mark generated data files as generated for GitHub
2+
src/data/*.json linguist-generated=true
3+

TechStacks.Client/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
"scripts": {
88
"dev": "node server.js",
99
"dtos": "npx get-dtos ts",
10+
"generate-data": "node scripts/generate-static-data.mjs",
11+
"prebuild:local": "npm run generate-data",
1012
"build": "next build && cp -r dist/* ../TechStacks/wwwroot/ && cp dist/_next/static/chunks/*.css ../TechStacks/wwwroot/css/app.css",
1113
"build:prod": "next build",
1214
"start": "next start",
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Generate static JSON data files for build-time static site generation
4+
* This script fetches data from the API and saves it to ./src/data/*.json
5+
* so that GitHub Actions can build pages without calling APIs at build time.
6+
*/
7+
8+
import { writeFileSync, mkdirSync } from 'fs';
9+
import { fileURLToPath } from 'url';
10+
import { dirname, join } from 'path';
11+
12+
const __filename = fileURLToPath(import.meta.url);
13+
const __dirname = dirname(__filename);
14+
15+
// API base URL - can be overridden via environment variable
16+
const API_URL = process.env.API_URL || 'https://react.techstacks.io';
17+
18+
console.log(`Fetching data from: ${API_URL}`);
19+
20+
/**
21+
* Fetch data from API endpoint
22+
*/
23+
async function fetchApi(path) {
24+
const url = `${API_URL}${path}`;
25+
console.log(`Fetching: ${url}`);
26+
27+
const response = await fetch(url);
28+
if (!response.ok) {
29+
throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
30+
}
31+
32+
return await response.json();
33+
}
34+
35+
/**
36+
* Save data to JSON file
37+
*/
38+
function saveJson(filename, data) {
39+
const dataDir = join(__dirname, '../src/data');
40+
mkdirSync(dataDir, { recursive: true });
41+
42+
const filepath = join(dataDir, filename);
43+
writeFileSync(filepath, JSON.stringify(data, null, 2));
44+
console.log(`✓ Saved ${filename} (${JSON.stringify(data).length} bytes)`);
45+
}
46+
47+
/**
48+
* Main execution
49+
*/
50+
async function main() {
51+
try {
52+
console.log('Starting static data generation...\n');
53+
54+
// Fetch posts (for generateStaticParams)
55+
console.log('Fetching posts...');
56+
const postsResponse = await fetchApi('/api/QueryPosts?take=1000&orderBy=-created&fields=id,slug');
57+
saveJson('posts.json', {
58+
results: postsResponse.results || [],
59+
total: postsResponse.total || 0,
60+
generated: new Date().toISOString()
61+
});
62+
63+
// Fetch technologies (for generateStaticParams)
64+
console.log('\nFetching technologies...');
65+
const techResponse = await fetchApi('/api/GetAllTechnologies?include=total');
66+
saveJson('tech.json', {
67+
results: techResponse.results || [],
68+
total: techResponse.total || 0,
69+
generated: new Date().toISOString()
70+
});
71+
72+
// Fetch tech stacks (for generateStaticParams)
73+
console.log('\nFetching tech stacks...');
74+
const stacksResponse = await fetchApi('/api/GetAllTechnologyStacks?include=total');
75+
saveJson('stacks.json', {
76+
results: stacksResponse.results || [],
77+
total: stacksResponse.total || 0,
78+
generated: new Date().toISOString()
79+
});
80+
81+
console.log('\n✓ All static data generated successfully!');
82+
console.log(`\nGenerated files:`);
83+
console.log(` - src/data/posts.json (${postsResponse.results?.length || 0} posts)`);
84+
console.log(` - src/data/tech.json (${techResponse.results?.length || 0} technologies)`);
85+
console.log(` - src/data/stacks.json (${stacksResponse.results?.length || 0} stacks)`);
86+
87+
} catch (error) {
88+
console.error('\n✗ Error generating static data:', error.message);
89+
process.exit(1);
90+
}
91+
}
92+
93+
main();
94+

TechStacks.Client/src/app/posts/[id]/[slug]/page.tsx

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,22 @@
11
import PostDetailClient from './PostDetailClient';
2-
import { JsonServiceClient } from '@servicestack/client';
3-
import * as dtos from '@/shared/dtos';
2+
import postsData from '@/data/posts.json';
43

5-
// Generate static pages for all posts
4+
// Generate static pages for all posts from static data
65
export async function generateStaticParams() {
76
try {
8-
// Create a client with absolute URL for build-time fetching
9-
const buildClient = new JsonServiceClient('https://techstacks.io');
10-
const response = await buildClient.get(
11-
new dtos.QueryPosts({
12-
take: 1000,
13-
orderBy: '-created',
14-
fields: 'id,slug'
15-
})
16-
);
17-
const posts = response.results || [];
7+
// Read from static JSON data generated at build time
8+
const posts = postsData.results || [];
189

19-
console.log(`Generating ${posts.length} post pages`);
10+
console.log(`Generating ${posts.length} post pages from static data (generated: ${postsData.generated})`);
2011

2112
// Generate params for all posts
2213
return posts.map((post: any) => ({
2314
id: post.id.toString(),
2415
slug: post.slug,
2516
}));
2617
} catch (error) {
27-
console.error('Failed to fetch posts for static generation:', error);
28-
// Fallback to placeholder if API is unavailable during build
18+
console.error('Failed to load posts from static data:', error);
19+
// Fallback to placeholder if data is unavailable
2920
return [{ id: '0', slug: '_placeholder' }];
3021
}
3122
}

TechStacks.Client/src/app/stacks/[slug]/page.tsx

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,21 @@
11
import TechStackDetailClient from './TechStackDetailClient';
2-
import { JsonServiceClient } from '@servicestack/client';
3-
import * as dtos from '@/shared/dtos';
2+
import stacksData from '@/data/stacks.json';
43

5-
// Generate static pages for all tech stacks
4+
// Generate static pages for all tech stacks from static data
65
export async function generateStaticParams() {
76
try {
8-
// Create a client with absolute URL for build-time fetching
9-
const buildClient = new JsonServiceClient('https://techstacks.io');
10-
const response = await buildClient.get(new dtos.GetAllTechnologyStacks(), { include: 'total' });
11-
const stacks = response.results || [];
7+
// Read from static JSON data generated at build time
8+
const stacks = stacksData.results || [];
129

13-
console.log(`Generating ${stacks.length} tech stack pages`);
10+
console.log(`Generating ${stacks.length} tech stack pages from static data (generated: ${stacksData.generated})`);
1411

1512
// Generate params for all tech stacks
1613
return stacks.map((stack: any) => ({
1714
slug: stack.slug,
1815
}));
1916
} catch (error) {
20-
console.error('Failed to fetch tech stacks for static generation:', error);
21-
// Fallback to placeholder if API is unavailable during build
17+
console.error('Failed to load tech stacks from static data:', error);
18+
// Fallback to placeholder if data is unavailable
2219
return [{ slug: '_placeholder' }];
2320
}
2421
}

TechStacks.Client/src/app/tech/[slug]/page.tsx

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,21 @@
11
import TechnologyDetailClient from './TechnologyDetailClient';
2-
import { JsonServiceClient } from '@servicestack/client';
3-
import * as dtos from '@/shared/dtos';
2+
import techData from '@/data/tech.json';
43

5-
// Generate static pages for all technologies
4+
// Generate static pages for all technologies from static data
65
export async function generateStaticParams() {
76
try {
8-
// Create a client with absolute URL for build-time fetching
9-
const buildClient = new JsonServiceClient('https://techstacks.io');
10-
const response = await buildClient.get(new dtos.GetAllTechnologies(), { include: 'total' });
11-
const technologies = response.results || [];
7+
// Read from static JSON data generated at build time
8+
const technologies = techData.results || [];
129

13-
console.log(`Generating ${technologies.length} technology pages`);
10+
console.log(`Generating ${technologies.length} technology pages from static data (generated: ${techData.generated})`);
1411

1512
// Generate params for all technologies
1613
return technologies.map((tech: any) => ({
1714
slug: tech.slug,
1815
}));
1916
} catch (error) {
20-
console.error('Failed to fetch technologies for static generation:', error);
21-
// Fallback to placeholder if API is unavailable during build
17+
console.error('Failed to load technologies from static data:', error);
18+
// Fallback to placeholder if data is unavailable
2219
return [{ slug: '_placeholder' }];
2320
}
2421
}

0 commit comments

Comments
 (0)