feat: Prepare Vercel deployment with serverless adapter
- Extract buildApp() from server.ts into app.ts for reuse - Create api/index.ts as Vercel serverless function entry point - Add vercel.json with rewrites (static assets + catch-all to API) - Build script now copies static assets to public/ and templates to dist/ - Include src/templates/** in serverless function bundle - Local dev and tests continue to work unchanged (60/60 passing) https://claude.ai/code/session_01CvrcMDqfCKWV2hC3xpRbx3
This commit is contained in:
parent
e966049169
commit
014c4b3bf5
|
|
@ -2,4 +2,5 @@ node_modules/
|
|||
dist/
|
||||
.env
|
||||
src/public/css/app.css
|
||||
/public/
|
||||
*.tsbuildinfo
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import { buildApp } from "../src/app.js";
|
||||
|
||||
let appReady: ReturnType<typeof buildApp> | null = null;
|
||||
|
||||
function getApp() {
|
||||
if (!appReady) {
|
||||
appReady = buildApp().then(async (app) => {
|
||||
await app.ready();
|
||||
return app;
|
||||
});
|
||||
}
|
||||
return appReady;
|
||||
}
|
||||
|
||||
export default async function handler(
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
) {
|
||||
const app = await getApp();
|
||||
app.server.emit("request", req, res);
|
||||
}
|
||||
|
|
@ -7,9 +7,10 @@
|
|||
"dev": "concurrently \"npm run dev:server\" \"npm run dev:css\"",
|
||||
"dev:server": "tsx watch --env-file=.env src/server.ts",
|
||||
"dev:css": "npx @tailwindcss/cli -i src/styles/input.css -o src/public/css/app.css --watch",
|
||||
"build": "npm run build:css && npm run build:server",
|
||||
"build": "npm run build:css && npm run build:server && npm run build:assets",
|
||||
"build:server": "tsc",
|
||||
"build:css": "npx @tailwindcss/cli -i src/styles/input.css -o src/public/css/app.css --minify",
|
||||
"build:assets": "cp -r src/public/* public/ 2>/dev/null; cp -r src/templates dist/templates",
|
||||
"start": "node --env-file=.env dist/server.js",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
import Fastify from "fastify";
|
||||
import fastifyView from "@fastify/view";
|
||||
import fastifyStatic from "@fastify/static";
|
||||
import fastifyFormbody from "@fastify/formbody";
|
||||
import nunjucks from "nunjucks";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { dirname, join } from "node:path";
|
||||
import { config } from "./config/index.js";
|
||||
import { homeRoutes } from "./routes/home.js";
|
||||
import { flowRoutes } from "./routes/flow.js";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
export async function buildApp() {
|
||||
const app = Fastify({
|
||||
logger: {
|
||||
level: config.nodeEnv === "production" ? "info" : "debug",
|
||||
transport:
|
||||
config.nodeEnv === "development"
|
||||
? { target: "pino-pretty", options: { translateTime: "HH:MM:ss" } }
|
||||
: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
// Parse form body (application/x-www-form-urlencoded)
|
||||
await app.register(fastifyFormbody);
|
||||
|
||||
// Serve static assets (local dev only — Vercel serves from public/)
|
||||
await app.register(fastifyStatic, {
|
||||
root: join(__dirname, "public"),
|
||||
prefix: "/",
|
||||
});
|
||||
|
||||
// Nunjucks template engine
|
||||
nunjucks.configure(join(__dirname, "templates"), {
|
||||
autoescape: true,
|
||||
noCache: config.nodeEnv === "development",
|
||||
});
|
||||
|
||||
await app.register(fastifyView, {
|
||||
engine: { nunjucks },
|
||||
templates: join(__dirname, "templates"),
|
||||
options: {
|
||||
onConfigure: (_env: nunjucks.Environment) => {
|
||||
// Add any custom filters here
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Register routes
|
||||
await app.register(homeRoutes);
|
||||
await app.register(flowRoutes);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
|
@ -1,57 +1,8 @@
|
|||
import Fastify from "fastify";
|
||||
import fastifyView from "@fastify/view";
|
||||
import fastifyStatic from "@fastify/static";
|
||||
import fastifyFormbody from "@fastify/formbody";
|
||||
import nunjucks from "nunjucks";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { dirname, join } from "node:path";
|
||||
import { config } from "./config/index.js";
|
||||
import { homeRoutes } from "./routes/home.js";
|
||||
import { flowRoutes } from "./routes/flow.js";
|
||||
import { buildApp } from "./app.js";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const app = await buildApp();
|
||||
|
||||
const app = Fastify({
|
||||
logger: {
|
||||
level: config.nodeEnv === "production" ? "info" : "debug",
|
||||
transport:
|
||||
config.nodeEnv === "development"
|
||||
? { target: "pino-pretty", options: { translateTime: "HH:MM:ss" } }
|
||||
: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
// Parse form body (application/x-www-form-urlencoded)
|
||||
await app.register(fastifyFormbody);
|
||||
|
||||
// Serve static assets
|
||||
await app.register(fastifyStatic, {
|
||||
root: join(__dirname, "public"),
|
||||
prefix: "/",
|
||||
});
|
||||
|
||||
// Nunjucks template engine
|
||||
const nunjucksEnv = nunjucks.configure(join(__dirname, "templates"), {
|
||||
autoescape: true,
|
||||
noCache: config.nodeEnv === "development",
|
||||
});
|
||||
|
||||
await app.register(fastifyView, {
|
||||
engine: { nunjucks },
|
||||
templates: join(__dirname, "templates"),
|
||||
options: {
|
||||
onConfigure: (env: nunjucks.Environment) => {
|
||||
// Add any custom filters here
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Register routes
|
||||
await app.register(homeRoutes);
|
||||
await app.register(flowRoutes);
|
||||
|
||||
// Start server
|
||||
try {
|
||||
const address = await app.listen({ port: config.port, host: config.host });
|
||||
app.log.info(`Jus IA Start Kit running at ${address}`);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"$schema": "https://openapi.vercel.sh/vercel.json",
|
||||
"buildCommand": "npm run build",
|
||||
"outputDirectory": "public",
|
||||
"functions": {
|
||||
"api/index.ts": {
|
||||
"includeFiles": "src/templates/**"
|
||||
}
|
||||
},
|
||||
"rewrites": [
|
||||
{ "source": "/css/(.*)", "destination": "/css/$1" },
|
||||
{ "source": "/js/(.*)", "destination": "/js/$1" },
|
||||
{ "source": "/images/(.*)", "destination": "/images/$1" },
|
||||
{ "source": "/favicon.ico", "destination": "/favicon.ico" },
|
||||
{ "source": "/(.*)", "destination": "/api/index" }
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue