diff --git a/deploy/nginx.conf b/deploy/nginx.conf
index f993b6c1f..8dd8738c4 100644
--- a/deploy/nginx.conf
+++ b/deploy/nginx.conf
@@ -11,8 +11,6 @@ server {
autoindex off;
- error_page 404 /index.html;
-
location = /favicon.ico {
log_not_found off;
access_log off;
@@ -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;
}
-}
\ No newline at end of file
+}
diff --git a/src/pages/notFound/index.spec.tsx b/src/pages/notFound/index.spec.tsx
new file mode 100644
index 000000000..81b35c468
--- /dev/null
+++ b/src/pages/notFound/index.spec.tsx
@@ -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(
+
+
+
+ );
+
+ 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(
+
+
+
+ );
+
+ fireEvent.click(screen.getByRole('button', { name: 'Take me back' }));
+
+ expect(history.location.pathname).toBe('/');
+ });
+});
diff --git a/src/pages/notFound/index.tsx b/src/pages/notFound/index.tsx
index 4cec899bc..9b80475b2 100644
--- a/src/pages/notFound/index.tsx
+++ b/src/pages/notFound/index.tsx
@@ -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,
@@ -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 (
diff --git a/src/pages/notFound/nginxConfig.spec.ts b/src/pages/notFound/nginxConfig.spec.ts
new file mode 100644
index 000000000..3dcdfbe53
--- /dev/null
+++ b/src/pages/notFound/nginxConfig.spec.ts
@@ -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;');
+ });
+});