Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ yarn-error.log*
# vercel
.vercel

# cloudflare / opennext
.wrangler/
.open-next/
.dev.vars

# playwright
.playwright-mcp/

Expand Down
3 changes: 3 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { config } from '@fisch0920/config/eslint'

export default [
{
ignores: ['.next/**', '.open-next/**', '.wrangler/**']
},
...config,
{
files: ['**/*.ts', '**/*.tsx'],
Expand Down
2 changes: 1 addition & 1 deletion lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export const port = getEnv('PORT', '3000')
export const host = isDev ? `http://localhost:${port}` : `https://${domain}`
export const apiHost = isDev
? host
: `https://${process.env.VERCEL_URL || domain}`
: `https://${process.env.CF_PAGES_URL?.replace(/^https?:\/\//, '') || process.env.VERCEL_URL || domain}`

export const apiBaseUrl = `/api`

Expand Down
4 changes: 3 additions & 1 deletion lib/notion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
} from './config'
import { getTweetsMap } from './get-tweets'
import { notion } from './notion-api'
import { getPreviewImageMap } from './preview-images'

/** Parse category values from a Notion block's properties */
const getBlockCategories = (
Expand Down Expand Up @@ -135,6 +134,9 @@ export async function getPage(
}

if (isPreviewImageSupportEnabled) {
// Lazy import so `lqip-modern` / `sharp` are only loaded when the feature is enabled.
// Cloudflare Workers can't run sharp (native binding), so the flag stays false there.
const { getPreviewImageMap } = await import('./preview-images')
const previewImageMap = await getPreviewImageMap(recordMap)
;(recordMap as any).preview_images = previewImageMap
}
Expand Down
27 changes: 25 additions & 2 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,29 @@ export default {
// return config
// },

// See https://react-tweet.vercel.app/next#troubleshooting
transpilePackages: ['react-tweet']
// Transpile all runtime deps so Next 16's Turbopack doesn't externalize them.
// OpenNext/Cloudflare Workers has no node_modules at runtime — transpiling inlines them.
transpilePackages: [
'@fisch0920/use-dark-mode',
'@keyvhq/core',
'@keyvhq/redis',
'classnames',
'expiry-map',
'fathom-client',
'katex',
'ky',
'notion-client',
'notion-types',
'notion-utils',
'nprogress',
'p-map',
'p-memoize',
'posthog-js',
'prismjs',
'react-body-classname',
'react-notion-x',
'react-tweet',
'react-use',
'rss'
]
}
6 changes: 6 additions & 0 deletions open-next.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { defineCloudflareConfig } from '@opennextjs/cloudflare'
import kvIncrementalCache from '@opennextjs/cloudflare/overrides/incremental-cache/kv-incremental-cache'

export default defineCloudflareConfig({
incrementalCache: kvIncrementalCache
})
13 changes: 12 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
"build": "next build",
"start": "next start",
"deploy": "vercel deploy",
"cf:build": "opennextjs-cloudflare build",
"cf:preview": "opennextjs-cloudflare preview",
"cf:deploy": "opennextjs-cloudflare deploy",
"deps:upgrade": "[ -z $GITHUB_ACTIONS ] && pnpm up -L notion-client notion-types notion-utils react-notion-x || echo 'Skipping deps:update on CI'",
"deps:link": "[ -z $GITHUB_ACTIONS ] && run-s deps:link:* || echo 'Skipping deps:update on CI'",
"deps:unlink": "[ -z $GITHUB_ACTIONS ] && pnpm add notion-client notion-types notion-utils react-notion-x || echo 'Skipping deps:update on CI'",
Expand Down Expand Up @@ -58,6 +61,7 @@
},
"devDependencies": {
"@fisch0920/config": "^1.4.0",
"@opennextjs/cloudflare": "^1.19.1",
"@types/node": "^25.5.0",
"@types/prismjs": "^1.26.5",
"@types/react": "^19.2.14",
Expand All @@ -68,13 +72,20 @@
"npm-run-all2": "^8.0.4",
"patch-package": "^8.0.1",
"prettier": "^3.8.1",
"typescript": "^5.9.3"
"typescript": "^5.9.3",
"wrangler": "^4.83.0"
},
"overrides": {
"cacheable-request": {
"keyv": "npm:@keyvhq/core@~1.6.6"
}
},
"pnpm": {
"onlyBuiltDependencies": [
"esbuild",
"workerd"
]
},
"prettier": "@fisch0920/config/prettier",
"simple-git-hooks": {
"pre-commit": "npx lint-staged"
Expand Down
118 changes: 76 additions & 42 deletions pages/api/version.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,86 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { execFileSync } from 'child_process';
import os from 'os';
import type { NextApiRequest, NextApiResponse } from 'next'

const startTime = Date.now();
const startISO = new Date().toISOString();
const startTime = Date.now()
const startISO = new Date().toISOString()

// Realm-sigil word lists (forge realm)
const adjectives = [
"Annealed", "Bolted", "Carbonized", "Dense", "Electric",
"Flux", "Galvanized", "Hardened", "Ignited", "Joined",
"Keen", "Laminated", "Molten", "Nitrided", "Oxidized",
"Pressed", "Quenched", "Riveted", "Sintered", "Tempered"
];
'Annealed',
'Bolted',
'Carbonized',
'Dense',
'Electric',
'Flux',
'Galvanized',
'Hardened',
'Ignited',
'Joined',
'Keen',
'Laminated',
'Molten',
'Nitrided',
'Oxidized',
'Pressed',
'Quenched',
'Riveted',
'Sintered',
'Tempered'
]
const nouns = [
"Anvil", "Bellows", "Crucible", "Die", "Engine",
"Furnace", "Gear", "Hammer", "Ingot", "Jig",
"Kiln", "Lathe", "Mandrel", "Nozzle", "Oven",
"Piston", "Quench", "Rivet", "Spark", "Tongs"
];
'Anvil',
'Bellows',
'Crucible',
'Die',
'Engine',
'Furnace',
'Gear',
'Hammer',
'Ingot',
'Jig',
'Kiln',
'Lathe',
'Mandrel',
'Nozzle',
'Oven',
'Piston',
'Quench',
'Rivet',
'Spark',
'Tongs'
]

function generateName(hash: string): string {
const seed = parseInt(hash, 16) || 0;
const adj = adjectives[seed % adjectives.length];
const noun = nouns[(seed >> 8) % nouns.length];
return `${adj} ${noun} · ${hash}`;
const seed = Number.parseInt(hash, 16) || 0
const adj = adjectives[seed % adjectives.length]
const noun = nouns[(seed >> 8) % nouns.length]
return `${adj} ${noun} · ${hash}`
}

function gitInfo() {
// Vercel provides these env vars at build time (frozen into the serverless bundle)
const sha = process.env.VERCEL_GIT_COMMIT_SHA;
const ref = process.env.VERCEL_GIT_COMMIT_REF;
const sha =
process.env.WORKERS_CI_COMMIT_SHA ||
process.env.CF_PAGES_COMMIT_SHA ||
process.env.VERCEL_GIT_COMMIT_SHA
const ref =
process.env.WORKERS_CI_COMMIT_REF ||
process.env.CF_PAGES_BRANCH ||
process.env.VERCEL_GIT_COMMIT_REF
if (sha) {
return { hash: sha.slice(0, 7), branch: ref || 'unknown', dirty: false };
return { hash: sha.slice(0, 7), branch: ref || 'unknown', dirty: false }
}
// Fallback: try git (works locally, not on Vercel)
const info = { hash: 'dev', branch: 'unknown', dirty: false };
try {
info.hash = execFileSync('git', ['rev-parse', '--short', 'HEAD'], { encoding: 'utf8' }).trim() || 'dev';
info.branch = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { encoding: 'utf8' }).trim() || 'unknown';
try { execFileSync('git', ['diff', '--quiet']); } catch { info.dirty = true; }
} catch {}
return info;
return { hash: 'dev', branch: 'unknown', dirty: false }
}
Comment on lines 58 to 71
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gitInfo() no longer attempts a local git fallback, so /api/version will always report hash: "dev" when running locally even if a .git directory is present. If you still want accurate local version info without breaking Workers, consider adding an optional Node-only fallback using a dynamic import of child_process (and guard it behind detectPlatform() === "local" or a similar runtime check).

Copilot uses AI. Check for mistakes.

export default function handler(req: NextApiRequest, res: NextApiResponse) {
const git = gitInfo();
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Access-Control-Allow-Origin', '*');
function detectPlatform(): string {
if (process.env.WORKERS_CI_COMMIT_SHA || process.env.CF_PAGES)
return 'cloudflare'
if (process.env.VERCEL) return 'vercel'
return 'local'
}

export default function handler(_req: NextApiRequest, res: NextApiResponse) {
const git = gitInfo()
res.setHeader('Cache-Control', 'no-cache')
res.setHeader('Access-Control-Allow-Origin', '*')
res.status(200).json({
name: 'techempower',
description: 'Tech empower platform',
Expand All @@ -58,11 +92,11 @@ export default function handler(req: NextApiRequest, res: NextApiResponse) {
started: startISO,
uptime: Math.floor((Date.now() - startTime) / 1000),
realm: 'forge',
runtime: `node${process.version}`,
os: `${process.platform}/${process.arch}`,
host: os.hostname(),
pid: process.pid,
platform: detectPlatform(),
repo: 'https://github.com/jphein/techempower',
commit_url: git.hash !== 'dev' ? `https://github.com/jphein/techempower/commit/${git.hash}` : '',
});
commit_url:
git.hash !== 'dev'
? `https://github.com/jphein/techempower/commit/${git.hash}`
: ''
})
}
Loading
Loading