[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:
@@ -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 |
BIN
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';
|
||||
|
||||
Reference in New Issue
Block a user