Next.js Discord

Discord Forum

Possible way to speed up client side useEffect speed while switching favicons

Answered
Milkfish posted this in #help-forum
Open in Discord
MilkfishOP
Today I was trying to figure out how to change the icon of my next 13 app based on the user's prefered theme and through trial and error managed to make a component called "FaviconSetup" with the following implementation
'use client'

import { useEffect } from 'react'

export default function FaviconSetup() {
    useEffect(() => {
        const matcher: MediaQueryList = window.matchMedia('(prefers-color-scheme: dark)');
        const iconLink: HTMLLinkElement = document.createElement('link');

        iconLink.rel = 'icon';
        iconLink.href = '/icon-light.png';
        
        if(matcher.matches) iconLink.href = '/icon-dark.png';

        document.head.appendChild(iconLink);
    }, []);

    return null;
}

I am aware that there was a post using a the next-themes library but on my end using it didn't prove very successful, while this did I found the icon loads at a speed where the user can briefly notice it not being there, I assume this is because the useEffect takes time to execute, is there a way for me to speed it up or maybe place this piece of code somewhere that will run faster on the client's end?
Answered by Milkfish
After reading more on the subject of metadata, generatedMetadata and icons in general, I managed to find a way to do it using the icon.svg file, with the following svg
<svg height="200px" width="200px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 47.94 47.94" xml:space="preserve" fill="#000000">
    <style>
        g, path {
            fill: black;
        }
        @media (prefers-color-scheme: dark) {
            g, path {
                fill: white;
            }
        }
    </style>
    <g id="SVGRepo_bgCarrier" stroke-width="0"></g>
    <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
    <g id="SVGRepo_iconCarrier"> 
        <path d="M26.285,2.486l5.407,10.956c0.376,0.762,1.103,1.29,1.944,1.412l12.091,1.757 c2.118,0.308,2.963,2.91,1.431,4.403l-8.749,8.528c-0.608,0.593-0.886,1.448-0.742,2.285l2.065,12.042 c0.362,2.109-1.852,3.717-3.746,2.722l-10.814-5.685c-0.752-0.395-1.651-0.395-2.403,0l-10.814,5.685 c-1.894,0.996-4.108-0.613-3.746-2.722l2.065-12.042c0.144-0.837-0.134-1.692-0.742-2.285l-8.749-8.528 c-1.532-1.494-0.687-4.096,1.431-4.403l12.091-1.757c0.841-0.122,1.568-0.65,1.944-1.412l5.407-10.956 C22.602,0.567,25.338,0.567,26.285,2.486z"></path> 
    </g>
</svg>
View full answer

12 Replies

Grass carp
I think you should render different Headers based on the theme. Seems easier
@Grass carp I think you should render different Headers based on the theme. Seems easier
MilkfishOP
Sorry a bit late, but you mean something like
'use client'

export default function Favicon() {
    const matcher: MediaQueryList = window.matchMedia('(prefers-color-scheme: dark)');

    return <link rel="icon" href={(matcher.matches)? '/icon-dark.png' : '/icon-light.png'} />;
}

And then somewhere in my layout.tsx
        <html lang="en">
            <Head>
                <Favicon />
            </Head>
        </html>
Because I've tried this sort of approach and for some reason it doesn't seem to work
@DirtyCajunRice | AppDir cant use head in app dir
MilkfishOP
Oh, so is doing something like this fine then? Because this seems to work
            <head>
                <Favicon />    
            </head>
MilkfishOP
Ah nvm that had some errors in the terminal because the component was still using SSR, after doing some stackoverflowing this is the final (seemingly) working result
// NoSSR component that has everything inside it not use SSR anymore
import dynamic from "next/dynamic";
import React from "react";

function NoSSR({ children }: { children: React.ReactNode }) {
    return <>{children}</>;
}

export default dynamic(() => Promise.resolve(NoSSR), {
    ssr: false,
});

// Favicon component that handle the theme icon loading logic
'use client'

export default function Favicon() {
    const matcher: MediaQueryList = window.matchMedia('(prefers-color-scheme: dark)');

    return <link rel="icon" href={(matcher.matches)? '/icon-dark.png' : '/icon-light.png'} />;
}

// layout.tsx
        <html lang="en">
            <head>
                <NoSSR>
                    <Favicon />
                </NoSSR> 
            </head>
            // rest goes down here
        </html>
@DirtyCajunRice | AppDir you dont use head in app dir
MilkfishOP
Is there a way to do what I'm trying then?
@DirtyCajunRice | AppDir https://nextjs.org/docs/app/api-reference/functions/generate-metadata#generatemetadata-function
MilkfishOP
After reading more on the subject of metadata, generatedMetadata and icons in general, I managed to find a way to do it using the icon.svg file, with the following svg
<svg height="200px" width="200px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 47.94 47.94" xml:space="preserve" fill="#000000">
    <style>
        g, path {
            fill: black;
        }
        @media (prefers-color-scheme: dark) {
            g, path {
                fill: white;
            }
        }
    </style>
    <g id="SVGRepo_bgCarrier" stroke-width="0"></g>
    <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
    <g id="SVGRepo_iconCarrier"> 
        <path d="M26.285,2.486l5.407,10.956c0.376,0.762,1.103,1.29,1.944,1.412l12.091,1.757 c2.118,0.308,2.963,2.91,1.431,4.403l-8.749,8.528c-0.608,0.593-0.886,1.448-0.742,2.285l2.065,12.042 c0.362,2.109-1.852,3.717-3.746,2.722l-10.814-5.685c-0.752-0.395-1.651-0.395-2.403,0l-10.814,5.685 c-1.894,0.996-4.108-0.613-3.746-2.722l2.065-12.042c0.144-0.837-0.134-1.692-0.742-2.285l-8.749-8.528 c-1.532-1.494-0.687-4.096,1.431-4.403l12.091-1.757c0.841-0.122,1.568-0.65,1.944-1.412l5.407-10.956 C22.602,0.567,25.338,0.567,26.285,2.486z"></path> 
    </g>
</svg>
Answer
MilkfishOP
It manages to solve the issue with minimal effort I feel
Thanks for the help