diff --git a/packages/webapp/__tests__/SquadPageStaticProps.spec.ts b/packages/webapp/__tests__/SquadPageStaticProps.spec.ts index 9da0871925..1a5ae006be 100644 --- a/packages/webapp/__tests__/SquadPageStaticProps.spec.ts +++ b/packages/webapp/__tests__/SquadPageStaticProps.spec.ts @@ -150,7 +150,7 @@ describe('squad page getServerSideProps seo', () => { // SSR is always unauthenticated, so the API rejects every private squad with // FORBIDDEN before we ever see `public`. This fallback used to ship no seo at // all, which left private squads advertised as index,follow. - it.each([ApiError.Forbidden, ApiError.NotFound, ApiError.RateLimited])( + it.each([ApiError.Forbidden, ApiError.RateLimited])( 'marks the %s fallback as noindex and nofollow', async (code) => { mockRequest.mockRejectedValue({ @@ -170,4 +170,15 @@ describe('squad page getServerSideProps seo', () => { }); }, ); + + it('returns a real 404 for a squad that does not exist', async () => { + mockRequest.mockRejectedValue({ + response: { errors: [{ extensions: { code: ApiError.NotFound } }] }, + }); + + const result = await runGssp(); + + expect(result).toEqual({ notFound: true }); + expect(mockStaticFields).not.toHaveBeenCalled(); + }); }); diff --git a/packages/webapp/pages/squads/[handle]/index.tsx b/packages/webapp/pages/squads/[handle]/index.tsx index c22782787f..fe94d9923d 100644 --- a/packages/webapp/pages/squads/[handle]/index.tsx +++ b/packages/webapp/pages/squads/[handle]/index.tsx @@ -501,12 +501,20 @@ export async function getServerSideProps({ const errors = Object.values(ApiError); const errorCode = clientError?.response?.errors?.[0]?.extensions?.code; + // A squad that doesn't exist is a real 404, not a soft one, so crawlers + // and agents don't index or cite a page that isn't there. + if (errorCode === ApiError.NotFound) { + setCacheHeader(); + + return { notFound: true }; + } + if (errors.includes(errorCode)) { setCacheHeader(); // SSR always runs unauthenticated, so every private squad resolves as - // FORBIDDEN here. This branch also serves the soft-404/rate-limited - // cases, none of which should ever be advertised as indexable. + // FORBIDDEN here. This branch also serves the rate-limited and other + // API error cases, none of which should ever be advertised as indexable. return { props: { handle, seo: { ...noindexSeoProps } }, };