Certain custom components cause a 500 internal server error
Closed this issue · 1 comments
I have ran into custom components inside PortableText
causing a 500 internal server error twice now. The first time was when I was caching tweets with @upstash/redis
and I thought it was a setup issue. However, this second time, I was using the next-sanity/image
component which has a custom loader to use next/image
but utilize the Sanity CDN for image optimizations.
For simplicity, I will just focus on the next-sanity/image
issue.
Here is my custom portable text:
import Link from 'next/link'
import dynamic from 'next/dynamic'
import {
PortableText,
PortableTextComponents,
PortableTextMarkComponentProps,
} from '@portabletext/react'
import { PortableTextBlock } from 'sanity'
import { CameraIcon } from 'lucide-react'
import { Image as SanityImage } from '@/components/image'
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table'
const Tweet = dynamic(() => import('react-tweet').then((module) => module.Tweet), {
ssr: false,
})
const InternalLink = ({ children, value }: PortableTextMarkComponentProps) => {
let linkHref = `/${value?.reference.slug}`
if (value?.reference._type === 'division') {
linkHref = `/news/${value?.reference.slug}`
}
if (value?.reference._type === 'conference') {
linkHref = `/news/${value?.reference.divisionSlug}/${value?.reference.slug}`
}
return (
<Link href={linkHref} className="underline hover:text-muted-foreground">
{children}
</Link>
)
}
const ImageEmbed = ({ value }: { value: any }) => {
return (
<figure className="my-2 flex flex-col items-center self-center rounded-lg shadow-md">
<SanityImage
src={value}
width={720}
height={379}
priority={false}
className="rounded-lg"
alt={value.caption}
/>
{value.attribution && (
<figcaption className="flex items-center gap-2 text-sm text-muted-foreground">
<CameraIcon className="h-4 w-4" />
<span>Source: {value.attribution}</span>
</figcaption>
)}
</figure>
)
}
export function CustomPortableText({
paragraphClasses,
value,
}: {
paragraphClasses?: string
value: PortableTextBlock[]
}) {
const components: PortableTextComponents = {
block: {
normal: ({ children }) => {
return <p className={paragraphClasses}>{children}</p>
},
},
marks: {
internalLink: InternalLink,
link: ({ value, children }: PortableTextMarkComponentProps) => {
return value?.blank ? (
<a
className="underline hover:text-muted-foreground"
href={value?.href}
rel="noreferrer noopener"
target="_blank"
aria-label={`Opens ${value?.href} in a new tab`}
title={`Opens ${value?.href} in a new tab`}
>
{children}
</a>
) : (
<a className="underline hover:text-muted-foreground" href={value?.href}>
{children}
</a>
)
},
},
types: {
twitter: ({ value }) => {
return (
<div className="not-prose flex items-center justify-center">
<Tweet id={value.id} />
</div>
)
},
image: ImageEmbed,
top25Table: ({ value }) => {
return (
<div className="not-prose">
<Table>
<TableHeader>
<TableRow>
<TableHead>Voter</TableHead>
{Array.from(Array(25).keys()).map((num) => (
<TableHead key={num} className="w-8">
{num + 1}
</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{value.votes.map((vote: any) => {
return (
<TableRow key={vote._key}>
<TableCell className="whitespace-nowrap">
<div className="flex items-center">
<div>
<div className="font-medium">{vote.voterName}</div>
<div className="mt-1 text-sm italic text-muted-foreground">
{vote.voterAffiliation}
</div>
</div>
</div>
</TableCell>
{vote.teams &&
vote.teams.map((team: any) => (
<TableCell key={team._id}>
<div className="w-8">
<SanityImage
className="w-full"
src={team.image}
width={32}
height={32}
alt={team.name}
priority={false}
/>
</div>
</TableCell>
))}
</TableRow>
)
})}
</TableBody>
</Table>
</div>
)
},
},
}
return <PortableText components={components} value={value} />
}
and here is what the import { Image as SanityImage } from '@/components/image'
looks like:
import createImageUrlBuilder from '@sanity/image-url'
import { Image as SanityImage, type ImageProps } from 'next-sanity/image'
import { projectId, dataset } from '@/lib/sanity.api'
const imageBuilder = createImageUrlBuilder({
projectId,
dataset,
})
export const urlForImage = (source: Parameters<(typeof imageBuilder)['image']>[0]) =>
imageBuilder.image(source)
export function Image(
props: Omit<ImageProps, 'src' | 'alt'> & {
src: {
_key?: string | null
_type?: 'image' | string
asset: {
_type: 'reference'
_ref: string
}
crop: {
top: number
bottom: number
left: number
right: number
} | null
hotspot: {
x: number
y: number
height: number
width: number
} | null
caption?: string | undefined
}
alt?: string
},
) {
const { src, ...rest } = props
const imageBuilder = urlForImage(props.src)
if (props.width) {
imageBuilder.width(typeof props.width === 'string' ? parseInt(props.width, 10) : props.width)
}
if (props.height) {
imageBuilder.height(
typeof props.height === 'string' ? parseInt(props.height, 10) : props.height,
)
}
return (
<SanityImage
alt={typeof src.caption === 'string' ? src.caption : ''}
{...rest}
src={imageBuilder.url()}
/>
)
}
Everything works and loads properly locally (whether there are images in the block content or not). However, when I open a PR and go to view an article on a preview environment (whether there are images in the block content or not), I get a 500 internal error.
Here is what an article looks like locally:
and here is what it looks like when going to that same article on my preview environment
Looking at the Vercel logs, this is all that shows up:
This fortunately ended up not being an issue with the Portable Text. Apparently returning []
in my generateStaticParams
for preview env caused this.