Switch to static export with build-time data fetching
- Change from server-side rendering to static export - Fetch data at build time and save as JSON - Remove API routes and tests, use static JSON file - Switch back to WEB platform from WEB_COMPUTE - Update build test for static export - Exclude out directory from security scan - Much simpler and more reliable approach
This commit is contained in:
@@ -9,11 +9,10 @@ frontend:
|
|||||||
- env | grep CONFLUENCE | sed 's/=.*/=***/'
|
- env | grep CONFLUENCE | sed 's/=.*/=***/'
|
||||||
build:
|
build:
|
||||||
commands:
|
commands:
|
||||||
|
- npx tsx scripts/fetch-data.js
|
||||||
- npm run build
|
- npm run build
|
||||||
- cp -r .next/static .next/standalone/.next/
|
|
||||||
- cp .next/standalone/.next/required-server-files.json .next/standalone/
|
|
||||||
artifacts:
|
artifacts:
|
||||||
baseDirectory: .next/standalone
|
baseDirectory: out
|
||||||
files:
|
files:
|
||||||
- '**/*'
|
- '**/*'
|
||||||
cache:
|
cache:
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
import { NextResponse } from 'next/server';
|
|
||||||
import { fetchFromConfluence } from '../../../src/server/confluence';
|
|
||||||
|
|
||||||
export const runtime = 'nodejs';
|
|
||||||
|
|
||||||
export async function GET() {
|
|
||||||
try {
|
|
||||||
// Get environment variables from server-side only
|
|
||||||
const env = {
|
|
||||||
CONFLUENCE_API_URL: process.env.CONFLUENCE_API_URL,
|
|
||||||
CONFLUENCE_TOKEN: process.env.CONFLUENCE_TOKEN,
|
|
||||||
CONFLUENCE_PAGE_ID: process.env.CONFLUENCE_PAGE_ID,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Validate environment variables
|
|
||||||
if (!env.CONFLUENCE_API_URL || !env.CONFLUENCE_TOKEN || !env.CONFLUENCE_PAGE_ID) {
|
|
||||||
console.error('Missing Confluence environment variables');
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Server configuration error' },
|
|
||||||
{ status: 500 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const filaments = await fetchFromConfluence(env);
|
|
||||||
|
|
||||||
return NextResponse.json(filaments, {
|
|
||||||
headers: {
|
|
||||||
'Cache-Control': 'public, s-maxage=300, stale-while-revalidate=600',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error('API Error:', error);
|
|
||||||
// Never expose internal error details to client
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Failed to fetch filaments' },
|
|
||||||
{ status: 500 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -34,7 +34,7 @@ export default function Home() {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
const response = await axios.get('/api/filaments');
|
const response = await axios.get('/data.json');
|
||||||
setFilaments(response.data);
|
setFilaments(response.data);
|
||||||
setLastUpdate(new Date());
|
setLastUpdate(new Date());
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
output: 'standalone',
|
output: 'export',
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = nextConfig
|
module.exports = nextConfig
|
||||||
36
scripts/fetch-data.js
Normal file
36
scripts/fetch-data.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const { fetchFromConfluence } = require('../src/server/confluence.ts');
|
||||||
|
|
||||||
|
async function fetchData() {
|
||||||
|
console.log('Fetching data from Confluence...');
|
||||||
|
|
||||||
|
const env = {
|
||||||
|
CONFLUENCE_API_URL: process.env.CONFLUENCE_API_URL,
|
||||||
|
CONFLUENCE_TOKEN: process.env.CONFLUENCE_TOKEN,
|
||||||
|
CONFLUENCE_PAGE_ID: process.env.CONFLUENCE_PAGE_ID
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await fetchFromConfluence(env);
|
||||||
|
|
||||||
|
// Create public directory if it doesn't exist
|
||||||
|
const publicDir = path.join(__dirname, '..', 'public');
|
||||||
|
if (!fs.existsSync(publicDir)) {
|
||||||
|
fs.mkdirSync(publicDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write data to public directory
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(publicDir, 'data.json'),
|
||||||
|
JSON.stringify(data, null, 2)
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`✅ Fetched ${data.length} filaments`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to fetch data:', error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchData();
|
||||||
@@ -18,6 +18,7 @@ const excludePatterns = [
|
|||||||
/\.next/,
|
/\.next/,
|
||||||
/dist/,
|
/dist/,
|
||||||
/build/,
|
/build/,
|
||||||
|
/out/,
|
||||||
/terraform\.tfvars$/,
|
/terraform\.tfvars$/,
|
||||||
/\.env/,
|
/\.env/,
|
||||||
/security-check\.js$/,
|
/security-check\.js$/,
|
||||||
|
|||||||
@@ -26,8 +26,7 @@ try {
|
|||||||
// Check for required files
|
// Check for required files
|
||||||
console.log('\n🔍 Checking build output...');
|
console.log('\n🔍 Checking build output...');
|
||||||
const checks = [
|
const checks = [
|
||||||
{ path: '.next/standalone/server.js', name: 'Standalone server' },
|
{ path: 'out/index.html', name: 'Static HTML output' },
|
||||||
{ path: '.next/standalone/.next/required-server-files.json', name: 'Required server files' },
|
|
||||||
{ path: '.next/static', name: 'Static directory' },
|
{ path: '.next/static', name: 'Static directory' },
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -41,26 +40,6 @@ for (const check of checks) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test the copy command
|
|
||||||
console.log('\n📋 Testing copy command...');
|
|
||||||
try {
|
|
||||||
execSync('cp -r .next/static .next/standalone/.next/', { stdio: 'inherit' });
|
|
||||||
console.log('✅ Copy command successful');
|
|
||||||
} catch (e) {
|
|
||||||
console.error('❌ Copy command failed');
|
|
||||||
allPassed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check final structure
|
|
||||||
console.log('\n📁 Final structure check...');
|
|
||||||
const finalPath = '.next/standalone/.next/static';
|
|
||||||
if (fs.existsSync(finalPath)) {
|
|
||||||
console.log('✅ Static files copied successfully');
|
|
||||||
} else {
|
|
||||||
console.error('❌ Static files not in final location');
|
|
||||||
allPassed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!allPassed) {
|
if (!allPassed) {
|
||||||
console.error('\n❌ Build test failed! Do not push.');
|
console.error('\n❌ Build test failed! Do not push.');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ provider "aws" {
|
|||||||
resource "aws_amplify_app" "filamenteka" {
|
resource "aws_amplify_app" "filamenteka" {
|
||||||
name = "filamenteka"
|
name = "filamenteka"
|
||||||
repository = var.github_repository
|
repository = var.github_repository
|
||||||
platform = "WEB_COMPUTE"
|
platform = "WEB"
|
||||||
|
|
||||||
# GitHub access token for private repos
|
# GitHub access token for private repos
|
||||||
access_token = var.github_token
|
access_token = var.github_token
|
||||||
|
|||||||
Reference in New Issue
Block a user