Den manglende linken
Alle rammeverk har sine særegenheter – Next.js er ikke et unntak. Jeg kom over én slik ting da jeg prøvde å debugge hvorfor jeg fikk en 404-feil da jeg prøvde å dele en artikkel på Facebook. Dette er historien om hva som skjedde.
Første akt: Misvisende metadata
Når man deler en nettside på Facebook, henter Facebook noen metadata fra nettsiden. Blant disse er en meta-tagg som heter og:url
. Den skal inneholde en full URL til siden du deler. Jeg hadde denne meta-taggen, men den hadde en feil. URL-en var https://djupvik.digital/blog/2024/11/hallo-verden
, men den skulle ha vært https://djupvik.digital/nb/blog/2024/11/hallo-verden
. Next.js har støtte for flerspråklige nettsteder, og alle norske sider ligger under språkprefikset nb
. Noe var galt i koden min (igjen!).
Før jeg fortsetter vil jeg bare gjøre oppmerksom på at nettstedet mitt bruker den gamle «pages-ruteren» i Next.js, så koden vil være annerledes enn hvis du bruker den nyere «app-ruteren».
Det var ikke vanskelig å finne kilden til feilen. Metadata håndterer jeg i en egen React-komponent. Her er komponenten (redusert til et minimum av relevant kode, og delvis endret for å gjøre koden lettere å forstå):
import Head from 'next/head'
import { useRouter } from 'next/router'
interface MetadataProps {
}
const server = 'https://example.com'
function Metadata(props: MetadataProps) {
const router = useRouter()
const fullURL = new URL(router.asPath, server)
return (
<Head>
<meta content={fullURL.href} key="og:url" property="og:url" />
</Head>
)
}
export default Metadata
Problemet ligger i router.asPath
. Og riktig nok, dette fant jeg i dokumentasjonen: «basePath
og locale
er ikke inkludert» (min oversettelse). Så router.asPath
gir ikke den riktige banen til en språkspesifikk side.
Så hvorfor ikke bare legge på locale
manuelt? Slik:
const fullURL = new URL(`/${router.locale}/${router.asPath}`, server)
Joda, og så må man sjekke at du ikke legger på router.defaultLocale
, for den URL-en har ikke router.locale
foran seg. Alle elementene er jo der. Poenget er: hvorfor gjør Next.js det så vanskelig? Hvorfor ikke bare gi meg den riktige URL-en?
Andre akt: Jakten på URL-en
I stedet for å følge det fornuftige rådet i forrige avsnitt, falt jeg ned i ett av mine sedvanlige kaninhull. Next.js må da ha en eller annen funksjon for å returnere URL-en for den gjeldende siden, tenkte jeg. Jeg googlet etter en løsning.
Jeg kom over en diskusjon på GitHub hvor det henvises til en intern funksjon i Next.js som heter resolveHref
. Den så ut til å være løsningen på problemet. Men gjett hva som skjedde da jeg prøvde å bruke den? Akkurat samme problem som før – locale
er ikke inkludert.
Hva nå? Jeg leste noen flere tips. Noen anbefalte å lage en middleware-funksjon. Det finnes til og med en npm-pakke. Det er bare det at nesten alle rutene mine blir statisk generert, og middleware gjør at jeg må server-rendere alle rutene.
Det var en slags trøst i at problemet er ett som flere har hatt. Hvorfor er det så vanskelig å få en URL fra Next.js? Jeg kom over en lang forklaring. Men noen løsning var ikke i sikte.
Da var det ett eller annet bak i hjernen min som begynte å røre på seg. Hva hvis …?
Tredje akt: Link redder dagen
Her kommer løsningen som jeg kom frem til: Next.js har en link-komponent. Det er jo bare å wrappe Metadata-komponenten i en Link. Kunne det virkelig være så enkelt? Jeg fant frem dokumentasjonen til <Link>
. Der var det et avsnitt som ga meg svaret.
Jeg skyndte meg å implementere endringene, og testet. Suksess! Det var virkelig så enkelt!
Her kommer koden (redusert til et minimum). Først har vi komponenten for siden:
import type { GetStaticProps } from 'next'
import Link from 'next/link'
import Metadata from '../../../../src/scripts/components/metadata'
interface Params {
month: string
slug: string
year: string
}
interface PostProps {
params: Params
}
function Post({ params }: PostProps) {
const href = { query: params }
return (
<Link href={href} legacyBehavior passHref>
<Metadata />
</Link>
)
}
export const getStaticProps: GetStaticProps<{}, Params> = async (context) => {
const {
params = { month: '', slug: '', year: '' },
} = context
return { props: { params } }
}
export default Post
Og så har vi koden for Metadata-komponenten:
import Head from 'next/head'
import { forwardRef, type ForwardRefRenderFunction } from 'react'
interface MetadataProps {
href?: string
}
const server = 'https://example.com'
const Metadata: ForwardRefRenderFunction<HTMLMetaElement, MetadataProps> = (
props,
ref,
) => {
const { href } = props
const fullURL = new URL(href || '/', server)
return (
<Head>
<meta content={fullURL.href} key="og:url" property="og:url" ref={ref} />
</Head>
)
}
const ForwardedMetadata = forwardRef(Metadata)
export default ForwardedMetadata
Som du ser trengs bare et objekt med de dynamiske delene av URL-en som href
på Link
. Metadata
får den ferdige href
-en som en prop.
Teppefall
Hva er moralen i dette lille hverdagsdramaet? Vanskelig å si. Jeg kastet bort mye tid på dette tullet. Jeg kunne spart mye tid ved å bare lage URL-en selv. På den annen side var det litt gøy å finne en sånn merkelig workaround. Så her legger jeg løsningen frem, slik at noen andre som har samme problemet, forhåpentligvis kan spare litt tid.