[Website] Host customer story hero images locally and fix multi-segment redirects (#20790)

Customer story page shows the following error, which I believe leads to
an internal server on the individual customer story pages.

<img width="636" height="75" alt="image"
src="https://github.com/user-attachments/assets/fc9ede75-fd3b-4538-8211-8182f4a99b9b"
/>

This PR replaces remote URLs of those images with local copies to avoid
a 404 issue. Will test once deployed on dev to confirm if the error is
resolved, but locally, I do not see console errors any longer after this
change.

There is some duplicated copy that I found upon audit which can be made
DRY, but I will resolve it in a separate PR to keep this PR
single-responsibility.
This commit is contained in:
Abdullah.
2026-05-21 19:10:24 +05:00
committed by GitHub
parent abf5902ab5
commit 8826d12a18
15 changed files with 55 additions and 51 deletions
+11 -5
View File
@@ -90,16 +90,22 @@ const nextConfig: LinariaConfig = {
];
},
async rewrites() {
const localeAlternation = DEPLOYED_LOCALE_URL_SEGMENTS.join('|');
return {
beforeFiles: [
// Root rewrites to the source locale.
{ source: '/', destination: '/en' },
// Any path that isn't already locale-prefixed (en/, fr/), an internal
// Next.js path, a static asset folder, or a file with an extension
// rewrites to the source locale prefix. Mirrors proxy.ts Rule 4.
// Multi-segment paths (e.g. /customers/9dots, /articles/my-post).
// The Worker's path-to-regexp does not allow :param(regex) to match
// across "/" boundaries, so we split multi-segment into :first/:rest+.
{
source:
'/:rest((?!en$|en/|fr$|fr/|api|_next/static|_next/image|favicon\\.ico|robots\\.txt|sitemap\\.xml|illustrations|lottie|fonts|.+\\..+).+)',
source: `/:first((?!(?:${localeAlternation}|api|_next|images|illustrations|halftone|lottie|fonts)(?=/))[^/.]+)/:rest+`,
destination: '/en/:first/:rest+',
},
// Single-segment paths (e.g. /pricing, /customers, /why-twenty).
{
source: `/:rest((?!${DEPLOYED_LOCALE_URL_SEGMENTS.map((s) => `${s}$|${s}/`).join('|')}|api|_next/static|_next/image|favicon\\.ico|robots\\.txt|sitemap\\.xml|images|illustrations|lottie|fonts|.+\\..+).+)`,
destination: '/en/:rest',
},
],
Binary file not shown.

After

Width:  |  Height:  |  Size: 313 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

@@ -1,15 +1,14 @@
import { msg } from '@lingui/core/macro';
import { type CaseStudyData } from '@/lib/customers';
import { CaseStudyPageLayout } from '@/app/[locale]/customers/_components/CaseStudyPageLayout';
import { Heading, HeadingPart } from '@/design-system/components';
import { CASE_STUDY_HERO_IMAGES, type CaseStudyData } from '@/lib/customers';
import {
getRouteI18n,
type LocaleRouteParams,
} from '@/lib/i18n/utils/get-route-i18n';
import { buildLocalizedMetadata } from '@/lib/seo';
import { Heading, HeadingPart } from '@/design-system/components';
import { CaseStudyPageLayout } from '@/app/[locale]/customers/_components/CaseStudyPageLayout';
import { msg } from '@lingui/core/macro';
const PLACEHOLDER_HERO =
'https://images.unsplash.com/photo-1733244766159-f58f4184fd38?w=1600&q=80';
const HERO_IMAGE = CASE_STUDY_HERO_IMAGES.nineDots;
const META_TITLE = msg`Homeseller, WhatsApp, and a CRM built around the business | Nine Dots & Twenty`;
const META_DESCRIPTION = msg`How Nine Dots Ventures rebuilt a Singapore real estate agency on Twenty with APIs, n8n, Grafana, and AI on top of 2,000+ WhatsApp messages a day.`;
@@ -51,7 +50,7 @@ export default async function NineDotsCaseStudyPage({
authorAvatarSrc: '/images/partner/testimonials/mike-babiy.webp',
authorRole: msg`Founder, Nine Dots Ventures`,
clientIcon: 'nine-dots',
heroImageSrc: PLACEHOLDER_HERO,
heroImageSrc: HERO_IMAGE,
industry: msg`Real Estate`,
kpis: [
{ value: msg`150 hrs`, label: msg`Saved / month` },
@@ -1,5 +1,5 @@
import { msg } from '@lingui/core/macro';
import { type CaseStudyData } from '@/lib/customers';
import { CASE_STUDY_HERO_IMAGES, type CaseStudyData } from '@/lib/customers';
import {
getRouteI18n,
type LocaleRouteParams,
@@ -8,8 +8,7 @@ import { buildLocalizedMetadata } from '@/lib/seo';
import { Heading, HeadingPart } from '@/design-system/components';
import { CaseStudyPageLayout } from '@/app/[locale]/customers/_components/CaseStudyPageLayout';
const PLACEHOLDER_HERO =
'https://images.unsplash.com/photo-1687600154329-150952c73169?w=1600&q=80';
const HERO_IMAGE = CASE_STUDY_HERO_IMAGES.actEducation;
const META_TITLE = msg`Burned by vendor lock-in, AC&T built a CRM they actually own | Twenty`;
const META_DESCRIPTION = msg`How AC&T Education Migration and Flycoder replaced a shuttered vendor CRM with self-hosted Twenty, with 90%+ lower cost and full ownership.`;
@@ -51,7 +50,7 @@ export default async function ActEducationCaseStudyPage({
authorAvatarSrc: '/images/partner/testimonials/joseph-chiang.webp',
authorRole: msg`CRM Engineer, AC&T Education Migration`,
clientIcon: 'act-education',
heroImageSrc: PLACEHOLDER_HERO,
heroImageSrc: HERO_IMAGE,
industry: msg`Education`,
kpis: [{ value: msg`90%+`, label: msg`Lower CRM cost` }],
},
@@ -1,6 +1,6 @@
import { msg } from '@lingui/core/macro';
import { Heading, HeadingPart } from '@/design-system/components';
import type { CaseStudyData } from '@/lib/customers';
import { CASE_STUDY_HERO_IMAGES, type CaseStudyData } from '@/lib/customers';
import {
getRouteI18n,
type LocaleRouteParams,
@@ -8,8 +8,7 @@ import {
import { buildLocalizedMetadata } from '@/lib/seo';
import { CaseStudyPageLayout } from '@/app/[locale]/customers/_components/CaseStudyPageLayout';
const PLACEHOLDER_HERO =
'https://images.unsplash.com/photo-1702047149248-a6049168a2a8?w=1600&q=80';
const HERO_IMAGE = CASE_STUDY_HERO_IMAGES.alternativePartners;
const META_TITLE = msg`From Salesforce to self-hosted Twenty, powered by AI | Alternative Partners`;
const META_DESCRIPTION = msg`How Alternative Partners migrated from Salesforce to self-hosted Twenty using agentic AI in the implementation loop: fast migration, durable ownership.`;
@@ -51,7 +50,7 @@ export default async function AlternativePartnersCaseStudyPage({
authorAvatarSrc: '/images/partner/testimonials/benjamin-reynolds.webp',
authorRole: msg`Principal and Founder, Alternative Partners`,
clientIcon: 'alternative-partners',
heroImageSrc: PLACEHOLDER_HERO,
heroImageSrc: HERO_IMAGE,
industry: msg`Consulting`,
kpis: [
{ value: msg`AI-assisted`, label: msg`Salesforce migration` },
@@ -1,5 +1,5 @@
import { msg } from '@lingui/core/macro';
import { type CaseStudyData } from '@/lib/customers';
import { CASE_STUDY_HERO_IMAGES, type CaseStudyData } from '@/lib/customers';
import {
getRouteI18n,
type LocaleRouteParams,
@@ -8,8 +8,7 @@ import { buildLocalizedMetadata } from '@/lib/seo';
import { Heading, HeadingPart } from '@/design-system/components';
import { CaseStudyPageLayout } from '@/app/[locale]/customers/_components/CaseStudyPageLayout';
const PLACEHOLDER_HERO =
'https://images.unsplash.com/photo-1758873269035-aae0e1fd3422?w=1600&q=80';
const HERO_IMAGE = CASE_STUDY_HERO_IMAGES.elevateConsulting;
const META_TITLE = msg`Twenty as the API backbone of a go-to-market stack | Elevate Consulting`;
const META_DESCRIPTION = msg`How Elevate Consulting moved off documents and spreadsheets to Twenty as the API-connected CRM at the center of their stack.`;
@@ -53,7 +52,7 @@ export default async function ElevateConsultingCaseStudyPage({
author: 'Justin Beadle',
authorRole: msg`Director of Digital and Information, Elevate Consulting`,
clientIcon: 'elevate-consulting',
heroImageSrc: PLACEHOLDER_HERO,
heroImageSrc: HERO_IMAGE,
industry: msg`Management Consulting`,
kpis: [
{ value: msg`1 click`, label: msg`Proposal automation` },
@@ -1,5 +1,5 @@
import { msg } from '@lingui/core/macro';
import { type CaseStudyData } from '@/lib/customers';
import { CASE_STUDY_HERO_IMAGES, type CaseStudyData } from '@/lib/customers';
import {
getRouteI18n,
type LocaleRouteParams,
@@ -8,8 +8,7 @@ import { buildLocalizedMetadata } from '@/lib/seo';
import { Heading, HeadingPart } from '@/design-system/components';
import { CaseStudyPageLayout } from '@/app/[locale]/customers/_components/CaseStudyPageLayout';
const PLACEHOLDER_HERO =
'https://images.unsplash.com/photo-1744830343976-ce690ba2a67c?w=1600&q=80';
const HERO_IMAGE = CASE_STUDY_HERO_IMAGES.netzero;
const META_TITLE = msg`A CRM that grows with you | NetZero & Twenty`;
const META_DESCRIPTION = msg`How NetZero uses Twenty across carbon credits, agricultural products, and franchised industrial systems with a modular CRM and a roadmap toward AI-assisted workflows.`;
@@ -50,7 +49,7 @@ export default async function NetZeroCaseStudyPage({
authorAvatarSrc: '/images/partner/testimonials/olivier-reinaud.webp',
authorRole: msg`Co-founder, NetZero`,
clientIcon: 'netzero',
heroImageSrc: PLACEHOLDER_HERO,
heroImageSrc: HERO_IMAGE,
industry: msg`Agribusiness`,
kpis: [
{ value: msg`3 product lines`, label: msg`On a single CRM` },
@@ -1,5 +1,5 @@
import { msg } from '@lingui/core/macro';
import { type CaseStudyData } from '@/lib/customers';
import { CASE_STUDY_HERO_IMAGES, type CaseStudyData } from '@/lib/customers';
import {
getRouteI18n,
type LocaleRouteParams,
@@ -8,8 +8,7 @@ import { buildLocalizedMetadata } from '@/lib/seo';
import { Heading, HeadingPart } from '@/design-system/components';
import { CaseStudyPageLayout } from '@/app/[locale]/customers/_components/CaseStudyPageLayout';
const PLACEHOLDER_HERO =
'https://images.unsplash.com/photo-1756830231350-3b501f63c5c1?w=1600&q=80';
const HERO_IMAGE = CASE_STUDY_HERO_IMAGES.w3villa;
const META_TITLE = msg`When your CRM is the product: W3Grads on Twenty | W3villa Technologies`;
const META_DESCRIPTION = msg`How W3villa Technologies shipped W3Grads, an AI mock interview platform for institutions, on Twenty as the operational backbone.`;
@@ -51,7 +50,7 @@ export default async function W3villaCaseStudyPage({
authorAvatarSrc: '/images/partner/testimonials/amrendra-singh.webp',
authorRole: msg`VP of Engineering, W3villa Technologies`,
clientIcon: 'w3villa',
heroImageSrc: PLACEHOLDER_HERO,
heroImageSrc: HERO_IMAGE,
industry: msg`EdTech`,
kpis: [{ value: msg`Zero`, label: msg`Manual work at core` }],
},
@@ -2,7 +2,16 @@ import { msg } from '@lingui/core/macro';
import type { CaseStudyCatalogEntry } from '@/lib/customers/types';
import { theme } from '@/theme';
const PLACEHOLDER_HERO = '/images/shared/people/avatars/katherine-adams.webp';
const CASE_STUDY_IMAGE_BASE = '/images/customers/case-studies';
export const CASE_STUDY_HERO_IMAGES = {
nineDots: `${CASE_STUDY_IMAGE_BASE}/nine-dots.webp`,
alternativePartners: `${CASE_STUDY_IMAGE_BASE}/alternative-partners.webp`,
netzero: `${CASE_STUDY_IMAGE_BASE}/netzero.webp`,
actEducation: `${CASE_STUDY_IMAGE_BASE}/act-education.webp`,
w3villa: `${CASE_STUDY_IMAGE_BASE}/w3villa.webp`,
elevateConsulting: `${CASE_STUDY_IMAGE_BASE}/elevate-consulting.webp`,
} as const;
export const CASE_STUDY_HALFTONE_PALETTE: readonly {
dashColor: string;
@@ -57,13 +66,12 @@ export const CASE_STUDY_CATALOG_ENTRIES: CaseStudyCatalogEntry[] = [
author: 'Mike Babiy & Azmat Parveen',
authorAvatarSrc: '/images/partner/testimonials/mike-babiy.webp',
clientIcon: 'nine-dots',
heroImageSrc: PLACEHOLDER_HERO,
heroImageSrc: CASE_STUDY_HERO_IMAGES.nineDots,
},
catalogCard: {
summary: msg`Nine Dots put Twenty at the center of Homeseller's stack with APIs, automation, and AI on top of WhatsApp-heavy operations.`,
date: msg`Jul 2025`,
coverImageSrc:
'https://images.unsplash.com/photo-1733244766159-f58f4184fd38?w=1600&q=80',
coverImageSrc: CASE_STUDY_HERO_IMAGES.nineDots,
},
},
{
@@ -80,13 +88,12 @@ export const CASE_STUDY_CATALOG_ENTRIES: CaseStudyCatalogEntry[] = [
author: 'Benjamin Reynolds',
authorAvatarSrc: '/images/partner/testimonials/benjamin-reynolds.webp',
clientIcon: 'alternative-partners',
heroImageSrc: PLACEHOLDER_HERO,
heroImageSrc: CASE_STUDY_HERO_IMAGES.alternativePartners,
},
catalogCard: {
summary: msg`Alternative Partners replaced Salesforce with self-hosted Twenty, using agentic AI to compress migration work.`,
date: msg`2025`,
coverImageSrc:
'https://images.unsplash.com/photo-1702047149248-a6049168d2a8?w=1600&q=80',
coverImageSrc: CASE_STUDY_HERO_IMAGES.alternativePartners,
},
},
{
@@ -103,13 +110,12 @@ export const CASE_STUDY_CATALOG_ENTRIES: CaseStudyCatalogEntry[] = [
author: 'Olivier Reinaud',
authorAvatarSrc: '/images/partner/testimonials/olivier-reinaud.webp',
clientIcon: 'netzero',
heroImageSrc: PLACEHOLDER_HERO,
heroImageSrc: CASE_STUDY_HERO_IMAGES.netzero,
},
catalogCard: {
summary: msg`NetZero uses Twenty as a modular CRM across product lines and countries, with a roadmap into AI-assisted workflows.`,
date: msg`2025`,
coverImageSrc:
'https://images.unsplash.com/photo-1744830343976-ce690ba2a67c?w=1600&q=80',
coverImageSrc: CASE_STUDY_HERO_IMAGES.netzero,
},
},
{
@@ -123,13 +129,12 @@ export const CASE_STUDY_CATALOG_ENTRIES: CaseStudyCatalogEntry[] = [
author: 'Joseph Chiang',
authorAvatarSrc: '/images/partner/testimonials/joseph-chiang.webp',
clientIcon: 'act-education',
heroImageSrc: PLACEHOLDER_HERO,
heroImageSrc: CASE_STUDY_HERO_IMAGES.actEducation,
},
catalogCard: {
summary: msg`AC&T and Flycoder moved from a dead vendor export to self-hosted Twenty, with over 90% lower CRM cost and full control.`,
date: msg`2025`,
coverImageSrc:
'https://images.unsplash.com/photo-1687600154329-150952c73169?w=1600&q=80',
coverImageSrc: CASE_STUDY_HERO_IMAGES.actEducation,
},
},
{
@@ -143,13 +148,12 @@ export const CASE_STUDY_CATALOG_ENTRIES: CaseStudyCatalogEntry[] = [
author: 'Amrendra Pratap Singh',
authorAvatarSrc: '/images/partner/testimonials/amrendra-singh.webp',
clientIcon: 'w3villa',
heroImageSrc: PLACEHOLDER_HERO,
heroImageSrc: CASE_STUDY_HERO_IMAGES.w3villa,
},
catalogCard: {
summary: msg`W3villa shipped W3Grads on Twenty for AI interviews, scoring, and institution-scale workflows without rebuilding CRM plumbing.`,
date: msg`2025`,
coverImageSrc:
'https://images.unsplash.com/photo-1756830231350-3b501f63c5c1?w=1600&q=80',
coverImageSrc: CASE_STUDY_HERO_IMAGES.w3villa,
},
},
{
@@ -171,13 +175,12 @@ export const CASE_STUDY_CATALOG_ENTRIES: CaseStudyCatalogEntry[] = [
title: msg`Twenty as the API backbone of a go-to-market stack`,
author: 'Justin Beadle',
clientIcon: 'elevate-consulting',
heroImageSrc: PLACEHOLDER_HERO,
heroImageSrc: CASE_STUDY_HERO_IMAGES.elevateConsulting,
},
catalogCard: {
summary: msg`Elevate Consulting uses Twenty as the API backbone connecting billing, Teams, resourcing, and a custom front end around client and opportunity data.`,
date: msg`Jun 2025`,
coverImageSrc:
'https://images.unsplash.com/photo-1758873269035-aae0e1fd3422?w=1600&q=80',
coverImageSrc: CASE_STUDY_HERO_IMAGES.elevateConsulting,
},
},
];
@@ -11,5 +11,6 @@ export type {
export {
CASE_STUDY_CATALOG_ENTRIES,
CASE_STUDY_HALFTONE_PALETTE,
CASE_STUDY_HERO_IMAGES,
getCaseStudyPalette,
} from './case-study-catalog';