fix(website): force-dynamic partner profiles to stop OpenNext 404 cache (#22120)
## Summary Partner profile pages (`/partners/profile/[slug]`) returned **404 on every slug** on OpenNext/Cloudflare while `/partners/list` showed live partners from the same API. PR #21963 fixed the list with `export const dynamic = 'force-dynamic'` but only added `dynamicParams = true` on profiles. That is not sufficient on OpenNext — the Worker kept serving **cached prerendered 404s** even when `TWENTY_PARTNERS_API_KEY` was present at runtime. This PR mirrors the list page: **`force-dynamic` on the profile route**, plus a small lint guard so both marketplace routes stay dynamic. ## Root cause Partner data is fetched server-side from `https://partners.twenty.com/s/partners` using `TWENTY_PARTNERS_API_KEY`. That key is a **Wrangler runtime secret** (not in `dev.env` / `prod.env`, not available during CI build — by repo convention). | Route | Before | Behavior | |-------|--------|----------| | `/partners/list` | `force-dynamic` (#21963) | Fetches at request time on Worker → works | | `/partners/profile/[slug]` | static + `dynamicParams = true` | Build prewarm often empty; OpenNext served cached 404 | ## Fix - Add `export const dynamic = 'force-dynamic'` to `profile/[slug]/page.tsx` (keep `dynamicParams = true`). - Add `scripts/check-partners-marketplace-routes.mjs` — fails lint if list or profile drop `force-dynamic`. - Wire guard into `project.json` `lint` target (runs before existing `check-conventions.mjs`). **No infra changes.** We intentionally did not add a GitHub Actions secret for the API key — that would contradict the documented pattern (`wrangler secret put` only). ## Verification - [x] `node scripts/check-partners-marketplace-routes.mjs` → OK - [x] `npx jest src/partners-marketplace` → 36/36 pass - [x] Deployed to **dev** (`deploy-website`, env `dev`, ref `rk-partner-profile-404`) - [x] `curl -sI https://twenty-main.com/partners/profile/atlasprods-technologies-llp` → **HTTP 200** - [x] Browser: list → profile link loads ## Test plan - [ ] CI lint + tests green - [ ] After merge: deploy prod when ready (`environment: prod`, confirm `website`) - [ ] Spot-check `https://twenty.com/partners/profile/<slug>` → 200 ## Out of scope - Build-time `generateStaticParams` prewarm (would need a separate infra discussion; not required once profiles are `force-dynamic`) - Per-slug `/s/partner-by-slug` endpoint (optional perf follow-up) <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22120?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
@@ -39,7 +39,7 @@
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"cwd": "{projectRoot}",
|
||||
"command": "node scripts/check-conventions.mjs && npx oxlint -c .oxlintrc.json . && npx oxfmt --check ."
|
||||
"command": "node scripts/check-partners-marketplace-routes.mjs && node scripts/check-conventions.mjs && npx oxlint -c .oxlintrc.json . && npx oxfmt --check ."
|
||||
},
|
||||
"configurations": {
|
||||
"fix": {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const routes = [
|
||||
'src/app/[locale]/(site)/partners/list/page.tsx',
|
||||
'src/app/[locale]/(site)/partners/profile/[slug]/page.tsx',
|
||||
];
|
||||
|
||||
const failures = [];
|
||||
|
||||
for (const relativePath of routes) {
|
||||
const fullPath = path.join(root, relativePath);
|
||||
const source = fs.readFileSync(fullPath, 'utf8');
|
||||
if (!/export const dynamic = 'force-dynamic';/.test(source)) {
|
||||
failures.push(
|
||||
`${relativePath}: must export dynamic = 'force-dynamic' (OpenNext runtime fetch; build has no TWENTY_PARTNERS_API_KEY).`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (failures.length > 0) {
|
||||
console.error('check-partners-marketplace-routes: FAILED');
|
||||
for (const failure of failures) {
|
||||
console.error(` ${failure}`);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('check-partners-marketplace-routes: OK');
|
||||
@@ -14,6 +14,8 @@ import { Menu } from '@/sections/menu';
|
||||
|
||||
type PartnerProfileParams = { locale: string; slug: string };
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export const dynamicParams = true;
|
||||
|
||||
export async function generateStaticParams(): Promise<Array<{ slug: string }>> {
|
||||
|
||||
Reference in New Issue
Block a user