Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions deploy/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ server {

autoindex off;

error_page 404 /index.html;

location = /favicon.ico {
log_not_found off;
access_log off;
Expand All @@ -32,13 +30,11 @@ server {

location / {
try_files $uri $uri/ /index.html;

add_header X-Not-Found $request_uri;
}

location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|json|txt|webp|woff|woff2)$ {
expires max;
log_not_found off;
try_files $uri =404;
}
}
}
56 changes: 56 additions & 0 deletions src/pages/notFound/index.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import '@testing-library/jest-dom';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { createMemoryHistory } from 'history';
import React from 'react';
import { Router } from 'react-router-dom';
import { NotFoundPage } from '.';

const mockCapture = jest.fn();
const mockPostHog = {
capture: mockCapture
};

jest.mock('posthog-js/react', () => ({
usePostHog: () => mockPostHog
}));

describe('NotFoundPage', () => {
afterEach(() => {
jest.clearAllMocks();
});

it('tracks the missing route with the current path and search params', async () => {
const history = createMemoryHistory({
initialEntries: ['/does-not-exist?source=test']
});

render(
<Router history={history}>
<NotFoundPage />
</Router>
);

await waitFor(() =>
expect(mockCapture).toHaveBeenCalledWith('not_found_page_view', {
path: '/does-not-exist',
search: '?source=test'
})
);
});

it('returns users to the home route', () => {
const history = createMemoryHistory({
initialEntries: ['/missing-route']
});

render(
<Router history={history}>
<NotFoundPage />
</Router>
);

fireEvent.click(screen.getByRole('button', { name: 'Take me back' }));

expect(history.location.pathname).toBe('/');
});
});
14 changes: 12 additions & 2 deletions src/pages/notFound/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import { useHistory } from 'react-router-dom';
import { usePostHog } from 'posthog-js/react';
import React, { useEffect } from 'react';
import { useHistory, useLocation } from 'react-router-dom';
import {
NotFoundPageContainer,
ContentWrapper,
Expand All @@ -14,6 +15,15 @@ import {

export function NotFoundPage() {
const history = useHistory();
const location = useLocation();
const posthog = usePostHog();

useEffect(() => {
posthog?.capture('not_found_page_view', {
path: location.pathname,
search: location.search
});
}, [location.pathname, location.search, posthog]);

return (
<NotFoundPageContainer>
Expand Down
16 changes: 16 additions & 0 deletions src/pages/notFound/nginxConfig.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import fs from 'fs';
import path from 'path';

describe('nginx 404 handling config', () => {
const config = fs.readFileSync(path.join(process.cwd(), 'deploy', 'nginx.conf'), 'utf8');

it('serves the React app for direct SPA route access', () => {
expect(config).toContain('try_files $uri $uri/ /index.html;');
});

it('keeps missing static assets as nginx 404 responses', () => {
expect(config).toContain('location ~* \\.(js|css|png|jpg|jpeg|gif|ico|svg|json|txt|webp|woff|woff2)$');
expect(config).toContain('try_files $uri =404;');
expect(config).not.toContain('error_page 404 /index.html;');
});
});