I’m using the app mode in a new nextjs project, and I have the requirement to update the favicon so that it’s different for different pages. I am doing this by having a global object like this:
interface ILayoutParams
{
favicon: string
}
const LayoutParams: ILayoutParams = {} as any
And having each child page set this property:
const ThisPage = () =>
{
LayoutParams.favicon = "This"
return <>content...</>
}
const ThatPage = () =>
{
LayoutParams.favicon = "That"
return <>content...</>
}
and, finally, rendering the icon like so in the layout component:
metadata.icons =
{
icon: `/images/favicon/${LayoutParams.favicon}.ico`,
}
This does work: the mysite.com/this
page loads and shows the correct icon This.ico
However, when I navigate to another page mysite.com/that
, it does NOT update the icon. If I then hard refresh the page, it again shows the correct icon.
I assume this is because the navigation is not actually loading the page, but swapping it in client-side, and this bypasses my LayoutParams scheme. But I cannot find any way to make it work the way I want.
What is the right way to pass child page properties to the layout page in such a way that it works on the server and the client?
You’re probably right in saying that this approach won’t work because of how navigation is managed: layouts in fact don’t re-render on navigation.
I think you could just use this approach with a custom function that returns a different icon based on the url params.
// app/shop/[slug]/icon.tsx
export default function Icon({ params }: { params: { slug: string } }) {
// if (slug === 'this') return <ThisIcon/>
// if (slug === 'that') return <ThatIcon/>
}
here the docs about making it based on the page you’re on, using the url params.
Alternatively, you could use a dedicated icon.tsx file for every page where a specific favicon is needed.
// app/this-page/icon.tsx
import { ImageResponse } from 'next/og'
// Image metadata
export const size = {
width: 32,
height: 32,
}
export const contentType = 'image/png'
// Image generation
export default function Icon() {
return new ImageResponse(
(
// ImageResponse JSX element
<div
style={{
fontSize: 24,
background: 'black',
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'white',
}}
>
A
</div>
),
// ImageResponse options
{
// For convenience, we can re-use the exported icons size metadata
// config to also set the ImageResponse's width and height.
...size,
}
)
}
Mind that “The default export function should return a Blob | ArrayBuffer | TypedArray | DataView | ReadableStream | Response” https://nextjs.org/docs/app/api-reference/file-conventions/metadata/app-icons#returns
EDIT: since this got me curious, there is another very “silly” solution. Just putting the relevant image as an “icon.png (or .ico or whatever)” directly in the interested page folder.
screenshot