chore(www): unpublish and redirect the legacy launch week pages (#49335)

Closes
[FE-4226](https://linear.app/supabase/issue/FE-4226/unpublish-and-redirect-legacy-launch-week-pages)

## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Content removal.

## What is the current behavior?

`/launch-week/x`, `/launch-week/12`, `/launch-week/13`, and
`/launch-week/14` are still published. Each one carries its own page
component and a ticket flow for a launch week that ended. The
accessibility scan flags them, and they hold no SEO value.

This follows #49281, which took down `/launch-week/6` on the same
pattern.

## What is the new behavior?

- Delete the `/launch-week/x`, `/12`, `/13`, and `/14` page routes.
- Redirect each path to its recap blog post, matching the destinations
agreed in `#team-marketing`.
- Point the Launch Week 12, 13, and 14 blog summary components at
`/launch-week` instead of their deleted pages. `LWXSummary` already does
this.
- Drop the `disableStickyNav` and `showLaunchWeekNavMode` checks in
`Nav` that only matched the deleted routes.
- Drop the Launch Week X branches in `useDarkLaunchWeeks` and `_app`.

| Source | Destination |
| --- | --- |
| `/launch-week/x` | `/blog/launch-week-x-best-launches` |
| `/launch-week/12` | `/blog/launch-week-12-top-10` |
| `/launch-week/13` | `/blog/launch-week-13-top-10` |
| `/launch-week/14` | `/blog/launch-week-14-top-10` |

## Additional context

`/launch-week/7` and `/launch-week/8` stay published. Neither has a
recap post to redirect to, so they need a destination decision before
they come down.

The `components/LaunchWeek/{X,12,13,14}` trees stay. `BlogPostRenderer`
imports the summary component from each one, and those summaries read
the same `Releases/data` modules the deleted pages used. The stage and
nav components under those directories are now unreachable, so they need
their own dead-code audit.

Assets under `public/images/launchweek/` are untouched, same as #49281.

## Manual testing

Preview:
https://zone-www-dot-com-git-www-redirect-legacy-launch-weeks-supabase.vercel.app

1. Open
[/launch-week/x](https://zone-www-dot-com-git-www-redirect-legacy-launch-weeks-supabase.vercel.app/launch-week/x).
It returns a 308 and lands on `/blog/launch-week-x-best-launches`.
2. Open
[/launch-week/12](https://zone-www-dot-com-git-www-redirect-legacy-launch-weeks-supabase.vercel.app/launch-week/12).
It returns a 308 and lands on `/blog/launch-week-12-top-10`.
3. Open
[/launch-week/13](https://zone-www-dot-com-git-www-redirect-legacy-launch-weeks-supabase.vercel.app/launch-week/13).
It returns a 308 and lands on `/blog/launch-week-13-top-10`.
4. Open
[/launch-week/14](https://zone-www-dot-com-git-www-redirect-legacy-launch-weeks-supabase.vercel.app/launch-week/14).
It returns a 308 and lands on `/blog/launch-week-14-top-10`.
5. On each of those blog posts, the launch week summary card header
links to `/launch-week`.
6. Open
[/launch-week](https://zone-www-dot-com-git-www-redirect-legacy-launch-weeks-supabase.vercel.app/launch-week),
[/launch-week/7](https://zone-www-dot-com-git-www-redirect-legacy-launch-weeks-supabase.vercel.app/launch-week/7),
and
[/launch-week/8](https://zone-www-dot-com-git-www-redirect-legacy-launch-weeks-supabase.vercel.app/launch-week/8).
All still load.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Miranda LimonczenkoandClaude Opus 5 authored and GitHub committed 2026-08-20 15:33:09 -07:00
1 parent 717927f4f2
commit 70715790b8
11 files changed
+29 -457

No files matched your search

@@ -9,7 +9,7 @@ const LW11Summary = () => {
<div className="w-full border bg-alternative-200 flex flex-col rounded-lg text-foreground-lighter mt-12">
<div className="w-full p-4 flex justify-between items-center">
<Link
href="/launch-week/12"
href="/launch-week"
className="flex items-center text-foreground font-medium gap-1.5 leading-none uppercase text-xs opacity-80 transition-opacity hover:opacity-100"
>
Launch Week
@@ -9,7 +9,7 @@ const LW13Summary = () => {
<div className="w-full border bg-alternative-200 flex flex-col rounded-lg text-foreground-lighter mt-12">
<div className="w-full p-4 flex justify-between items-center">
<Link
href="/launch-week/13"
href="/launch-week"
className="flex items-center text-foreground font-medium gap-1.5 leading-none uppercase text-xs opacity-80 transition-opacity hover:opacity-100"
>
Launch Week
@@ -14,7 +14,7 @@ const LW14Summary = () => {
>
<div className="w-full p-4 flex justify-between items-center">
<Link
href="/launch-week/14"
href="/launch-week"
className="flex items-center text-foreground font-medium gap-1.5 leading-none uppercase text-xs opacity-80 transition-opacity hover:opacity-100"
>
Launch Week 14
+2 -11
View File
@@ -43,19 +43,10 @@ const Nav = ({ hideNavbar, stickyNavbar = true }: Props) => {
const sendTelemetryEvent = useSendTelemetryEvent()
const userMenu = useDropdownMenu(user)
const isLaunchWeekXPage = pathname === '/launch-week/x'
const isLaunchWeek12Page = pathname === '/launch-week/12'
const isLaunchWeek13Page = pathname === '/launch-week/13'
const isGAWeekSection = pathname?.startsWith('/ga-week')
const isStateOfStartupsPage = pathname?.startsWith('/state-of-startups')
const disableStickyNav =
isLaunchWeekXPage ||
isGAWeekSection ||
isLaunchWeekXPage ||
isLaunchWeek12Page ||
isLaunchWeek13Page ||
!stickyNavbar
const showLaunchWeekNavMode = (isGAWeekSection || isLaunchWeekXPage) && !open
const disableStickyNav = isGAWeekSection || !stickyNavbar
const showLaunchWeekNavMode = isGAWeekSection && !open
const [scrolled, setScrolled] = React.useState(false)
React.useEffect(() => {
+1 -2
View File
@@ -7,9 +7,8 @@ const useDarkLaunchWeeks = () => {
const isLaunchWeek7 = pathname?.startsWith('/launch-week/7')
const isLaunchWeek8 = pathname?.startsWith('/launch-week/8')
const isLaunchWeekX = pathname?.startsWith('/launch-week/x')
return isLaunchWeek7 || isLaunchWeek8 || isLaunchWeekX
return isLaunchWeek7 || isLaunchWeek8
}
export default useDarkLaunchWeeks
+20
View File
@@ -2942,6 +2942,26 @@ module.exports = [
source: '/launch-week/6',
destination: '/blog/launch-week-6-wrap-up',
},
{
permanent: true,
source: '/launch-week/x',
destination: '/blog/launch-week-x-best-launches',
},
{
permanent: true,
source: '/launch-week/12',
destination: '/blog/launch-week-12-top-10',
},
{
permanent: true,
source: '/launch-week/13',
destination: '/blog/launch-week-13-top-10',
},
{
permanent: true,
source: '/launch-week/14',
destination: '/blog/launch-week-14-top-10',
},
{
permanent: true,
source: '/docs/guides/platform/enterprise-billing',
+3 -9
View File
@@ -46,15 +46,9 @@ export default function App({ Component, pageProps }: AppProps) {
const isDarkLaunchWeek = useDarkLaunchWeeks()
const forceDarkMode = isDarkLaunchWeek
let applicationName = 'Supabase'
let faviconRoute = DEFAULT_FAVICON_ROUTE
let themeColor = DEFAULT_FAVICON_THEME_COLOR
if (router.asPath?.includes('/launch-week/x')) {
applicationName = 'Supabase LWX'
faviconRoute = 'images/launchweek/lwx/favicon'
themeColor = 'FFFFFF'
}
const applicationName = 'Supabase'
const faviconRoute = DEFAULT_FAVICON_ROUTE
const themeColor = DEFAULT_FAVICON_THEME_COLOR
// Advertise the .md version for AI agents on pages that have one.
const cleanPath = (router.asPath ?? '/').split('?')[0].split('#')[0].replace(/\/$/, '') || '/'
-112
View File
@@ -1,112 +0,0 @@
import { useState, useEffect } from 'react'
import dynamic from 'next/dynamic'
import { useRouter } from 'next/router'
import { NextSeo } from 'next-seo'
import { Session } from '@supabase/supabase-js'
import { LW12_DATE, LW12_TITLE, LW_URL, SITE_ORIGIN } from '~/lib/constants'
import supabase from '~/lib/supabase'
import DefaultLayout from '~/components/Layouts/Default'
import { TicketState, ConfDataContext, UserData } from '~/components/LaunchWeek/hooks/use-conf-data'
import SectionContainer from '~/components/Layouts/SectionContainer'
import LWStickyNav from '~/components/LaunchWeek/12/Releases/LWStickyNav'
import LWHeader from '~/components/LaunchWeek/12/Releases/LWHeader'
import MainStage from '~/components/LaunchWeek/12/Releases/MainStage'
const BuildStage = dynamic(() => import('~/components/LaunchWeek/12/Releases/BuildStage'))
const CTABanner = dynamic(() => import('~/components/CTABanner'))
const LaunchWeekPrizeSection = dynamic(
() => import('~/components/LaunchWeek/12/LaunchWeekPrizeSection')
)
const LW12Meetups = dynamic(() => import('~/components/LaunchWeek/12/LWMeetups'))
export default function LaunchWeekIndex() {
const { query } = useRouter()
const TITLE = `${LW12_TITLE} | ${LW12_DATE}`
const DESCRIPTION = 'Join us for a week of announcing new features, every day at 7 AM PT.'
const OG_IMAGE = `${SITE_ORIGIN}/images/launchweek/12/lw12-og.png?lw=12`
const ticketNumber = query.ticketNumber?.toString()
const [session, setSession] = useState<Session | null>(null)
const [showCustomizationForm, setShowCustomizationForm] = useState<boolean>(false)
const defaultUserData = {
id: query.id?.toString(),
ticket_number: ticketNumber ? parseInt(ticketNumber, 10) : undefined,
name: query.name?.toString(),
username: query.username?.toString(),
platinum: !!query.platinum,
}
const [userData, setUserData] = useState<UserData>(defaultUserData)
const [ticketState, setTicketState] = useState<TicketState>('loading')
useEffect(() => {
if (supabase) {
supabase.auth.getSession().then(({ data: { session } }) => setSession(session))
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((_event, session) => {
setSession(session)
})
return () => subscription.unsubscribe()
}
}, [supabase])
useEffect(() => {
if (session?.user) {
if (userData?.id) {
return setTicketState('ticket')
}
return setTicketState('loading')
}
if (!session) return setTicketState('registration')
}, [session, userData])
return (
<>
<NextSeo
title={TITLE}
description={DESCRIPTION}
openGraph={{
title: TITLE,
description: DESCRIPTION,
url: `${LW_URL}/12`,
images: [
{
url: OG_IMAGE,
},
],
}}
/>
<ConfDataContext.Provider
value={{
supabase,
session,
userData,
setUserData,
ticketState,
setTicketState,
showCustomizationForm,
setShowCustomizationForm,
}}
>
<DefaultLayout>
<LWStickyNav />
<LWHeader />
<MainStage className="relative z-10" />
<BuildStage />
<SectionContainer id="meetups" className="scroll-mt-[60px] lw-nav-anchor">
<LW12Meetups meetups={[]} />
</SectionContainer>
<SectionContainer className="pt-8! scroll-mt-[60px] lw-nav-anchor" id="awards">
<LaunchWeekPrizeSection />
</SectionContainer>
<CTABanner />
</DefaultLayout>
</ConfDataContext.Provider>
</>
)
}
-117
View File
@@ -1,117 +0,0 @@
import { useState, useEffect } from 'react'
import dynamic from 'next/dynamic'
import { useRouter } from 'next/router'
import { NextSeo } from 'next-seo'
import { Session } from '@supabase/supabase-js'
import { LW13_DATE, LW13_TITLE, LW_URL, SITE_ORIGIN } from '~/lib/constants'
import supabase from '~/lib/supabase'
import DefaultLayout from '~/components/Layouts/Default'
import { TicketState, ConfDataContext, UserData } from '~/components/LaunchWeek/hooks/use-conf-data'
import LWStickyNav from '~/components/LaunchWeek/13/Releases/LWStickyNav'
import LWHeader from '~/components/LaunchWeek/13/Releases/LWHeader'
import MainStage from '~/components/LaunchWeek/13/Releases/MainStage'
const BuildStage = dynamic(() => import('~/components/LaunchWeek/13/Releases/BuildStage'))
const CTABanner = dynamic(() => import('~/components/CTABanner'))
export default function LaunchWeekIndex() {
const { query } = useRouter()
const TITLE = `${LW13_TITLE} | ${LW13_DATE}`
const DESCRIPTION = 'Join us for a week of announcing new features, every day at 7 AM PT.'
const OG_IMAGE = `${SITE_ORIGIN}/images/launchweek/12/lw13-og.png?lw=12`
const ticketNumber = query.ticketNumber?.toString()
const [session, setSession] = useState<Session | null>(null)
const [showCustomizationForm, setShowCustomizationForm] = useState<boolean>(false)
const defaultUserData = {
id: query.id?.toString(),
ticket_number: ticketNumber ? parseInt(ticketNumber, 10) : undefined,
name: query.name?.toString(),
username: query.username?.toString(),
platinum: !!query.platinum,
}
const [userData, setUserData] = useState<UserData>(defaultUserData)
const [ticketState, setTicketState] = useState<TicketState>('loading')
useEffect(() => {
if (supabase) {
supabase.auth.getSession().then(({ data: { session } }) => setSession(session))
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((_event, session) => {
setSession(session)
})
return () => subscription.unsubscribe()
}
}, [supabase])
useEffect(() => {
if (session?.user) {
if (userData?.id) {
return setTicketState('ticket')
}
return setTicketState('loading')
}
if (!session) return setTicketState('registration')
}, [session, userData])
return (
<>
<NextSeo
title={TITLE}
description={DESCRIPTION}
openGraph={{
title: TITLE,
description: DESCRIPTION,
url: `${LW_URL}/13`,
images: [
{
url: OG_IMAGE,
},
],
}}
/>
<ConfDataContext.Provider
value={{
supabase,
session,
userData,
setUserData,
ticketState,
setTicketState,
showCustomizationForm,
setShowCustomizationForm,
}}
>
<DefaultLayout>
<LWStickyNav />
<LWHeader />
<MainStage className="relative z-10" />
<BuildStage />
<CTABanner />
</DefaultLayout>
</ConfDataContext.Provider>
</>
)
}
// export const getServerSideProps: GetServerSideProps = async () => {
// const { data: meetups } = await supabase!
// .from('meetups')
// .select('*')
// .eq('launch_week', 'lw13')
// .neq('is_published', false)
// .order('start_at')
// return {
// props: {
// meetups,
// },
// }
// }
-61
View File
@@ -1,61 +0,0 @@
import { NextSeo } from 'next-seo'
import { LW14_DATE, LW14_TITLE, LW14_URL, SITE_ORIGIN } from 'lib/constants'
import { useRouter } from 'next/router'
import { Lw14ConfDataProvider } from 'components/LaunchWeek/14/hooks/use-conf-data'
import DefaultLayout from 'components/Layouts/Default'
import LWStickyNav from 'components/LaunchWeek/14/Releases/LWStickyNav'
import LWHeader from 'components/LaunchWeek/14/Releases/LWHeader'
import MainStage from 'components/LaunchWeek/14/Releases/MainStage'
import BuildStage from 'components/LaunchWeek/14/Releases/BuildStage'
const Lw14Page = () => {
const TITLE = `${LW14_TITLE} | ${LW14_DATE}`
const DESCRIPTION = 'Join us for a week of announcing new features, every day at 7 AM PT.'
const OG_IMAGE = `${SITE_ORIGIN}/images/launchweek/14/lw14-og.png?lw=14`
const { query } = useRouter()
const ticketNumber = query.ticketNumber?.toString()
const defaultUserData = {
id: query.id?.toString(),
ticket_number: ticketNumber ? parseInt(ticketNumber, 10) : undefined,
name: query.name?.toString(),
username: query.username?.toString(),
platinum: !!query.platinum,
}
return (
<>
<NextSeo
title={TITLE}
description={DESCRIPTION}
openGraph={{
title: TITLE,
description: DESCRIPTION,
url: LW14_URL,
images: [
{
url: OG_IMAGE,
},
],
}}
/>
<Lw14ConfDataProvider initState={{ userTicketData: defaultUserData, partymodeStatus: 'on' }}>
<DefaultLayout>
<div
style={{
fontFamily: 'Departure Mono, Source Code Pro, Office Code Pro, Menlo, monospace',
}}
>
<LWStickyNav />
<LWHeader />
<MainStage className="relative z-10" />
<BuildStage />
</div>
</DefaultLayout>
</Lw14ConfDataProvider>
</>
)
}
export default Lw14Page
-142
View File
@@ -1,142 +0,0 @@
import { useState, useEffect } from 'react'
import dynamic from 'next/dynamic'
import { GetServerSideProps } from 'next'
import { useRouter } from 'next/router'
import { NextSeo } from 'next-seo'
import { Session } from '@supabase/supabase-js'
import { LW_URL, SITE_ORIGIN } from '~/lib/constants'
import supabase from '~/lib/supabaseMisc'
import FaviconImports from '~/components/LaunchWeek/X/FaviconImports'
import DefaultLayout from '~/components/Layouts/Default'
import { TicketState, ConfDataContext, UserData } from '~/components/LaunchWeek/hooks/use-conf-data'
import SectionContainer from '~/components/Layouts/SectionContainer'
import { Meetup } from '~/components/LaunchWeek/X/LWXMeetups'
import LWXStickyNav from '~/components/LaunchWeek/X/Releases/LWXStickyNav'
import LWXHeader from '~/components/LaunchWeek/X/Releases/LWXHeader'
import MainStage from '~/components/LaunchWeek/X/Releases/MainStage'
const BuildStage = dynamic(() => import('~/components/LaunchWeek/X/Releases/BuildStage'))
const LWXMeetups = dynamic(() => import('~/components/LaunchWeek/X/LWXMeetups'))
const LaunchWeekPrizeSection = dynamic(
() => import('~/components/LaunchWeek/X/LaunchWeekPrizeSection')
)
interface Props {
meetups?: Meetup[]
}
export default function LaunchWeekIndex({ meetups }: Props) {
const { query } = useRouter()
const TITLE = 'Supabase Launch Week X | 11-15 December 2023'
const DESCRIPTION = 'Join us for a week of announcing new features, every day at 8 AM PT.'
const OG_IMAGE = `${SITE_ORIGIN}/images/launchweek/lwx/lwx-og.jpg`
const ticketNumber = query.ticketNumber?.toString()
const bgImageId = query.bgImageId?.toString()
const [session, setSession] = useState<Session | null>(null)
const [showCustomizationForm, setShowCustomizationForm] = useState<boolean>(false)
const defaultUserData = {
id: query.id?.toString(),
ticketNumber: ticketNumber ? parseInt(ticketNumber, 10) : undefined,
name: query.name?.toString(),
username: query.username?.toString(),
golden: !!query.golden,
bgImageId: bgImageId ? parseInt(bgImageId, 10) : undefined,
}
const [userData, setUserData] = useState<UserData>(defaultUserData)
const [ticketState, setTicketState] = useState<TicketState>('loading')
useEffect(() => {
if (supabase) {
supabase.auth.getSession().then(({ data: { session } }) => setSession(session))
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((_event, session) => {
setSession(session)
})
return () => subscription.unsubscribe()
}
}, [supabase])
useEffect(() => {
document.body.classList.add('bg-[#060809]')
return () => {
if (document.body.classList.contains('bg-[#060809]')) {
document.body.classList.remove('bg-[#060809]')
}
}
}, [])
useEffect(() => {
if (session?.user) {
if (userData?.id) {
return setTicketState('ticket')
}
return setTicketState('loading')
}
if (!session) return setTicketState('registration')
}, [session, userData])
return (
<>
<NextSeo
title={TITLE}
description={DESCRIPTION}
openGraph={{
title: TITLE,
description: DESCRIPTION,
url: `${LW_URL}/x`,
images: [
{
url: OG_IMAGE,
},
],
}}
/>
<FaviconImports />
<ConfDataContext.Provider
value={{
supabase,
session,
userData,
setUserData,
ticketState,
setTicketState,
showCustomizationForm,
setShowCustomizationForm,
}}
>
<DefaultLayout>
<LWXStickyNav />
<LWXHeader />
<MainStage />
<BuildStage />
<SectionContainer id="meetups" className="scroll-mt-[-66px]">
<LWXMeetups meetups={meetups} />
</SectionContainer>
<SectionContainer className="lg:pb-40">
<LaunchWeekPrizeSection />
</SectionContainer>
</DefaultLayout>
</ConfDataContext.Provider>
</>
)
}
export const getServerSideProps: GetServerSideProps = async () => {
const { data: meetups } = await supabase!.from('lwx_meetups').select('*')
return {
props: {
meetups:
// @ts-ignore
meetups?.sort((a, b) => (new Date(a.start_at) > new Date(b.start_at) ? 1 : -1)) ?? [],
},
}
}