mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-10-03 10:13:47 +10:00
feat(deploy): deploy Vercel as frontend and backend services
Vercel now deploys Reactive Resume as two services in one project: `frontend` serves the static Vite build from apps/web/dist, and `backend` runs the Hono server Function from apps/server. Top-level rewrites send server-owned paths (/api, /uploads, /mcp, /.well-known, robots.txt, sitemap.xml, llms.txt, schema.json, index.html) and every path without a file extension to `backend`, so HTML shells keep their injected SEO metadata. Paths with a file extension go to `frontend`. The service builder needs a few accommodations: - apps/server/vercel.mjs replaces api/index.mjs as the entrypoint, because a service entrypoint must exist before the build runs. - `outputDirectory: "."` stops the builder from using the Docker entrypoint in dist/ as the Function handler. - The builder loads external CommonJS dependencies through pnpm links that it leaves out of the Function. ioredis and react-reconciler are now bundled with their dependencies, and bcrypt is replaced with bcryptjs, which reads and writes the same $2b$ hashes. The Vercel compatibility workflow now builds with the services framework and loads a copy of the backend Function outside the checkout, so a dependency missing from the Function fails CI. The stale PDFKit trace checks are removed. Existing Vercel installations must set Framework Preset to Services before redeploying; the self-hosting guide documents this.
This commit is contained in:
@@ -58,29 +58,35 @@ jobs:
|
||||
import { writeFileSync } from 'node:fs';
|
||||
writeFileSync('.vercel/project.json', JSON.stringify({
|
||||
projectId: 'prj_ci', orgId: 'team_ci', projectName: 'reactive-resume-ci',
|
||||
settings: { framework: null, nodeVersion: '24.x', createdAt: 0 }
|
||||
settings: { framework: 'services', nodeVersion: '24.x', createdAt: 0 }
|
||||
}));
|
||||
JS
|
||||
pnpm dlx --allow-build=esbuild vercel@60.0.1 build --prod --yes --global-config "$RUNNER_TEMP/vercel-offline"
|
||||
- name: Check Lambda module loading and function budget
|
||||
pnpm dlx --allow-build=esbuild vercel@61.0.0 build --prod --yes --global-config "$RUNNER_TEMP/vercel-offline"
|
||||
# Runs the backend Function from a copy outside the checkout, so a dependency the build left out fails here.
|
||||
- name: Check backend Function loading and budget
|
||||
run: |
|
||||
node --no-experimental-require-module --input-type=module - <<'JS'
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync, readdirSync } from 'node:fs';
|
||||
const config = JSON.parse(readFileSync('.vercel/output/functions/api/index.func/.vc-config.json'));
|
||||
import { cpSync, lstatSync, mkdirSync, readFileSync, readlinkSync, symlinkSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
const func = '.vercel/output/services/backend/functions/index.func';
|
||||
const config = JSON.parse(readFileSync(`${func}/.vc-config.json`));
|
||||
assert.equal(config.runtime, 'nodejs24.x');
|
||||
assert.equal(config.maxDuration, 300);
|
||||
const tracedFiles = Object.keys(config.filePathMap ?? {});
|
||||
assert.ok(tracedFiles.some((path) => path.endsWith('/pdfkit/js/standard-fonts/Helvetica.cjs')));
|
||||
assert.ok(tracedFiles.some((path) => path.endsWith('/pdfkit/js/data/Helvetica.afm')));
|
||||
for (const name of readdirSync('apps/server/dist')) {
|
||||
if (name.endsWith('.mjs') && !['index.mjs', 'prepare-deployment.mjs'].includes(name)) {
|
||||
await import(`./apps/server/dist/${name}`);
|
||||
}
|
||||
assert.equal(config.handler, 'apps/server/vercel.mjs');
|
||||
const root = join(process.env.RUNNER_TEMP, 'backend-function');
|
||||
cpSync(func, root, { recursive: true, verbatimSymlinks: true });
|
||||
for (const [path, source] of Object.entries(config.filePathMap ?? {})) {
|
||||
mkdirSync(dirname(join(root, path)), { recursive: true });
|
||||
if (lstatSync(source).isSymbolicLink()) symlinkSync(readlinkSync(source), join(root, path));
|
||||
else cpSync(source, join(root, path));
|
||||
}
|
||||
const { default: app } = await import('./apps/server/dist/vercel.mjs');
|
||||
const response = await app.fetch(new Request('http://localhost:3000/api/storage/stage', { method: 'POST', body: '{}' }));
|
||||
assert.equal(response.status, 401);
|
||||
const { default: app } = await import(join(root, config.handler));
|
||||
const stage = await app.fetch(new Request('http://localhost:3000/api/storage/stage', { method: 'POST', body: '{}' }));
|
||||
assert.equal(stage.status, 401);
|
||||
const home = await app.fetch(new Request('http://localhost:3000/'));
|
||||
assert.equal(home.status, 200);
|
||||
assert.match(await home.text(), /application\/ld\+json/);
|
||||
process.exit(0);
|
||||
JS
|
||||
|
||||
|
||||
Reference in New Issue
Block a user