diff --git a/app/not-found.tsx b/app/not-found.tsx
new file mode 100644
index 00000000..2180c156
--- /dev/null
+++ b/app/not-found.tsx
@@ -0,0 +1 @@
+export { NotFoundPage as default } from '@/pages/not-found';
diff --git a/app/page.tsx b/app/page.tsx
deleted file mode 100644
index a444596c..00000000
--- a/app/page.tsx
+++ /dev/null
@@ -1,6 +0,0 @@
-import { redirect } from 'next/navigation';
-import { PagePath } from '@/shared/const/pages';
-
-export default function RedirectPage() {
- redirect(PagePath.Trade);
-}
diff --git a/app/trade/page.ts b/app/trade/page.ts
deleted file mode 100644
index 52c11093..00000000
--- a/app/trade/page.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { RedirectToPair } from '@/pages/trade/redirect.tsx';
-
-export default RedirectToPair;
diff --git a/middleware.ts b/middleware.ts
new file mode 100644
index 00000000..5d3460f4
--- /dev/null
+++ b/middleware.ts
@@ -0,0 +1,6 @@
+// Redirects "/" and "/trade" paths to "/trade/:primary/:numeraire"
+export const config = {
+ matcher: ['/', '/trade'],
+};
+
+export { tradeMiddleware as middleware } from '@/pages/trade/index.server';
diff --git a/src/pages/not-found/index.ts b/src/pages/not-found/index.ts
new file mode 100644
index 00000000..6278dd6b
--- /dev/null
+++ b/src/pages/not-found/index.ts
@@ -0,0 +1 @@
+export { NotFoundPage } from './page';
diff --git a/src/pages/not-found/link.tsx b/src/pages/not-found/link.tsx
new file mode 100644
index 00000000..5ae95879
--- /dev/null
+++ b/src/pages/not-found/link.tsx
@@ -0,0 +1,15 @@
+'use client';
+
+import Link from 'next/link';
+import { PagePath } from '@/shared/const/pages';
+import { Button } from '@penumbra-zone/ui/Button';
+
+export const GoBackLink = () => {
+ return (
+
+
+
+
+
+ );
+};
diff --git a/src/pages/not-found/page.tsx b/src/pages/not-found/page.tsx
new file mode 100644
index 00000000..a1491161
--- /dev/null
+++ b/src/pages/not-found/page.tsx
@@ -0,0 +1,21 @@
+import { XCircle } from 'lucide-react';
+import { Text } from '@penumbra-zone/ui/Text';
+import { PenumbraWaves } from '@/pages/explore/ui/waves';
+import { GoBackLink } from './link';
+
+export const NotFoundPage = () => {
+ return (
+
+
+
+
+
+
+ Page not found
+
+
+
+
+
+ );
+};
diff --git a/src/pages/trade/api/middleware.ts b/src/pages/trade/api/middleware.ts
new file mode 100644
index 00000000..fb691243
--- /dev/null
+++ b/src/pages/trade/api/middleware.ts
@@ -0,0 +1,23 @@
+import { NextResponse, NextRequest } from 'next/server';
+import { ChainRegistryClient } from '@penumbra-labs/registry';
+import { getClientSideEnv } from '@/shared/api/env/getClientSideEnv';
+import { assetPatterns } from '@penumbra-zone/types/assets';
+
+export const tradeMiddleware = async (request: NextRequest) => {
+ const { PENUMBRA_CHAIN_ID } = getClientSideEnv();
+
+ const chainRegistryClient = new ChainRegistryClient();
+ const registry = await chainRegistryClient.remote.get(PENUMBRA_CHAIN_ID);
+ const allAssets = registry
+ .getAllAssets()
+ .filter(m => !assetPatterns.delegationToken.matches(m.display))
+ .toSorted((a, b) => Number(b.priorityScore - a.priorityScore));
+
+ const baseAsset = allAssets[0]?.symbol;
+ const quoteAsset = allAssets[1]?.symbol;
+ if (!baseAsset || !quoteAsset) {
+ return NextResponse.redirect(new URL('not-found', request.url));
+ }
+
+ return NextResponse.redirect(new URL(`/trade/${baseAsset}/${quoteAsset}`, request.url));
+};
diff --git a/src/pages/trade/index.server.ts b/src/pages/trade/index.server.ts
new file mode 100644
index 00000000..44636ea9
--- /dev/null
+++ b/src/pages/trade/index.server.ts
@@ -0,0 +1 @@
+export { tradeMiddleware } from './api/middleware';
diff --git a/src/pages/trade/redirect.tsx b/src/pages/trade/redirect.tsx
deleted file mode 100644
index e036a58b..00000000
--- a/src/pages/trade/redirect.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-'use client';
-
-import { redirect } from 'next/navigation';
-import { ChainRegistryClient } from '@penumbra-labs/registry';
-import { envQueryFn } from '@/shared/api/env/env.ts';
-import { useQuery } from '@tanstack/react-query';
-import { assetPatterns } from '@penumbra-zone/types/assets';
-
-const redirectSymbolsQueryFn = async () => {
- const { PENUMBRA_CHAIN_ID } = await envQueryFn();
- const chainRegistryClient = new ChainRegistryClient();
- const registry = await chainRegistryClient.remote.get(PENUMBRA_CHAIN_ID);
- const allAssets = registry
- .getAllAssets()
- .filter(m => !assetPatterns.delegationToken.matches(m.display))
- .toSorted((a, b) => Number(b.priorityScore - a.priorityScore));
-
- const baseAsset = allAssets[0]?.symbol;
- const quoteAsset = allAssets[1]?.symbol;
- if (!baseAsset || !quoteAsset) {
- throw new Error('Could not find symbols in registry');
- }
-
- return { baseAsset, quoteAsset };
-};
-
-export const RedirectToPair = () => {
- const { data, isLoading, error } = useQuery({
- queryKey: ['redirectSymbols'],
- retry: 1,
- queryFn: redirectSymbolsQueryFn,
- });
-
- if (error) {
- return {String(error)}
;
- } else if (isLoading || !data) {
- return Loading...
;
- } else {
- redirect(`/trade/${data.baseAsset}/${data.quoteAsset}`);
- }
-};