Skip to content

Commit 6aec449

Browse files
refactor: harden website runtime, routing, and hero visual (#20113)
This centralizes routing/SEO ownership, replaces deprecated middleware with proxy, adds safer lifecycle/runtime primitives, and introduces visual error boundaries so broken WebGL/canvas-heavy visuals can fail gracefully instead of taking down the page. It also hardens animation, resize, visibility, cleanup, and WebGL fallback paths for a broader range of browsers and devices. The hero visual was split from large monolithic files into focused domain folders for shell, pages, shared primitives, window interactions, terminal conversation, prompt, editor, and traffic-light behavior. Legacy unused section code was removed, visual configs were extracted, state/geometry logic was moved into testable modules, and coverage was added across routing, SEO, lifecycle, animation, visual runtime, halftone behavior, and hero interactions. More changes on the way, but this should make the website a lot more stable - disabling WebGL on Firefox and loading the website does not cause crashes on local any longer, will test on dev once this is merged.
1 parent 7ea1dfd commit 6aec449

261 files changed

Lines changed: 13974 additions & 21085 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/twenty-website-new/scripts/check-boundaries.mjs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,26 @@ const RULES = [
3131
'preference defaults.',
3232
].join('\n '),
3333
},
34+
{
35+
id: 'no-raw-animation-frame',
36+
description:
37+
'`requestAnimationFrame(...)` / `cancelAnimationFrame(...)` may only be used inside shared runtime primitives.',
38+
pattern: /\b(?:window\.)?(?:requestAnimationFrame|cancelAnimationFrame)\s*\(/,
39+
appliesTo: (rel) =>
40+
rel.startsWith('src/') && /\.(ts|tsx|mjs|js|jsx)$/.test(rel),
41+
exempt: (rel) =>
42+
rel.startsWith('src/lib/animation/') ||
43+
rel.startsWith('src/lib/visual-runtime/') ||
44+
rel.includes('__tests__') ||
45+
rel === 'src/app/[locale]/halftone/_lib/exporters.ts' ||
46+
rel.endsWith('.d.ts'),
47+
help: [
48+
'Use `createAnimationFrameLoop` from `@/lib/animation` for one-shot',
49+
'or UI frame scheduling. Use `createVisualRenderLoop` from',
50+
'`@/lib/visual-runtime` for canvas/WebGL renderers so tab visibility,',
51+
'element visibility, cleanup, and render failures are handled consistently.',
52+
].join('\n '),
53+
},
3454
];
3555

3656
const SKIP_DIRS = new Set([

packages/twenty-website-new/src/app/[locale]/(home)/page.tsx

Lines changed: 2 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import { MENU_DATA } from '@/sections/Menu/data';
1111
import { TRUSTED_BY_DATA } from '@/sections/TrustedBy/data';
1212
import { Body, Eyebrow, Heading, LinkButton } from '@/design-system/components';
1313
import { Pages } from '@/lib/pages';
14-
import { ArrowRightUpIcon } from '@/icons';
1514
import { fetchCommunityStats } from '@/lib/community/fetch-community-stats';
1615
import { mergeSocialLinkLabels } from '@/lib/community/merge-social-link-labels';
1716
import { Faq } from '@/sections/Faq/components';
@@ -22,23 +21,15 @@ import { Menu } from '@/sections/Menu/components';
2221
import { Problem } from '@/sections/Problem/components';
2322
import { Testimonials } from '@/sections/Testimonials/components';
2423
import { ThreeCards } from '@/sections/ThreeCards/components';
25-
import { buildLocalizedMetadata } from '@/lib/seo';
24+
import { buildRouteMetadata } from '@/lib/seo';
2625
import { TrustedBy } from '@/sections/TrustedBy/components';
2726
import { theme } from '@/theme';
2827
import { css } from '@linaria/core';
2928
import { styled } from '@linaria/react';
3029

31-
export const generateMetadata = buildLocalizedMetadata({
32-
path: '/',
33-
title: 'Twenty | #1 open source CRM',
34-
description:
35-
'The #1 open source CRM for modern teams. Modular, scalable, and built to fit your business.',
36-
});
30+
export const generateMetadata = buildRouteMetadata('home');
3731

3832
const HOME_TOP_BACKGROUND_COLOR = '#F4F4F4';
39-
const PRODUCT_HUNT_LAUNCH_URL =
40-
'https://www.producthunt.com/products/twenty-crm?launch=twenty-2-0';
41-
const PRODUCT_HUNT_BRAND_COLOR = '#DA552F';
4233

4334
const HeroHeadingGroup = styled.div`
4435
align-items: center;
@@ -52,58 +43,6 @@ const HeroHeadingGroup = styled.div`
5243
}
5344
`;
5445

55-
const HeroLaunchChip = styled.a`
56-
align-items: center;
57-
background: ${theme.colors.primary.background[100]};
58-
border: 1px solid ${theme.colors.primary.border[10]};
59-
border-radius: 999px;
60-
color: ${theme.colors.primary.text[100]};
61-
display: inline-flex;
62-
font-family: ${theme.font.family.mono};
63-
font-size: ${theme.font.size(2.5)};
64-
font-weight: ${theme.font.weight.medium};
65-
gap: ${theme.spacing(2)};
66-
line-height: ${theme.lineHeight(3)};
67-
padding: ${theme.spacing(2)} ${theme.spacing(3)};
68-
text-decoration: none;
69-
text-transform: uppercase;
70-
transition:
71-
border-color 180ms ease,
72-
color 180ms ease,
73-
transform 180ms ease;
74-
white-space: nowrap;
75-
76-
&:is(:hover, :focus-visible) {
77-
border-color: ${PRODUCT_HUNT_BRAND_COLOR};
78-
color: ${PRODUCT_HUNT_BRAND_COLOR};
79-
transform: translateY(-1px);
80-
}
81-
82-
&:focus-visible {
83-
outline: 1px solid ${theme.colors.highlight[100]};
84-
outline-offset: 2px;
85-
}
86-
87-
@media (prefers-reduced-motion: reduce) {
88-
transition: none;
89-
}
90-
`;
91-
92-
const HeroLaunchChipDot = styled.span`
93-
background: ${PRODUCT_HUNT_BRAND_COLOR};
94-
border-radius: 999px;
95-
display: block;
96-
flex-shrink: 0;
97-
height: ${theme.spacing(2)};
98-
width: ${theme.spacing(2)};
99-
`;
100-
101-
const HeroLaunchChipLabel = styled.span`
102-
align-items: center;
103-
display: inline-flex;
104-
gap: ${theme.spacing(1.5)};
105-
`;
106-
10746
const HeroIntroGroup = styled.div`
10847
align-items: center;
10948
display: flex;
@@ -177,17 +116,6 @@ export default async function HomePage() {
177116
<Hero.Root backgroundColor={HOME_TOP_BACKGROUND_COLOR} showHomeBackground>
178117
<HeroIntroGroup data-halftone-exclude>
179118
<HeroHeadingGroup>
180-
<HeroLaunchChip
181-
href={PRODUCT_HUNT_LAUNCH_URL}
182-
rel="noopener noreferrer"
183-
target="_blank"
184-
>
185-
<HeroLaunchChipDot />
186-
<HeroLaunchChipLabel>
187-
Live on Product Hunt
188-
<ArrowRightUpIcon size={8} strokeColor="currentColor" />
189-
</HeroLaunchChipLabel>
190-
</HeroLaunchChip>
191119
<Hero.Heading page={Pages.Home} segments={HERO_DATA.heading} />
192120
<Hero.Body page={Pages.Home} body={HERO_DATA.body} size="sm" />
193121
</HeroHeadingGroup>

packages/twenty-website-new/src/app/[locale]/customers/page.tsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,10 @@ import { Menu } from '@/sections/Menu/components';
1414
import { Signoff } from '@/sections/Signoff/components';
1515
import { TrustedBy } from '@/sections/TrustedBy/components';
1616
import { theme } from '@/theme';
17-
import { buildLocalizedMetadata } from '@/lib/seo';
17+
import { buildRouteMetadata } from '@/lib/seo';
1818
import { css } from '@linaria/core';
1919

20-
export const generateMetadata = buildLocalizedMetadata({
21-
path: '/customers',
22-
title: 'Customers | Twenty',
23-
description:
24-
'Meet the teams running their business on Twenty. Real customer stories on how they shaped the CRM to fit their workflow.',
25-
});
20+
export const generateMetadata = buildRouteMetadata('customers');
2621

2722
const HERO_HEADING = [
2823
{ text: 'See how teams ', fontFamily: 'serif' as const },

packages/twenty-website-new/src/app/[locale]/enterprise/activate/EnterpriseActivateClient.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
buttonBaseStyles,
66
} from '@/design-system/components/Button/BaseButton';
77
import { Body, Heading } from '@/design-system/components';
8+
import { useTimeoutRegistry } from '@/lib/react';
89
import { theme } from '@/theme';
910
import { css } from '@linaria/core';
1011
import { styled } from '@linaria/react';
@@ -133,6 +134,7 @@ const nextStepItemClassName = css`
133134
export function EnterpriseActivateClient() {
134135
const searchParams = useSearchParams();
135136
const sessionId = searchParams.get('session_id');
137+
const timeoutRegistry = useTimeoutRegistry();
136138
const [result, setResult] = useState<ActivationResult | null>(null);
137139
const [error, setError] = useState<string | null>(null);
138140
const [loading, setLoading] = useState(true);
@@ -146,14 +148,21 @@ export function EnterpriseActivateClient() {
146148
return;
147149
}
148150

151+
const abortController = new AbortController();
152+
149153
const activate = async () => {
150154
try {
151155
const response = await fetch(
152156
`/api/enterprise/activate?session_id=${encodeURIComponent(sessionId)}`,
157+
{ signal: abortController.signal },
153158
);
154159
const data: { error?: string } & Partial<ActivationResult> =
155160
await response.json();
156161

162+
if (abortController.signal.aborted) {
163+
return;
164+
}
165+
157166
if (!response.ok) {
158167
setError(data.error ?? 'Activation failed');
159168

@@ -170,13 +179,23 @@ export function EnterpriseActivateClient() {
170179
setError('Activation response was incomplete.');
171180
}
172181
} catch {
182+
if (abortController.signal.aborted) {
183+
return;
184+
}
185+
173186
setError('Failed to activate enterprise key. Please try again.');
174187
} finally {
175-
setLoading(false);
188+
if (!abortController.signal.aborted) {
189+
setLoading(false);
190+
}
176191
}
177192
};
178193

179194
void activate();
195+
196+
return () => {
197+
abortController.abort();
198+
};
180199
}, [sessionId]);
181200

182201
const handleCopy = async () => {
@@ -186,7 +205,7 @@ export function EnterpriseActivateClient() {
186205

187206
await navigator.clipboard.writeText(result.enterpriseKey);
188207
setCopied(true);
189-
setTimeout(() => {
208+
timeoutRegistry.schedule(() => {
190209
setCopied(false);
191210
}, 2000);
192211
};

packages/twenty-website-new/src/app/[locale]/enterprise/activate/page.tsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,11 @@ import { mergeSocialLinkLabels } from '@/lib/community/merge-social-link-labels'
88
import { Hero } from '@/sections/Hero/components';
99
import { Menu } from '@/sections/Menu/components';
1010
import { theme } from '@/theme';
11-
import { buildLocalizedMetadata } from '@/lib/seo';
11+
import { buildRouteMetadata } from '@/lib/seo';
1212
import { Suspense } from 'react';
1313
import { styled } from '@linaria/react';
1414

15-
export const generateMetadata = buildLocalizedMetadata({
16-
path: '/enterprise/activate',
17-
title: 'Enterprise activation | Twenty',
18-
description:
19-
'Complete activation for your Twenty self-hosted enterprise license.',
20-
});
15+
export const generateMetadata = buildRouteMetadata('enterpriseActivate');
2116

2217
const ENTERPRISE_ACTIVATE_HEADING: HeadingType[] = [
2318
{ text: 'Enterprise ', fontFamily: 'serif' },

0 commit comments

Comments
 (0)