feat(deploy): #66 apps/api/vercel.json for second Vercel project#72
feat(deploy): #66 apps/api/vercel.json for second Vercel project#72fray-cloud wants to merge 6 commits into
Conversation
Root vercel.json was a Vite SPA rewrite (\`/(.*) → /index.html\`) that would break Next.js deploys by routing every path into a nonexistent index.html. The Next App Router handles routing itself — no rewrite needed at the Vercel layer. Per the Vercel monorepo guide and the roadmap, web and api will each become their own Vercel project with Root Directory apps/web and apps/api respectively. Root-level vercel.json would apply to both and cannot express per-app config coherently, so it stays removed. - nx affected -t lint test build --exclude web-e2e green - nx.json sharedGlobals previously referenced .circleci and .github/workflows/ci.yml, not vercel.json — no cache invalidation to worry about Refs #62
Closes the server-only half of #3 by routing every public-data-portal call through a Next Route Handler that reads the key from process.env.DATA_GO_KR_SERVICE_KEY (no NEXT_PUBLIC_ prefix). The browser bundle no longer carries the key. apps/web/app/api/data-go-kr/[...path]/route.ts (new): - Catch-all GET handler that mirrors the upstream http://apis.data.go.kr/1543061/abandonmentPublicSrvc/<path> structure - Reads search params from the incoming request, injects serviceKey and _type=json server-side, then forwards via fetch - Passes through status/content-type, returns 500 if the env var is missing, 502 if the upstream fetch throws apps/web/src/new-api/service.ts: - baseURL switched to '/api/data-go-kr' (relative path, no host) - Drops the serviceKey query param entirely — the proxy handles it - Drops the import.meta / process.env env-check code - Same export surface (getAPI, serviceAPI) so all 5 callers under new-api/{animalInfo,kind,sigungu,shelter,sido} keep working unchanged apps/web/.env.example: - NEXT_PUBLIC_DATA_GO_KR_SERVICE_KEY → DATA_GO_KR_SERVICE_KEY - Comment updated: server-only, read by the Route Handler Build output: ○ / ○ /_not-found ƒ /api/data-go-kr/[...path] ← new dynamic route ○ /like ○ /search - nx affected -t lint test build --exclude web-e2e green Refs #63
The public-data-portal endpoint moved to v2 with a renamed base path and per-resource _v2 suffixes. Update the proxy and the consumers in the same PR so the Route Handler delivered in the previous commit keeps working end-to-end. Route Handler upstream URL: - http://apis.data.go.kr/1543061/abandonmentPublicSrvc + https://apis.data.go.kr/1543061/abandonmentPublicService_v2 (now also HTTPS, matching the v2 spec at data.go.kr/data/15098931) new-api path constants (all five callers): - /abandonmentPublic → /abandonmentPublic_v2 - /sido → /sido_v2 - /sigungu → /sigungu_v2 - /kind → /kind_v2 - /shelter → /shelter_v2 shared-types AnimalInfo: - popfile (single Image URL) → popfile1 (required) plus optional popfile2..popfile8. The v2 schema returns up to 8 image URLs per rescue notice; we only render the first today, but the optional fields are typed for the gallery work in #18 e2e / future ports. apps/web AnimalCard.tsx: - item.popfile → item.popfile1 - nx affected -t lint test build --exclude web-e2e green Refs #63
@vercel/remote-nx wires Nx Remote Cache into a Vercel-hosted endpoint via tasksRunnerOptions in nx.json. Our nx.json has never had any tasksRunnerOptions wired to it (we now use the standard Nx Cloud runner via nxCloudAccessToken), so the dependency was dead weight. - grep '@vercel/remote-nx' apps libs → zero source references - nx affected -t lint test build --exclude web-e2e green (10/10 cache) Refs #64
Adds apps/api/api/index.ts as the Vercel Node Function entrypoint for the NestJS app. Local dev keeps using apps/api/src/main.ts via `nx serve api`; this handler is only consumed by Vercel. Pattern (per NestJS FAQ /faq/serverless.md): - Wrap NestFactory.create with ExpressAdapter so we can hand the underlying express app to @codegenie/serverless-express - Cache the resulting handler in a module-scope var so cold start pays the bootstrap cost once and warm invocations skip it - Export default Handler from aws-lambda types so Vercel's Node runtime auto-detects the entry Why @codegenie/ instead of @vendia/ as the roadmap mentions: @vendia/serverless-express was renamed to @codegenie/serverless- express; the Nest docs already use the new name. apps/api/tsconfig.app.json: - include adds api/**/*.ts so the new entrypoint type-checks under nx lint api / nx build api src/main.ts is untouched. webpack.config.js still bundles main.ts via @nx/webpack/app-plugin, so `nx build api` and `nx serve api` keep working identically. - nx affected -t lint test build --exclude web-e2e green - new file lints clean The Vercel project + vercel.json wiring lands in #66. Refs #65
Vercel config for the api project (separate from web). When the user creates a second Vercel project pointing at this repo with Root Directory set to apps/api, this file becomes its config. apps/api/vercel.json: - framework: null — we ship a Node Function, not a framework preset - functions["api/index.ts"]: @vercel/node@5 runtime, maxDuration 30s - rewrites: /api and /api/* both resolve to /api/index so the serverless-express handler from #65 receives the full request apps/api/tsconfig.json: - experimentalDecorators + emitDecoratorMetadata + target es2021 + module commonjs added at the project tsconfig level. Vercel's @vercel/node compiler reads the nearest tsconfig.json (not tsconfig.app.json), so without these flags the NestJS decorators in api/index.ts → src/app/app.module would lose their metadata and DI would fail at cold start. The flags duplicate what tsconfig.app.json already sets, so nx build api is unaffected. - nx affected -t lint test build --exclude web-e2e green The actual project creation in the Vercel Dashboard + env var configuration is documented in #67 (deployment matrix). Refs #66
Reviewer's GuideConfigures a separate Vercel deployment target for the NestJS API app using a new serverless entrypoint and per-project vercel.json, while routing web app traffic through a new Next.js route handler that proxies to the public-data-portal v2 APIs and updates shared types and call sites accordingly. Sequence diagram for Next.js data-go-kr proxy route handlersequenceDiagram
actor User
participant Browser
participant WebApp
participant RouteHandler
participant DataGoKrAPI
User->>Browser: Initiate search in UI
Browser->>WebApp: Request /api/data-go-kr/{path}?query
WebApp->>RouteHandler: Invoke GET handler
RouteHandler->>RouteHandler: Read DATA_GO_KR_SERVICE_KEY from env
RouteHandler->>RouteHandler: Build upstream URL with serviceKey and _type=json
RouteHandler->>DataGoKrAPI: GET abandonmentPublicService_v2/{path}
DataGoKrAPI-->>RouteHandler: JSON response
RouteHandler-->>WebApp: Forward body and content-type
WebApp-->>Browser: Return response to client
Browser-->>User: Render search results without exposing API key
Sequence diagram for Vercel Node Function NestJS bootstrap and reusesequenceDiagram
participant Client
participant Vercel
participant ApiFunction
participant NestFactory
participant ExpressApp
Client->>Vercel: HTTP request /api
Vercel->>ApiFunction: Invoke default handler(event, context, callback)
alt First invocation (cold start)
ApiFunction->>ApiFunction: cachedHandler is undefined
ApiFunction->>ExpressApp: Create express application instance
ApiFunction->>NestFactory: create(AppModule, ExpressAdapter(expressApp))
NestFactory-->>ApiFunction: Nest application instance
ApiFunction->>ApiFunction: app.setGlobalPrefix(api)
ApiFunction->>ApiFunction: app.init()
ApiFunction->>ApiFunction: cachedHandler = serverlessExpress(app: expressApp)
ApiFunction->>ApiFunction: Invoke cachedHandler(event, context, callback)
else Subsequent invocation (warm)
ApiFunction->>ApiFunction: Reuse cachedHandler
ApiFunction->>ApiFunction: Invoke cachedHandler(event, context, callback)
end
ApiFunction-->>Vercel: Lambda style response
Vercel-->>Client: HTTP response
Class diagram for Vercel Node Function NestJS integrationclassDiagram
class ApiIndex {
- cachedHandler Handler
+ bootstrap() Promise_Handler_
+ handler(event any, context any, callback any) Promise_any_
}
class AppModule
class NestFactory {
+ create(moduleRef any, adapter ExpressAdapter) Promise_NestApplication_
}
class ExpressAdapter {
+ ExpressAdapter(expressApp ExpressApplication)
}
class ExpressApplication
class ServerlessExpress {
+ serverlessExpress(app ExpressApplication) Handler
}
class Handler
class NestApplication {
+ setGlobalPrefix(prefix string) void
+ init() Promise_void_
}
ApiIndex ..> AppModule : uses
ApiIndex ..> NestFactory : uses
ApiIndex ..> ExpressAdapter : uses
ApiIndex ..> ServerlessExpress : uses
NestFactory ..> NestApplication : creates
ExpressAdapter ..> ExpressApplication : wraps
ServerlessExpress ..> Handler : returns
ApiIndex o-- Handler : holds cachedHandler
NestApplication ..> ExpressApplication : runs_on
Class diagram for updated APIResponse item image fieldsclassDiagram
class APIResponse~T~ {
+ response Header
+ body Body~T~
}
class Body~T~ {
+ items T[]
+ numOfRows number
+ pageNo number
+ totalCount number
}
class AnimalItem {
+ desertionNo string
+ filename string
+ happenDt string
+ happenPlace string
+ kindCd string
+ colorCd string
+ age string
+ weight string
+ noticeNo string
+ noticeSdt string
+ noticeEdt string
+ popfile1 string
+ popfile2 string
+ popfile3 string
+ popfile4 string
+ popfile5 string
+ popfile6 string
+ popfile7 string
+ popfile8 string
+ processState string
+ sexCd string
+ neuterYn string
+ specialMark string
+ careNm string
+ careTel string
+ careAddr string
+ orgNm string
+ chargeNm string
+ officetel string
}
APIResponse ..> Body : contains
Body ..> AnimalItem : items_of
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
View your CI Pipeline Execution ↗ for commit f7f2a47
☁️ Nx Cloud last updated this comment at |
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- In the Next route handler (
apps/web/app/api/data-go-kr/[...path]/route.ts),context.paramsshouldn’t be typed or treated as aPromise—usecontext: { params: { path: string[] } }and drop theawaitto align with Next’s route handler typings and avoid confusion. - The data-go-kr proxy currently buffers the full upstream response with
await upstream.text(); if responses can get large, consider returning a streamed response orupstream.bodydirectly viaNextResponseto reduce memory usage and latency.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In the Next route handler (`apps/web/app/api/data-go-kr/[...path]/route.ts`), `context.params` shouldn’t be typed or treated as a `Promise`—use `context: { params: { path: string[] } }` and drop the `await` to align with Next’s route handler typings and avoid confusion.
- The data-go-kr proxy currently buffers the full upstream response with `await upstream.text()`; if responses can get large, consider returning a streamed response or `upstream.body` directly via `NextResponse` to reduce memory usage and latency.
## Individual Comments
### Comment 1
<location path="apps/web/src/new-site/search/card/AnimalCard.tsx" line_range="25-27" />
<code_context>
</div>
<figure className="px-10 pt-10">
- <img src={item.popfile} alt="pet" className="rounded-xl h-32" />
+ <img src={item.popfile1} alt="pet" className="rounded-xl h-32" />
</figure>
<CardContent className="flex flex-col items-center text-center">
</code_context>
<issue_to_address>
**suggestion:** Handle missing or empty `popfile1` more defensively in the card rendering.
Since the API can now return up to 8 image fields, `popfile1` may be empty while other `popfileX` values are present. Consider either falling back to the first non-empty `popfileX` or only rendering the `<img>` when `popfile1` is truthy and otherwise showing a placeholder, to avoid broken images with partial data.
```suggestion
{(() => {
const imageSrc =
item.popfile1 ||
item.popfile2 ||
item.popfile3 ||
item.popfile4 ||
item.popfile5 ||
item.popfile6 ||
item.popfile7 ||
item.popfile8
return imageSrc ? (
<figure className="px-10 pt-10">
<img src={imageSrc} alt="pet" className="rounded-xl h-32" />
</figure>
) : (
<figure className="px-10 pt-10">
<div className="rounded-xl h-32 w-full flex items-center justify-center bg-gray-100 text-gray-400">
No image available
</div>
</figure>
)
})()}
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| <figure className="px-10 pt-10"> | ||
| <img src={item.popfile} alt="pet" className="rounded-xl h-32" /> | ||
| <img src={item.popfile1} alt="pet" className="rounded-xl h-32" /> | ||
| </figure> |
There was a problem hiding this comment.
suggestion: Handle missing or empty popfile1 more defensively in the card rendering.
Since the API can now return up to 8 image fields, popfile1 may be empty while other popfileX values are present. Consider either falling back to the first non-empty popfileX or only rendering the <img> when popfile1 is truthy and otherwise showing a placeholder, to avoid broken images with partial data.
| <figure className="px-10 pt-10"> | |
| <img src={item.popfile} alt="pet" className="rounded-xl h-32" /> | |
| <img src={item.popfile1} alt="pet" className="rounded-xl h-32" /> | |
| </figure> | |
| {(() => { | |
| const imageSrc = | |
| item.popfile1 || | |
| item.popfile2 || | |
| item.popfile3 || | |
| item.popfile4 || | |
| item.popfile5 || | |
| item.popfile6 || | |
| item.popfile7 || | |
| item.popfile8 | |
| return imageSrc ? ( | |
| <figure className="px-10 pt-10"> | |
| <img src={imageSrc} alt="pet" className="rounded-xl h-32" /> | |
| </figure> | |
| ) : ( | |
| <figure className="px-10 pt-10"> | |
| <div className="rounded-xl h-32 w-full flex items-center justify-center bg-gray-100 text-gray-400"> | |
| No image available | |
| </div> | |
| </figure> | |
| ) | |
| })()} |
Summary
Stacked on #71. Vercel config for the api project (separate from
web). When the user creates a second Vercel project pointing at this repo with Root Directory set toapps/api, this file becomes its config.apps/api/vercel.json(new)framework: null— we ship a Node Function, not a framework presetfunctions["api/index.ts"]→@vercel/node@5runtime,maxDuration: 30srewrites:/apiand/api/*both resolve to/api/indexso theserverless-expresshandler from [area:vercel-deploy] NestJS serverless handler entrypoint (@codegenie/serverless-express) #65 receives the full requestapps/api/tsconfig.jsonAdds
experimentalDecorators,emitDecoratorMetadata,target: es2021,module: commonjsat the project tsconfig level. Vercel's@vercel/nodecompiler reads the nearesttsconfig.json(nottsconfig.app.json), so without these flags the NestJS decorators inapi/index.ts → src/app/app.modulewould lose their metadata and DI would fail at cold start. The flags duplicate whattsconfig.app.jsonalready sets, sonx build apiis unaffected.The actual project creation in the Vercel Dashboard + env var configuration is documented in #67 (deployment matrix).
Closes #66.
Test plan
npx nx affected -t lint test build --exclude web-e2egreennx build api(webpack bundler for local) still producesdist/apps/api/main.js🤖 Generated with Claude Code
Summary by Sourcery
Configure the API app for Vercel deployment and route web traffic through a secure proxy to the public data portal v2 API.
New Features:
Enhancements: