Remix Favicon Generator
Remix (and React Router framework mode, which is the same route API under renamed packages) does not pick up icons from a special filename. You put files in public/ and return them from the root route's links() export so <Links /> can render them into the document head.
public/ plus a root links() export
Anything under public/ is copied to the client build root, so public/favicon.ico is served at /favicon.ico. That alone is not enough for a complete set: browsers still need link tags for PNG sizes, the apple-touch-icon, and a manifest, and those tags come from route modules rather than from a static HTML file.
The root route is the right place. Its links() return value is aggregated with every other matched route and rendered by <Links /> in the document shell, so a declaration there covers every page including error boundaries that still render inside the root layout.
Remix v2 vs React Router framework mode
React Router (current)
Start hereFramework mode, new apps
- Packages are react-router and @react-router/*
- Same links() / <Links /> API as Remix v2
- public/ still holds stable static filenames
- Root route file is usually app/root.tsx
Remix v2
Existing apps
- Packages are @remix-run/*
- Upgrade path is mostly import renames once future flags are on
- app/root.tsx and public/ layout are the same idea
- Icon setup does not change when you rename packages
Where the files go
- my-remix-app/
- public/copied to the client build root
- favicon.icoadd
- favicon-32x32.pngadd
- apple-touch-icon.pngadd180x180
- icon-192.pngadd
- icon-512.pngadd
- site.webmanifestadd
- app/
- root.tsxlinks() lives here
- routes/
Same layout whether the package.json still says remix or already says react-router.
app/root.tsx
import { Links, Meta, Outlet, Scripts } from "react-router";
// Remix v2: import those from "@remix-run/react" instead.
export function links() {
return [
{ rel: "icon", href: "/favicon.ico", sizes: "any" },
{
rel: "icon",
type: "image/png",
sizes: "32x32",
href: "/favicon-32x32.png",
},
{ rel: "apple-touch-icon", href: "/apple-touch-icon.png" },
{ rel: "manifest", href: "/site.webmanifest" },
];
}
export default function App() {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body>
<Outlet />
<Scripts />
</body>
</html>
);
}Paths are root-absolute against public/. Dropping <Links /> from the shell silently drops every route's link declarations, including the favicon.
Design for the smallest size first





Remix favicon FAQ
Can I put the tags in a raw HTML file instead?
Framework mode apps do not ship a hand-edited index.html the way a Vite SPA does. The document shell is the root route component. If the tags are missing from links() or <Links /> is missing from the shell, they will not appear in the served HTML.
Should icons go through the Vite import graph?
No. Importing an image from app/ fingerprints the filename, which breaks every fixed href and every browser request for /favicon.ico. Keep icons in public/ so the path stays stable.
I upgraded from Remix to React Router and the icons vanished.
Check that root.tsx still exports links() and still renders <Links /> after the import rename. The public/ files do not move. A missing <Links /> in the new root shell is the usual cause.