Files
upstream-mirror e7da97194a
Some checks failed
App Prod-Parity E2E Dispatch / dispatch (push) Failing after 10s
CD Storybook UI / build (push) Failing after 3m25s
CD Storybook UI / dispatch (push) Has been skipped
CD deploy main / website-changed-files (push) Successful in 18m8s
CI Agent Skills / changed-files-check (push) Successful in 18m9s
CI Create App E2E minimal / changed-files-check (push) Successful in 24s
CI Create App / changed-files-check (push) Successful in 26s
CI Docs / changed-files-check (push) Successful in 26s
CI Emails / changed-files-check (push) Successful in 28s
CI Example App Hello World / changed-files-check (push) Successful in 23s
CI Front / changed-files-check (push) Has been skipped
CI Example App Postcard / changed-files-check (push) Successful in 27s
CI UI / changed-files-check (push) Has been skipped
CI Utils Test / changed-files-check (push) Successful in 45s
CI Twenty Apps / discover (push) Successful in 1m3s
CD deploy main / deploy-main (push) Failing after 3s
CI Agent Skills / agent-skills-validate (push) Has been skipped
CI Create App / create-app-test (lint) (push) Has been skipped
CI Emails / emails-test (push) Has been skipped
CI Front / front-task (lingui:compile) (push) Has been skipped
CI Front / front-task (lint) (push) Has been skipped
CI Example App Postcard / example-app-postcard (push) Has been skipped
CI UI / ui-task (lint) (push) Has been skipped
CI UI / ui-task (pack-size) (push) Has been skipped
CI UI / ui-task (size) (push) Has been skipped
CI UI / ui-task (test) (push) Has been skipped
CI UI / ui-task (typecheck) (push) Has been skipped
CI Utils Test / utils-test (push) Has been skipped
CI Twenty Apps / ${{ matrix.app.name }} (push) Has been skipped
CI Emails / ci-emails-status-check (push) Successful in 2s
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Successful in 2s
CI Example App Postcard / ci-example-app-postcard-status-check (push) Successful in 3s
CI UI / ui-sb-test (push) Has been skipped
CI Utils Test / ci-utils-test-status-check (push) Successful in 2s
CI Twenty Apps / ci-twenty-apps-status-check (push) Successful in 2s
CI UI / ci-ui-status-check (push) Successful in 2s
CI Desktop / desktop (push) Failing after 1m8s
Push docs to Crowdin / Push documentation to Crowdin (push) Failing after 55s
Push translations to Crowdin / Extract and upload translations (push) Failing after 5s
Push website translations to Crowdin / Extract and upload website translations (push) Failing after 5s
CI Create App / create-app-test (test) (push) Has been skipped
CI Create App / create-app-test (typecheck) (push) Has been skipped
CI Docs / docs-lint (push) Has been skipped
CI Example App Hello World / example-app-hello-world (push) Has been skipped
CI Front / front-task (lingui:extract) (push) Has been skipped
CI Front / front-task (test) (push) Has been skipped
CI Front / front-task (typecheck) (push) Has been skipped
CI UI / ui-sb-build (push) Failing after 1m32s
CI Twenty Apps / ${{ matrix.app.name }} (${{ matrix.server-source }}) (push) Has been skipped
CI Agent Skills / agent-skills-publish (push) Has been skipped
CI Create App / ci-create-app-status-check (push) Successful in 2s
CI Agent Skills / agent-skills-remote-source (push) Has been skipped
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI E2E Main / e2e-test (push) Has been cancelled
CI Front / front-sb-build (push) Has been cancelled
CI Front / front-build (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI E2E Main / ci-e2e-main-status-check (push) Has been cancelled
CI E2E Main / notify-main-ci-failure (push) Has been cancelled
CI E2E Main / qa-scout-comment (push) Has been cancelled
CI Front / front-sb-test (push) Has been cancelled
CI Front / front-sb-screenshots (push) Has been cancelled
CI Front / ci-front-status-check (push) Has been cancelled
DPA Sub-Processor Sync / sync-subprocessors (push) Failing after 3s
AI Catalog Sync / sync-catalog (push) Failing after 2s
snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
2026-09-17 23:28:13 +05:00

245 lines
7.3 KiB
TypeScript

import { initOpenNextCloudflareForDev } from '@opennextjs/cloudflare';
import withLinaria, { type LinariaConfig } from 'next-with-linaria';
import path from 'path';
import { WEBSITE_LOCALE_LIST } from './src/platform/i18n/website-locale-list';
import { buildLocaleRewrites } from './src/platform/routing/locale-rewrite-patterns';
const SECURITY_HEADERS: { key: string; value: string }[] = [
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload',
},
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=(), payment=()',
},
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'Content-Security-Policy', value: "frame-ancestors 'none'" },
];
// Skew protection: CI sets DEPLOYMENT_ID at build time so it's baked into
// prerendered HTML + the RSC payloads. The Worker reads the same value at
// runtime (via the worker env var) and routes mismatched requests to the
// matching older Worker version via its preview URL. Required whenever
// open-next.config.ts has cloudflare.skewProtection.enabled.
const deploymentId = process.env.DEPLOYMENT_ID;
const nextConfig: LinariaConfig = {
deploymentId,
reactCompiler: true,
images: {
formats: ['image/avif', 'image/webp'],
remotePatterns: [
{
hostname: 'avatars.githubusercontent.com',
pathname: '/**',
protocol: 'https',
},
{
hostname: 'twenty-icons.com',
pathname: '/**',
protocol: 'https',
},
],
},
linaria: {
configFile: path.resolve(__dirname, 'wyw-in-js.config.cjs'),
},
experimental: {
swcPlugins: [
[
'@lingui/swc-plugin',
{
runtimeModules: {
i18n: ['@lingui/core', 'i18n'],
trans: ['@lingui/react', 'Trans'],
},
},
],
],
},
async headers() {
return [
{
source: '/:path*',
headers: SECURITY_HEADERS,
},
{
source: '/(images|illustrations|lottie)/:path*',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=31536000, immutable',
},
],
},
];
},
// Clean public URLs: the source locale is unprefixed, other locales get a
// short segment. Rewrites map unprefixed paths onto the internal /[locale]
// tree; redirects canonicalize away explicit source-locale prefixes.
async rewrites() {
return {
beforeFiles: buildLocaleRewrites(WEBSITE_LOCALE_LIST),
};
},
async redirects() {
return [
// Canonicalise www → apex. Host-based; fires before any locale logic.
// The root-path rule must come before the :path* one — Next.js's
// path-to-regexp leaves a literal `:path*` in the Location header
// when the parameter matches empty against an absolute destination.
{
source: '/',
has: [{ type: 'host', value: 'www.twenty.com' }],
destination: 'https://twenty.com/',
permanent: true,
},
{
source: '/:path*',
has: [{ type: 'host', value: 'www.twenty.com' }],
destination: 'https://twenty.com/:path*',
permanent: true,
},
{
source: '/',
has: [{ type: 'host', value: 'www.twenty-main.com' }],
destination: 'https://twenty-main.com/',
permanent: true,
},
{
source: '/:path*',
has: [{ type: 'host', value: 'www.twenty-main.com' }],
destination: 'https://twenty-main.com/:path*',
permanent: true,
},
// Strip the source-locale prefix: /en/foo → /foo (301).
{ source: '/en', destination: '/', statusCode: 301 },
{ source: '/en/:path*', destination: '/:path*', statusCode: 301 },
{
source: '/enterprise/activate',
destination: '/organization/activate',
permanent: true,
},
{
source: `/:locale(${WEBSITE_LOCALE_LIST.join('|')})/enterprise/activate`,
destination: '/:locale/organization/activate',
permanent: true,
},
// /partners/list folded into the lead page, whose directory zone is the
// same grid. Both the unprefixed and the locale-prefixed URLs were in the
// sitemap, so both need the 308.
{ source: '/partners/list', destination: '/partners', permanent: true },
{
source: `/:locale(${WEBSITE_LOCALE_LIST.join('|')})/partners/list`,
destination: '/:locale/partners',
permanent: true,
},
{
source: '/user-guide',
destination: 'https://docs.twenty.com/user-guide/introduction',
permanent: true,
},
{
source: '/user-guide/section/:folder/:slug*',
destination: 'https://docs.twenty.com/user-guide/:folder/:slug*',
permanent: true,
},
{
source: '/user-guide/:folder/:slug*',
destination: 'https://docs.twenty.com/user-guide/:folder/:slug*',
permanent: true,
},
{
source: '/developers',
destination: 'https://docs.twenty.com/developers/introduction',
permanent: true,
},
{
source: '/developers/section/:folder/:slug*',
destination: 'https://docs.twenty.com/developers/:folder/:slug*',
permanent: true,
},
{
source: '/developers/:folder/:slug*',
destination: 'https://docs.twenty.com/developers/:folder/:slug*',
permanent: true,
},
{
source: '/twenty-ui',
destination: 'https://docs.twenty.com/twenty-ui/introduction',
permanent: true,
},
{
source: '/twenty-ui/section/:folder/:slug*',
destination: 'https://docs.twenty.com/twenty-ui/:folder/:slug*',
permanent: true,
},
{
source: '/twenty-ui/:folder/:slug*',
destination: 'https://docs.twenty.com/twenty-ui/:folder/:slug*',
permanent: true,
},
{
source: '/resources/why-twenty',
destination: '/why-twenty',
permanent: true,
},
{
source: '/story',
destination: '/why-twenty',
permanent: true,
},
{
source: '/legal/privacy',
destination: '/privacy-policy',
permanent: true,
},
{
source: '/legal/terms',
destination: '/terms',
permanent: true,
},
{
source: '/legal/dpa',
destination: '/terms',
permanent: true,
},
{
source: '/case-studies/9-dots-story',
destination: '/customers/9dots',
permanent: true,
},
{
source: '/case-studies/act-immi-story',
destination: '/customers/act-education',
permanent: true,
},
{
source: '/case-studies/:slug*',
destination: '/customers',
permanent: true,
},
{
source: '/implementation-services',
destination: '/partners',
permanent: true,
},
{
source: '/onboarding-packages',
destination: '/partners',
permanent: true,
},
];
},
};
export default withLinaria(nextConfig);
// Binds the Cloudflare dev context (R2 incremental cache, env vars) into
// `next dev` so local runs mirror the deployed OpenNext worker.
initOpenNextCloudflareForDev();