Contents
  1. What the server has to do
  2. Serving icons with Express
  3. Without a framework: the built-in http module
  4. The HTML link tags
  5. Preparing the SVG from a vector logo
  6. Why the favicon is not showing: checks in order
  7. Rights to the logo
  8. What this guide cannot confirm

Setting up a favicon in Node.js comes down to two jobs. The server must return the icon files at predictable paths with the correct Content-Type header, and the HTML pages must point to those files with link tags. Node.js has no favicon feature of its own, so the method depends on how the app serves files: through Express static middleware, through a dedicated favicon middleware, or through a hand-written route in the built-in http module. This guide covers all three, along with preparing an SVG icon from a vector logo and an ordered checklist for when the icon does not appear. No details about a specific project were supplied, so the examples use common conventions that you may need to adapt.

What the server has to do

Browsers request an icon in two ways. Many still request the root-level favicon.ico path automatically, even when the page declares no icon. They also follow any icon link tags in the page head. A working setup therefore serves a favicon.ico at the site root as a fallback and serves any SVG or PNG icons that the HTML references. Each file must be reachable without authentication and returned with a matching MIME type: image/x-icon for ICO, image/svg+xml for SVG, and image/png for PNG.

Serving icons with Express

The simplest approach in Express is to place the icon files in a public folder and serve that folder with the built-in static middleware. Express sets the Content-Type from the file extension, so an .svg file is sent as image/svg+xml without extra configuration.

const express = require('express');
const path = require('path');

const app = express();

// Serves favicon.ico, favicon.svg and other icons from ./public
app.use(express.static(path.join(__dirname, 'public')));

app.listen(process.env.PORT);

Register the static middleware before any catch-all route. In single-page apps, a fallback route that returns index.html for every unknown path is a frequent cause of failure: the browser asks for the icon and receives an HTML document.

The serve-favicon package is an alternative for the default icon. It keeps the file in memory and answers requests for the root favicon.ico path early in the middleware chain, before logging or session middleware run.

const favicon = require('serve-favicon');

app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));

This package handles only the implicit favicon.ico request. SVG icons, Apple touch icons and any other files referenced from HTML still need static middleware or explicit routes.

Without a framework: the built-in http module

With the plain http module, nothing is served unless you write a route for it. Map each icon path to a file and a MIME type, read the file, and send it with the correct header.

const http = require('http');
const fs = require('fs');
const path = require('path');

const icons = {
  '/favicon.ico': { file: 'favicon.ico', type: 'image/x-icon' },
  '/favicon.svg': { file: 'favicon.svg', type: 'image/svg+xml' }
};

http.createServer((req, res) => {
  const icon = icons[req.url];
  if (icon) {
    fs.readFile(path.join(__dirname, 'public', icon.file), (err, data) => {
      if (err) {
        res.writeHead(404);
        return res.end();
      }
      res.writeHead(200, { 'Content-Type': icon.type });
      res.end(data);
    });
    return;
  }
  // ...other routes
}).listen(process.env.PORT);

This example matches req.url exactly. If you later append a query string for cache busting, strip it before the lookup, or the route will stop matching.

<link rel="icon" href="/favicon.ico" sizes="any">
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">

The ICO entry is a fallback for browsers that do not use SVG favicons. Marking it with sizes="any" is a common way to stop some browsers from preferring the ICO when an SVG is also declared. The Apple touch icon is a PNG because that context does not use SVG. If pages are rendered from templates, put these tags in the shared layout so every route includes them. Browser support for SVG favicons changes over time, so check current compatibility data for the browsers you target.

An .svg extension and a sharp preview do not prove that a file is vector artwork. Open the file in a text editor and look at which elements it contains. That is an observable property of the file. How well the logo reads at tab size is a visual judgment, and you can only settle it by viewing the result.

Element in the file | What it indicates | Effect on a favicon
path, circle, rect, polygon | True vector geometry | Scales cleanly; preferred
text | Live text that depends on fonts | Renders in a fallback font if the font is not on the device; convert to outlines
image with embedded or linked data | Raster content wrapped in SVG | Does not scale like vector art; linked images are not loaded in icon context
script, or references to external files | Behavior or external resources | Not executed or loaded when the SVG is used as an image

Also confirm that the root svg element has a viewBox and the standard SVG namespace declaration in its xmlns attribute. A browser may reject a standalone SVG served without the namespace, even if the same markup displays when it is pasted inline into HTML. A square viewBox avoids unexpected cropping or letterboxing. A full logo with fine detail or a wordmark often becomes illegible at icon size. Deciding whether to use a simplified mark instead is a design call, not a file check. An SVG can include a style block with a prefers-color-scheme media query to adapt to dark browser themes, but support for this varies.

Why the favicon is not showing: checks in order

  • 1. Request the icon path directly in the browser. If you get a 404 or an HTML page, the problem is the server route, the static folder location or middleware order, not the icon file.
  • 2. Check the response headers in the browser developer tools. The Content-Type should match the file format; text/html or text/plain points to a fallback route or a misconfigured handler.
  • 3. Confirm that authentication, session or redirect middleware does not intercept icon requests before they reach the static handler.
  • 4. View the page source and verify that the link tags exist and that their href paths match the files actually served.
  • 5. If a Content-Security-Policy header is set, for example through Helmet, make sure its img-src directive allows the icon's origin.
  • 6. Inspect the SVG as described above: namespace declaration present, viewBox present, no dependency on live text, embedded raster images or external files.
  • 7. Rule out caching. Browsers store favicons separately and keep them stubbornly, so a normal reload may not show a new icon. Test in a private window or rename the file or add a version parameter to the href.
  • 8. If the app uses a framework or bundler with its own static asset conventions, confirm where it expects icon files, because its rules apply instead of a hand-written setup.

Having a logo file does not mean you may use or modify it. Simplifying a mark, recoloring it or cropping it for a favicon counts as modification. If the logo belongs to a client or a third party, confirm usage and modification rights before deploying it.

What this guide cannot confirm

No project-specific evidence was provided. The framework, Node.js version, deployment setup, existing middleware and the actual logo file are unknown. The examples reflect common patterns, not a verified configuration. If the icon still fails after the checks above, the next steps are to inspect the real response headers and the SVG source, and to review how the specific framework or hosting layer handles static files.