2 Commits
Author SHA1 Message Date
asepharyana 8790a4f65a fix(web): render content pages dynamically so next build needs no DB
CI / typecheck + build (turbo) (push) Canceled after 0s
The home, doc, and search pages queried Postgres during SSG/static data
collection, so 'next build' failed in CI (no DB). Mark them
force-dynamic (and drop generateStaticParams from the doc page) so they
render at request time — instant at this corpus scale and build-safe
without a live database.
2026-08-19 22:56:01 +07:00
asepharyana b454a0fbac fix(embeddings): defer EMBED_* validation to embed() runtime, not constructor
Constructor threw if EMBED_BASE_URL/API_KEY/MODEL unset, which broke next
build SSG collection (imports search pkg before .env exists). Validation
now happens in embed() so the package is build-safe; a missing config still
errors clearly when a semantic/hybrid search actually runs.

With the DATABASE_URL import-time throw also removed, the whole monorepo
now builds in CI without any .env present.
2026-08-19 22:52:05 +07:00
4 changed files with 24 additions and 13 deletions

No files matched your search

+6 -8
View File
@@ -3,6 +3,12 @@ import { notFound } from "next/navigation";
import { getDocument, listDocuments, getRelated, listRevisions } from "@mcpedia/core"; import { getDocument, listDocuments, getRelated, listRevisions } from "@mcpedia/core";
import Markdown from "@/components/Markdown"; import Markdown from "@/components/Markdown";
// Render at request time. The content lives in Postgres (populated by the
// indexer/worker), which is not available at build time (CI has no DB), so we
// opt out of static generation. At this corpus scale request-time rendering is
// instant.
export const dynamic = "force-dynamic";
export default async function DocPage({ export default async function DocPage({
params, params,
}: { }: {
@@ -80,11 +86,3 @@ export default async function DocPage({
</article> </article>
); );
} }
export async function generateStaticParams() {
const docs = await listDocuments();
return docs.map((d) => ({
section: d.section,
slug: d.slug.split("/").slice(1),
}));
}
+4
View File
@@ -2,6 +2,10 @@ import Link from "next/link";
import { listDocuments } from "@mcpedia/core"; import { listDocuments } from "@mcpedia/core";
import type { DocumentMeta } from "@mcpedia/core"; import type { DocumentMeta } from "@mcpedia/core";
// Render at request time — content is in Postgres (not available at build in
// CI, which has no DB). Request-time rendering is instant at this corpus scale.
export const dynamic = "force-dynamic";
const SECTIONS = ["docs", "writeups", "research", "notes"] as const; const SECTIONS = ["docs", "writeups", "research", "notes"] as const;
export default async function Home() { export default async function Home() {
+4
View File
@@ -1,6 +1,10 @@
import Link from "next/link"; import Link from "next/link";
import { keywordSearch, hybridSearch } from "@mcpedia/core"; import { keywordSearch, hybridSearch } from "@mcpedia/core";
// Render at request time — queries Postgres on each request (not at build,
// where CI has no DB).
export const dynamic = "force-dynamic";
type Mode = "keyword" | "hybrid"; type Mode = "keyword" | "hybrid";
export default async function SearchPage({ export default async function SearchPage({
+10 -5
View File
@@ -32,11 +32,11 @@ export class OpenRouterEmbeddingProvider implements EmbeddingProvider {
this.baseUrl = (opts?.baseUrl ?? EMBED_BASE_URL).replace(/\/$/, ""); this.baseUrl = (opts?.baseUrl ?? EMBED_BASE_URL).replace(/\/$/, "");
this.apiKey = opts?.apiKey ?? EMBED_API_KEY; this.apiKey = opts?.apiKey ?? EMBED_API_KEY;
this.model = opts?.model ?? EMBED_MODEL; this.model = opts?.model ?? EMBED_MODEL;
if (!this.baseUrl || !this.apiKey || !this.model) { // NOTE: we do NOT validate baseUrl/apiKey/model here. Throwing in the
throw new Error( // constructor breaks `next build` (SSG data collection imports the search
"OpenRouterEmbeddingProvider: missing EMBED_BASE_URL / EMBED_API_KEY / EMBED_MODEL", // package before any .env is present) and any runtime-injected env. The
); // provider is constructed lazily at first query, so an unset config
} // surfaces as a clear error from embed() at request time, not a build crash.
} }
get dimensions(): number { get dimensions(): number {
@@ -45,6 +45,11 @@ export class OpenRouterEmbeddingProvider implements EmbeddingProvider {
async embed(texts: string[]): Promise<number[][]> { async embed(texts: string[]): Promise<number[][]> {
if (texts.length === 0) return []; if (texts.length === 0) return [];
if (!this.baseUrl || !this.apiKey || !this.model) {
throw new Error(
"OpenRouterEmbeddingProvider: missing EMBED_BASE_URL / EMBED_API_KEY / EMBED_MODEL at request time",
);
}
const res = await fetch(`${this.baseUrl}/embeddings`, { const res = await fetch(`${this.baseUrl}/embeddings`, {
method: "POST", method: "POST",
headers: { headers: {