diff --git a/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/create/page.tsx b/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/create/page.tsx
index 2e93c057..837f913e 100644
--- a/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/create/page.tsx
+++ b/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/create/page.tsx
@@ -1,4 +1,5 @@
import React from 'react';
+import { notFound } from 'next/navigation';
import { BoardRouteParameters } from '@/app/lib/types';
import { getBoardDetails } from '@/app/lib/__generated__/product-sizes-data';
import { parseBoardRouteParams } from '@/app/lib/url-utils';
@@ -37,49 +38,54 @@ function getMoonBoardHoldSetImages(layoutKey: MoonBoardLayoutKey, setIds: number
}
export default async function CreateClimbPage(props: CreateClimbPageProps) {
- const [params, searchParams] = await Promise.all([props.params, props.searchParams]);
+ try {
+ const [params, searchParams] = await Promise.all([props.params, props.searchParams]);
- // Check if any parameters are in numeric format (old URLs)
- const hasNumericParams = [params.layout_id, params.size_id, params.set_ids].some((param) =>
- param.includes(',') ? param.split(',').every((id) => /^\d+$/.test(id.trim())) : /^\d+$/.test(param),
- );
+ // Check if any parameters are in numeric format (old URLs)
+ const hasNumericParams = [params.layout_id, params.size_id, params.set_ids].some((param) =>
+ param.includes(',') ? param.split(',').every((id) => /^\d+$/.test(id.trim())) : /^\d+$/.test(param),
+ );
- let parsedParams;
+ let parsedParams;
- if (hasNumericParams) {
- parsedParams = parseBoardRouteParams(params);
- } else {
- parsedParams = await parseBoardRouteParamsWithSlugs(params);
- }
+ if (hasNumericParams) {
+ parsedParams = parseBoardRouteParams(params);
+ } else {
+ parsedParams = await parseBoardRouteParamsWithSlugs(params);
+ }
+
+ // Handle MoonBoard separately (no database, different renderer)
+ if (parsedParams.board_name === 'moonboard') {
+ const layoutInfo = getMoonBoardLayoutInfo(parsedParams.layout_id);
+ if (!layoutInfo) {
+ return
Invalid MoonBoard layout
;
+ }
- // Handle MoonBoard separately (no database, different renderer)
- if (parsedParams.board_name === 'moonboard') {
- const layoutInfo = getMoonBoardLayoutInfo(parsedParams.layout_id);
- if (!layoutInfo) {
- return Invalid MoonBoard layout
;
+ const holdSetImages = getMoonBoardHoldSetImages(layoutInfo.layoutKey, parsedParams.set_ids);
+
+ return (
+
+ );
}
- const holdSetImages = getMoonBoardHoldSetImages(layoutInfo.layoutKey, parsedParams.set_ids);
+ // Aurora boards (kilter, tension) - use database
+ const boardDetails = await getBoardDetails(parsedParams);
return (
-
);
+ } catch (error) {
+ console.error('Error in create page:', error);
+ notFound();
}
-
- // Aurora boards (kilter, tension) - use database
- const boardDetails = await getBoardDetails(parsedParams);
-
- return (
-
- );
}
diff --git a/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/import/page.tsx b/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/import/page.tsx
index ce909b71..92159f4f 100644
--- a/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/import/page.tsx
+++ b/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/import/page.tsx
@@ -9,7 +9,7 @@ import {
MoonBoardLayoutKey,
} from '@/app/lib/moonboard-config';
import { Metadata } from 'next';
-import { redirect } from 'next/navigation';
+import { notFound, redirect } from 'next/navigation';
export const metadata: Metadata = {
title: 'Import Climbs | Boardsesh',
@@ -35,40 +35,45 @@ function getMoonBoardHoldSetImages(layoutKey: MoonBoardLayoutKey, setIds: number
}
export default async function ImportPage(props: ImportPageProps) {
- const params = await props.params;
+ try {
+ const params = await props.params;
- // Check if any parameters are in numeric format (old URLs)
- const hasNumericParams = [params.layout_id, params.size_id, params.set_ids].some((param) =>
- param.includes(',') ? param.split(',').every((id) => /^\d+$/.test(id.trim())) : /^\d+$/.test(param),
- );
+ // Check if any parameters are in numeric format (old URLs)
+ const hasNumericParams = [params.layout_id, params.size_id, params.set_ids].some((param) =>
+ param.includes(',') ? param.split(',').every((id) => /^\d+$/.test(id.trim())) : /^\d+$/.test(param),
+ );
- let parsedParams;
+ let parsedParams;
- if (hasNumericParams) {
- parsedParams = parseBoardRouteParams(params);
- } else {
- parsedParams = await parseBoardRouteParamsWithSlugs(params);
- }
+ if (hasNumericParams) {
+ parsedParams = parseBoardRouteParams(params);
+ } else {
+ parsedParams = await parseBoardRouteParamsWithSlugs(params);
+ }
- // Only MoonBoard supports bulk import for now
- if (parsedParams.board_name !== 'moonboard') {
- // Redirect to the board's climb list page
- redirect(`/${params.board_name}/${params.layout_id}/${params.size_id}/${params.set_ids}/${params.angle}`);
- }
+ // Only MoonBoard supports bulk import for now
+ if (parsedParams.board_name !== 'moonboard') {
+ // Redirect to the board's climb list page
+ redirect(`/${params.board_name}/${params.layout_id}/${params.size_id}/${params.set_ids}/${params.angle}`);
+ }
- const layoutInfo = getMoonBoardLayoutInfo(parsedParams.layout_id);
- if (!layoutInfo) {
- return Invalid MoonBoard layout
;
- }
+ const layoutInfo = getMoonBoardLayoutInfo(parsedParams.layout_id);
+ if (!layoutInfo) {
+ return Invalid MoonBoard layout
;
+ }
- const holdSetImages = getMoonBoardHoldSetImages(layoutInfo.layoutKey, parsedParams.set_ids);
+ const holdSetImages = getMoonBoardHoldSetImages(layoutInfo.layoutKey, parsedParams.set_ids);
- return (
-
- );
+ return (
+
+ );
+ } catch (error) {
+ console.error('Error in import page:', error);
+ notFound();
+ }
}
diff --git a/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/layout.tsx b/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/layout.tsx
index 9f4697c9..280a28a9 100644
--- a/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/layout.tsx
+++ b/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/layout.tsx
@@ -4,7 +4,7 @@ import { Affix, Layout } from 'antd';
import { ParsedBoardRouteParameters, BoardRouteParameters, BoardDetails } from '@/app/lib/types';
import { parseBoardRouteParams, constructClimbListWithSlugs } from '@/app/lib/url-utils';
import { parseBoardRouteParamsWithSlugs } from '@/app/lib/url-utils.server';
-import { permanentRedirect } from 'next/navigation';
+import { notFound, permanentRedirect } from 'next/navigation';
import { Content } from 'antd/es/layout/layout';
import QueueControlBar from '@/app/components/queue-control/queue-control-bar';
import { getBoardDetails } from '@/app/lib/__generated__/product-sizes-data';
@@ -103,80 +103,85 @@ interface BoardLayoutProps {
}
export default async function BoardLayout(props: PropsWithChildren) {
- const params = await props.params;
+ try {
+ const params = await props.params;
+
+ const { children } = props;
+
+ // Parse the route parameters
+ // Check if any parameters are in numeric format (old URLs)
+ const hasNumericParams = [params.layout_id, params.size_id, params.set_ids].some((param) =>
+ param.includes(',') ? param.split(',').every((id) => /^\d+$/.test(id.trim())) : /^\d+$/.test(param),
+ );
- const { children } = props;
+ let parsedParams: ParsedBoardRouteParameters;
- // Parse the route parameters
- // Check if any parameters are in numeric format (old URLs)
- const hasNumericParams = [params.layout_id, params.size_id, params.set_ids].some((param) =>
- param.includes(',') ? param.split(',').every((id) => /^\d+$/.test(id.trim())) : /^\d+$/.test(param),
- );
+ if (hasNumericParams) {
+ // For old URLs, use the simple parsing function first
+ parsedParams = parseBoardRouteParams(params);
- let parsedParams: ParsedBoardRouteParameters;
+ // Redirect old URLs to new slug format
+ const boardDetails = getBoardDetailsUniversal(parsedParams);
+
+ if (boardDetails.layout_name && boardDetails.size_name && boardDetails.set_names) {
+ const newUrl = constructClimbListWithSlugs(
+ boardDetails.board_name,
+ boardDetails.layout_name,
+ boardDetails.size_name,
+ boardDetails.size_description,
+ boardDetails.set_names,
+ parsedParams.angle,
+ );
+
+ permanentRedirect(newUrl);
+ }
+ } else {
+ // For new URLs, use the slug parsing function
+ parsedParams = await parseBoardRouteParamsWithSlugs(params);
+ }
- if (hasNumericParams) {
- // For old URLs, use the simple parsing function first
- parsedParams = parseBoardRouteParams(params);
+ const { board_name, angle } = parsedParams;
- // Redirect old URLs to new slug format
+ // Fetch the board details server-side
const boardDetails = getBoardDetailsUniversal(parsedParams);
- if (boardDetails.layout_name && boardDetails.size_name && boardDetails.set_names) {
- const newUrl = constructClimbListWithSlugs(
- boardDetails.board_name,
- boardDetails.layout_name,
- boardDetails.size_name,
- boardDetails.size_description,
- boardDetails.set_names,
- parsedParams.angle,
- );
-
- permanentRedirect(newUrl);
- }
- } else {
- // For new URLs, use the slug parsing function
- parsedParams = await parseBoardRouteParamsWithSlugs(params);
+ return (
+
+
+
+
+
+
+
+
+ }>
+ {children}
+
+
+
+
+
+
+
+
+
+
+
+ );
+ } catch (error) {
+ console.error('Error in board layout:', error);
+ notFound();
}
-
- const { board_name, angle } = parsedParams;
-
- // Fetch the board details server-side
- const boardDetails = getBoardDetailsUniversal(parsedParams);
-
- return (
-
-
-
-
-
-
-
-
- }>
- {children}
-
-
-
-
-
-
-
-
-
-
-
- );
}
diff --git a/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/layout.tsx b/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/layout.tsx
index 1a9ed9f4..de61d1fa 100644
--- a/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/layout.tsx
+++ b/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/layout.tsx
@@ -6,7 +6,7 @@ import { BoardRouteParameters, ParsedBoardRouteParameters } from '@/app/lib/type
import { parseBoardRouteParams, constructClimbListWithSlugs } from '@/app/lib/url-utils';
import { parseBoardRouteParamsWithSlugs } from '@/app/lib/url-utils.server';
import { getBoardDetails } from '@/app/lib/__generated__/product-sizes-data';
-import { permanentRedirect } from 'next/navigation';
+import { notFound, permanentRedirect } from 'next/navigation';
import ListLayoutClient from './layout-client';
interface LayoutProps {
@@ -14,43 +14,48 @@ interface LayoutProps {
}
export default async function ListLayout(props: PropsWithChildren) {
- const params = await props.params;
-
- const { children } = props;
-
- // Check if any parameters are in numeric format (old URLs)
- const hasNumericParams = [params.layout_id, params.size_id, params.set_ids].some((param) =>
- param.includes(',') ? param.split(',').every((id) => /^\d+$/.test(id.trim())) : /^\d+$/.test(param),
- );
-
- let parsedParams: ParsedBoardRouteParameters;
-
- if (hasNumericParams) {
- // For old URLs, use the simple parsing function first
- parsedParams = parseBoardRouteParams(params);
+ try {
+ const params = await props.params;
+
+ const { children } = props;
+
+ // Check if any parameters are in numeric format (old URLs)
+ const hasNumericParams = [params.layout_id, params.size_id, params.set_ids].some((param) =>
+ param.includes(',') ? param.split(',').every((id) => /^\d+$/.test(id.trim())) : /^\d+$/.test(param),
+ );
+
+ let parsedParams: ParsedBoardRouteParameters;
+
+ if (hasNumericParams) {
+ // For old URLs, use the simple parsing function first
+ parsedParams = parseBoardRouteParams(params);
+
+ // Redirect old URLs to new slug format
+ const boardDetails = await getBoardDetails(parsedParams);
+
+ if (boardDetails.layout_name && boardDetails.size_name && boardDetails.set_names) {
+ const newUrl = constructClimbListWithSlugs(
+ boardDetails.board_name,
+ boardDetails.layout_name,
+ boardDetails.size_name,
+ boardDetails.size_description,
+ boardDetails.set_names,
+ parsedParams.angle,
+ );
+
+ permanentRedirect(newUrl);
+ }
+ } else {
+ // For new URLs, use the slug parsing function
+ parsedParams = await parseBoardRouteParamsWithSlugs(params);
+ }
- // Redirect old URLs to new slug format
+ // Fetch the climbs and board details server-side
const boardDetails = await getBoardDetails(parsedParams);
- if (boardDetails.layout_name && boardDetails.size_name && boardDetails.set_names) {
- const newUrl = constructClimbListWithSlugs(
- boardDetails.board_name,
- boardDetails.layout_name,
- boardDetails.size_name,
- boardDetails.size_description,
- boardDetails.set_names,
- parsedParams.angle,
- );
-
- permanentRedirect(newUrl);
- }
- } else {
- // For new URLs, use the slug parsing function
- parsedParams = await parseBoardRouteParamsWithSlugs(params);
+ return {children};
+ } catch (error) {
+ console.error('Error in list layout:', error);
+ notFound();
}
-
- // Fetch the climbs and board details server-side
- const boardDetails = await getBoardDetails(parsedParams);
-
- return {children};
}
diff --git a/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/page.tsx b/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/page.tsx
index ace45492..f6135833 100644
--- a/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/page.tsx
+++ b/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/page.tsx
@@ -18,109 +18,106 @@ export default async function DynamicResultsPage(props: {
params: Promise;
searchParams: Promise;
}) {
- const searchParams = await props.searchParams;
- const params = await props.params;
- // Check if any parameters are in numeric format (old URLs)
- const hasNumericParams = [params.layout_id, params.size_id, params.set_ids].some((param) =>
- param.includes(',') ? param.split(',').every((id) => /^\d+$/.test(id.trim())) : /^\d+$/.test(param),
- );
-
- let parsedParams;
-
- if (hasNumericParams) {
- // For old URLs, use the simple parsing function first
- parsedParams = parseBoardRouteParams(params);
-
- // Redirect old URLs to new slug format
- const boardDetails = await getBoardDetails(parsedParams);
-
- if (boardDetails.layout_name && boardDetails.size_name && boardDetails.set_names) {
- const newUrl = constructClimbListWithSlugs(
- boardDetails.board_name,
- boardDetails.layout_name,
- boardDetails.size_name,
- boardDetails.size_description,
- boardDetails.set_names,
- parsedParams.angle,
- );
-
- // Preserve search parameters
- const searchString = new URLSearchParams(
- Object.entries(searchParams).reduce(
- (acc, [key, value]) => {
- if (value !== undefined) {
- acc[key] = String(value);
- }
- return acc;
- },
- {} as Record,
- ),
- ).toString();
- const finalUrl = searchString ? `${newUrl}?${searchString}` : newUrl;
-
- permanentRedirect(finalUrl);
+ try {
+ const searchParams = await props.searchParams;
+ const params = await props.params;
+ // Check if any parameters are in numeric format (old URLs)
+ const hasNumericParams = [params.layout_id, params.size_id, params.set_ids].some((param) =>
+ param.includes(',') ? param.split(',').every((id) => /^\d+$/.test(id.trim())) : /^\d+$/.test(param),
+ );
+
+ let parsedParams;
+
+ if (hasNumericParams) {
+ // For old URLs, use the simple parsing function first
+ parsedParams = parseBoardRouteParams(params);
+
+ // Redirect old URLs to new slug format
+ const boardDetails = await getBoardDetails(parsedParams);
+
+ if (boardDetails.layout_name && boardDetails.size_name && boardDetails.set_names) {
+ const newUrl = constructClimbListWithSlugs(
+ boardDetails.board_name,
+ boardDetails.layout_name,
+ boardDetails.size_name,
+ boardDetails.size_description,
+ boardDetails.set_names,
+ parsedParams.angle,
+ );
+
+ // Preserve search parameters
+ const searchString = new URLSearchParams(
+ Object.entries(searchParams).reduce(
+ (acc, [key, value]) => {
+ if (value !== undefined) {
+ acc[key] = String(value);
+ }
+ return acc;
+ },
+ {} as Record,
+ ),
+ ).toString();
+ const finalUrl = searchString ? `${newUrl}?${searchString}` : newUrl;
+
+ permanentRedirect(finalUrl);
+ }
+ } else {
+ // For new URLs, use the slug parsing function
+ parsedParams = await parseBoardRouteParamsWithSlugs(params);
}
- } else {
- // For new URLs, use the slug parsing function
- parsedParams = await parseBoardRouteParamsWithSlugs(params);
- }
-
- const searchParamsObject: SearchRequestPagination = parsedRouteSearchParamsToSearchParams(searchParams);
- // For the SSR version we increase the pageSize so it also gets whatever page number
- // is in the search params. Without this, it would load the SSR version of the page on page 2
- // which would then flicker once SWR runs on the client.
- const requestedPageSize = (Number(searchParamsObject.page) + 1) * Number(searchParamsObject.pageSize);
-
- // Enforce max page size to prevent excessive database queries
- searchParamsObject.pageSize = Math.min(requestedPageSize, MAX_PAGE_SIZE);
- searchParamsObject.page = 0;
-
- // Build the search input for caching
- // Note: We only cache non-personalized queries (no auth-dependent filters)
- // User-specific filters (hideAttempted, hideCompleted, etc.) are applied client-side
- const searchInput = {
- boardName: parsedParams.board_name,
- layoutId: parsedParams.layout_id,
- sizeId: parsedParams.size_id,
- setIds: parsedParams.set_ids.join(','),
- angle: parsedParams.angle,
- page: searchParamsObject.page,
- pageSize: searchParamsObject.pageSize,
- gradeAccuracy: searchParamsObject.gradeAccuracy ? String(searchParamsObject.gradeAccuracy) : undefined,
- minGrade: searchParamsObject.minGrade || undefined,
- maxGrade: searchParamsObject.maxGrade || undefined,
- minAscents: searchParamsObject.minAscents || undefined,
- sortBy: searchParamsObject.sortBy || 'ascents',
- sortOrder: searchParamsObject.sortOrder || 'desc',
- name: searchParamsObject.name || undefined,
- setter: searchParamsObject.settername && searchParamsObject.settername.length > 0 ? searchParamsObject.settername : undefined,
- };
-
- // Check if this is a default search (no custom filters applied)
- // Default searches can be cached much longer (30 days vs 1 hour)
- const isDefaultSearch =
- !searchParamsObject.gradeAccuracy &&
- !searchParamsObject.minGrade &&
- !searchParamsObject.maxGrade &&
- !searchParamsObject.minAscents &&
- !searchParamsObject.name &&
- (!searchParamsObject.settername || searchParamsObject.settername.length === 0) &&
- (searchParamsObject.sortBy || 'ascents') === 'ascents' &&
- (searchParamsObject.sortOrder || 'desc') === 'desc';
-
- let searchResponse: ClimbSearchResponse;
- let boardDetails: BoardDetails;
-
- try {
- [searchResponse, boardDetails] = await Promise.all([
+ const searchParamsObject: SearchRequestPagination = parsedRouteSearchParamsToSearchParams(searchParams);
+
+ // For the SSR version we increase the pageSize so it also gets whatever page number
+ // is in the search params. Without this, it would load the SSR version of the page on page 2
+ // which would then flicker once SWR runs on the client.
+ const requestedPageSize = (Number(searchParamsObject.page) + 1) * Number(searchParamsObject.pageSize);
+
+ // Enforce max page size to prevent excessive database queries
+ searchParamsObject.pageSize = Math.min(requestedPageSize, MAX_PAGE_SIZE);
+ searchParamsObject.page = 0;
+
+ // Build the search input for caching
+ // Note: We only cache non-personalized queries (no auth-dependent filters)
+ // User-specific filters (hideAttempted, hideCompleted, etc.) are applied client-side
+ const searchInput = {
+ boardName: parsedParams.board_name,
+ layoutId: parsedParams.layout_id,
+ sizeId: parsedParams.size_id,
+ setIds: parsedParams.set_ids.join(','),
+ angle: parsedParams.angle,
+ page: searchParamsObject.page,
+ pageSize: searchParamsObject.pageSize,
+ gradeAccuracy: searchParamsObject.gradeAccuracy ? String(searchParamsObject.gradeAccuracy) : undefined,
+ minGrade: searchParamsObject.minGrade || undefined,
+ maxGrade: searchParamsObject.maxGrade || undefined,
+ minAscents: searchParamsObject.minAscents || undefined,
+ sortBy: searchParamsObject.sortBy || 'ascents',
+ sortOrder: searchParamsObject.sortOrder || 'desc',
+ name: searchParamsObject.name || undefined,
+ setter: searchParamsObject.settername && searchParamsObject.settername.length > 0 ? searchParamsObject.settername : undefined,
+ };
+
+ // Check if this is a default search (no custom filters applied)
+ // Default searches can be cached much longer (30 days vs 1 hour)
+ const isDefaultSearch =
+ !searchParamsObject.gradeAccuracy &&
+ !searchParamsObject.minGrade &&
+ !searchParamsObject.maxGrade &&
+ !searchParamsObject.minAscents &&
+ !searchParamsObject.name &&
+ (!searchParamsObject.settername || searchParamsObject.settername.length === 0) &&
+ (searchParamsObject.sortBy || 'ascents') === 'ascents' &&
+ (searchParamsObject.sortOrder || 'desc') === 'desc';
+
+ const [searchResponse, boardDetails]: [ClimbSearchResponse, BoardDetails] = await Promise.all([
cachedSearchClimbs(SEARCH_CLIMBS, { input: searchInput }, isDefaultSearch),
getBoardDetails(parsedParams),
]);
+
+ return ;
} catch (error) {
- console.error('Error fetching results or climb:', error);
+ console.error('Error in list page:', error);
notFound();
}
-
- return ;
}
diff --git a/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/play/[climb_uuid]/page.tsx b/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/play/[climb_uuid]/page.tsx
index 8b77e22b..f3a41760 100644
--- a/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/play/[climb_uuid]/page.tsx
+++ b/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/play/[climb_uuid]/page.tsx
@@ -1,4 +1,5 @@
import React from 'react';
+import { notFound } from 'next/navigation';
import { BoardRouteParametersWithUuid } from '@/app/lib/types';
import { parseBoardRouteParams, extractUuidFromSlug, constructPlayUrlWithSlugs } from '@/app/lib/url-utils';
import { parseBoardRouteParamsWithSlugs } from '@/app/lib/url-utils.server';
@@ -79,46 +80,51 @@ export async function generateMetadata(props: { params: Promise;
}): Promise {
- const params = await props.params;
+ try {
+ const params = await props.params;
- // Check if any parameters are in numeric format (old URLs)
- const hasNumericParams = [params.layout_id, params.size_id, params.set_ids].some((param) =>
- param.includes(',') ? param.split(',').every((id) => /^\d+$/.test(id.trim())) : /^\d+$/.test(param),
- );
+ // Check if any parameters are in numeric format (old URLs)
+ const hasNumericParams = [params.layout_id, params.size_id, params.set_ids].some((param) =>
+ param.includes(',') ? param.split(',').every((id) => /^\d+$/.test(id.trim())) : /^\d+$/.test(param),
+ );
- let parsedParams;
+ let parsedParams;
- if (hasNumericParams) {
- parsedParams = parseBoardRouteParams({
- ...params,
- climb_uuid: extractUuidFromSlug(params.climb_uuid),
- });
- } else {
- parsedParams = await parseBoardRouteParamsWithSlugs(params);
- }
+ if (hasNumericParams) {
+ parsedParams = parseBoardRouteParams({
+ ...params,
+ climb_uuid: extractUuidFromSlug(params.climb_uuid),
+ });
+ } else {
+ parsedParams = await parseBoardRouteParamsWithSlugs(params);
+ }
- const boardDetails = await getBoardDetails(parsedParams);
+ const boardDetails = await getBoardDetails(parsedParams);
- // Try to get the initial climb for SSR
- let initialClimb = null;
- try {
- const climb = await getClimb(parsedParams);
- if (climb) {
- const litUpHoldsMap = convertLitUpHoldsStringToMap(climb.frames, parsedParams.board_name)[0];
- initialClimb = {
- ...climb,
- litUpHoldsMap,
- };
+ // Try to get the initial climb for SSR
+ let initialClimb = null;
+ try {
+ const climb = await getClimb(parsedParams);
+ if (climb) {
+ const litUpHoldsMap = convertLitUpHoldsStringToMap(climb.frames, parsedParams.board_name)[0];
+ initialClimb = {
+ ...climb,
+ litUpHoldsMap,
+ };
+ }
+ } catch {
+ // Climb will be loaded from queue context on client
}
- } catch {
- // Climb will be loaded from queue context on client
- }
- return (
-
- );
+ return (
+
+ );
+ } catch (error) {
+ console.error('Error in play page:', error);
+ notFound();
+ }
}
diff --git a/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/play/layout.tsx b/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/play/layout.tsx
index 783e7ada..df295517 100644
--- a/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/play/layout.tsx
+++ b/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/play/layout.tsx
@@ -1,5 +1,6 @@
import React from 'react';
import { PropsWithChildren } from 'react';
+import { notFound } from 'next/navigation';
import { BoardRouteParameters, ParsedBoardRouteParameters } from '@/app/lib/types';
import { parseBoardRouteParams } from '@/app/lib/url-utils';
@@ -12,23 +13,28 @@ interface LayoutProps {
}
export default async function PlayLayout(props: PropsWithChildren) {
- const params = await props.params;
- const { children } = props;
+ try {
+ const params = await props.params;
+ const { children } = props;
- // Check if any parameters are in numeric format (old URLs)
- const hasNumericParams = [params.layout_id, params.size_id, params.set_ids].some((param) =>
- param.includes(',') ? param.split(',').every((id) => /^\d+$/.test(id.trim())) : /^\d+$/.test(param),
- );
+ // Check if any parameters are in numeric format (old URLs)
+ const hasNumericParams = [params.layout_id, params.size_id, params.set_ids].some((param) =>
+ param.includes(',') ? param.split(',').every((id) => /^\d+$/.test(id.trim())) : /^\d+$/.test(param),
+ );
- let parsedParams: ParsedBoardRouteParameters;
+ let parsedParams: ParsedBoardRouteParameters;
- if (hasNumericParams) {
- parsedParams = parseBoardRouteParams(params);
- } else {
- parsedParams = await parseBoardRouteParamsWithSlugs(params);
- }
+ if (hasNumericParams) {
+ parsedParams = parseBoardRouteParams(params);
+ } else {
+ parsedParams = await parseBoardRouteParamsWithSlugs(params);
+ }
- const boardDetails = await getBoardDetails(parsedParams);
+ const boardDetails = await getBoardDetails(parsedParams);
- return {children};
+ return {children};
+ } catch (error) {
+ console.error('Error in play layout:', error);
+ notFound();
+ }
}