The 3 annoying things solved for vanilla JS/TS SPAs: routing, navigation & SEO
📚 Documentation | 💻 GitHub
🌍 Language / Langue:
The 3 annoying things solved for your vanilla JS/TS SPAs: routing, navigation, SEO, multi-language
- File-based routing - Create
pages/About.ts→ Route/aboutautomatically - Automatic navigation - Generate menus without imposed CSS
- SEO helpers - Meta tags in one line
npm install love-on-the-routeimport {
createRouter,
autoDiscoverPagesIntelligent,
generateRoutes,
} from "love-on-the-route";
const pages = import.meta.glob("./pages/**/*.ts", { eager: true }) as Record<
string,
{
default: () => HTMLElement;
}
>;
const { routes } = autoDiscoverPagesIntelligent(pages);
const router = createRouter(document.body);
generateRoutes(router, routes);
router.render();That's it! Your pages in pages/ become navigable routes.
src/
├── pages/
│ ├── Home.ts # → Route "/"
│ ├── About.ts # → Route "/about"
│ └── Contact.ts # → Route "/contact"
└── main.ts
// pages/Services.ts
export default function Services(): HTMLElement {
const container = document.createElement("div");
container.innerHTML = `
<h1>Our Services</h1>
<p>List of our services...</p>
`;
return container;
}Automatically:
- Route
/servicescreated - Ready for navigation and SEO (if you add them)
import {
createRouter,
autoDiscoverPagesIntelligent,
generateRoutes,
} from "love-on-the-route";
const pages = import.meta.glob("./pages/**/*.ts", { eager: true }) as Record<
string,
{
default: () => HTMLElement;
}
>;
const { routes } = autoDiscoverPagesIntelligent(pages);
const router = createRouter(document.body);
generateRoutes(router, routes);
router.render();âś… What you get: File-based routing
❌ What you don't get: Navigation, SEO (you add them if needed)
import {
createRouter,
autoDiscoverPagesIntelligent,
generateRoutes,
LoveNav,
} from "love-on-the-route";
const pages = import.meta.glob("./pages/**/*.ts", { eager: true }) as Record<
string,
{
default: () => HTMLElement;
title?: string;
description?: string;
keywords?: string[];
}
>;
const { routes } = autoDiscoverPagesIntelligent(pages);
const router = createRouter(document.body);
const nav = new LoveNav(routes.map((r) => ({ path: r.path, title: r.title })));
document.body.insertBefore(nav.render(), document.querySelector("#content"));
generateRoutes(router, routes);
router.render();âś… What you get: File-based routing + Automatic navigation
❌ What you don't get: SEO (you add it if needed)
import {
createRouter,
autoDiscoverPagesIntelligent,
generateRoutes,
updateSEO,
} from "love-on-the-route";
const pages = import.meta.glob("./pages/**/*.ts", { eager: true }) as Record<
string,
{
default: () => HTMLElement;
title?: string;
description?: string;
keywords?: string[];
}
>;
const { routes } = autoDiscoverPagesIntelligent(pages);
const routesWithSEO = routes.map((route) => ({
...route,
component: () => {
const pageModule = Object.values(pages).find(
(module) => module.default.name === route.component.name
);
updateSEO({
title: pageModule?.title || route.title,
description: pageModule?.description,
url: window.location.href,
keywords: pageModule?.keywords,
});
return route.component();
},
}));
const router = createRouter(document.body);
generateRoutes(router, routesWithSEO);
router.render();Simpler alternative - Add SEO directly to your pages:
// pages/About.ts
import { updateSEO } from "love-on-the-route";
export const title = "About";
export const description = "Learn more about our company";
export const keywords = ["company", "history", "team"];
export default function About(): HTMLElement {
updateSEO({
title,
description,
url: window.location.href,
keywords,
});
const container = document.createElement("div");
container.innerHTML = `
<h1>${title}</h1>
<p>Discover our story...</p>
`;
return container;
}âś… What you get: File-based routing + Manual SEO control
âś… What you control: Exactly when and how SEO is updated
pages/
├── en/
│ ├── Home.ts # → Route "/en"
│ └── About.ts # → Route "/en/about"
└── fr/
├── Home.ts # → Route "/fr"
└── About.ts # → Route "/fr/about"
const { routes, languages, isMultilingual } =
autoDiscoverPagesIntelligent(pages);
if (isMultilingual) {
console.log("Detected languages:", languages); // ["en", "fr"]
// Automatic language selector configuration
}import {
createRouter,
autoDiscoverPagesIntelligent,
generateRoutes,
updateSEO,
LoveNav,
LangSelector,
filterRoutesByCurrentLanguage,
} from "love-on-the-route";
const pages = import.meta.glob("./pages/**/*.ts", { eager: true }) as Record<
string,
{
default: () => HTMLElement;
title?: string;
description?: string;
keywords?: string[];
}
>;
const { routes, languages, isMultilingual } =
autoDiscoverPagesIntelligent(pages);
const routesWithSEO = routes.map((route) => ({
...route,
component: () => {
const pageModule = Object.values(pages).find(
(module) => module.default.name === route.component.name
);
updateSEO({
title: pageModule?.title || route.title,
description: pageModule?.description,
url: window.location.href,
keywords: pageModule?.keywords,
});
return route.component();
},
}));
const router = createRouter(document.body);
generateRoutes(router, routesWithSEO);
let nav: LoveNav;
const updateNavigation = () => {
const currentLanguageRoutes = filterRoutesByCurrentLanguage(
routes,
languages
);
const navRoutes = currentLanguageRoutes.map((r) => ({
path: r.path,
title: r.title,
}));
if (nav) {
nav.updateRoutes(navRoutes);
} else {
nav = new LoveNav(navRoutes);
document.body.insertBefore(
nav.render(),
document.querySelector("#content")
);
}
};
updateNavigation();
if (isMultilingual && languages) {
const langOptions = languages.map((lang) => ({
code: lang,
label: lang === "en" ? "English" : lang === "fr" ? "Français" : lang,
flag: lang === "en" ? "🇬🇧" : lang === "fr" ? "🇫🇷" : undefined,
}));
const langSelector = new LangSelector(langOptions, {
containerClass: "language-selector",
linkClass: "lang-link",
activeClass: "active",
showFlags: true,
});
document.body.insertBefore(
langSelector.render(),
document.querySelector("#content")
);
window.addEventListener("routeChanged", updateNavigation);
}
router.render();
if (isMultilingual && window.location.pathname === "/") {
const defaultLang = languages?.[0] || "en";
router.navigate(`/${defaultLang}`);
}Alternative with SVG/images :
if (isMultilingual && languages) {
const langOptions = languages.map((lang) => ({
code: lang,
label: lang === "en" ? "English" : lang === "fr" ? "Français" : lang,
flag:
lang === "en"
? `<img src="/flags/en.svg" alt="EN" width="16" height="12" style="margin-right: 4px;">`
: lang === "fr"
? `<svg width="16" height="12" style="margin-right: 4px;"><rect width="16" height="4" fill="#0055A4"/><rect y="4" width="16" height="4" fill="white"/><rect y="8" width="16" height="4" fill="#EF4135"/></svg>`
: undefined,
}));
const langSelector = new LangSelector(langOptions, {
containerClass: "language-selector",
linkClass: "lang-link",
activeClass: "active",
showFlags: true,
});
document.body.insertBefore(
langSelector.render(),
document.querySelector("#content")
);
window.addEventListener("routeChanged", updateNavigation);
}New: LoveNav now supports native logo integration with multiple configuration options.
import { LoveNav } from "love-on-the-route";
const routes = [
{ path: "/", title: "Home" },
{ path: "/about", title: "About" },
{ path: "/contact", title: "Contact" },
];
const nav = new LoveNav(routes, {
containerClass: "main-navigation",
linkClass: "nav-link",
activeClass: "active",
logo: {
html: `<img src="/logo.svg" alt="My Site" width="120" height="40">`,
href: "/",
replacesHome: true, // Logo replaces the "Home" link
linkClass: "logo-link",
containerClass: "logo-container",
},
});
document.body.appendChild(nav.render());const nav = new LoveNav(routes, {
containerClass: "main-navigation",
linkClass: "nav-link",
activeClass: "active",
separateLogoFromNav: true, // Logo rendered separately
logo: {
html: `<img src="/logo.svg" alt="My Site" width="120" height="40">`,
href: "/",
linkClass: "logo-link",
containerClass: "logo-container",
},
});
const { logo, nav: navElement } = nav.renderSeparate();
// Full control over placement
const header = document.createElement("header");
header.style.cssText =
"display: flex; justify-content: space-between; align-items: center; padding: 1rem;";
if (logo) header.appendChild(logo);
header.appendChild(navElement);
document.body.appendChild(header);const nav = new LoveNav(routes, {
logo: {
html: `<img src="/logo.svg" alt="My Site" width="80" height="30">`,
href: "/",
replacesHome: false, // Logo AND Home link (default)
linkClass: "logo-link",
},
});New: Access current language for dynamic component localization.
import {
getCurrentLanguage,
detectCurrentLanguage,
watchLanguageChanges,
} from "love-on-the-route";
// Simple current language detection
const currentLang = getCurrentLanguage();
console.log(currentLang); // 'fr', 'en', etc.
// Language detection with supported languages
const lang = detectCurrentLanguage(["en", "fr", "es"]);
console.log(lang); // With guaranteed fallback
// Watch for language changes (reactive components)
const unsubscribe = watchLanguageChanges((newLang) => {
console.log("Language changed to:", newLang);
// Update your component...
});
// Stop watching
unsubscribe();Example: Localized Component
import { getCurrentLanguage, watchLanguageChanges } from "love-on-the-route";
class LocalizedComponent {
private currentLang: string;
private translations = {
en: { welcome: "Welcome", button: "Click me" },
fr: { welcome: "Bienvenue", button: "Cliquez-moi" },
es: { welcome: "Bienvenido", button: "Haz clic" },
};
constructor() {
this.currentLang = getCurrentLanguage();
this.render();
// Auto-update on language changes
watchLanguageChanges((newLang) => {
this.currentLang = newLang;
this.render();
});
}
render() {
const t = this.translations[this.currentLang] || this.translations.en;
const container = document.createElement("div");
container.innerHTML = `
<h1>${t.welcome}</h1>
<button>${t.button}</button>
`;
return container;
}
}const { routes, languages, isMultilingual } =
autoDiscoverPagesIntelligent(pages);
if (isMultilingual) {
const getHomePath = (): string => {
const currentPath = window.location.pathname;
const currentLang = languages.find((lang) =>
currentPath.startsWith(`/${lang}`)
);
return `/${currentLang || languages[0]}`;
};
let nav: LoveNav;
const updateNavigation = () => {
const currentLanguageRoutes = filterRoutesByCurrentLanguage(
routes,
languages
);
const navRoutes = currentLanguageRoutes.map((r) => ({
path: r.path,
title: r.title,
}));
if (nav) {
nav.updateRoutes(navRoutes);
// Update logo href for current language
nav.updateLogo({
html: `<img src="/logo.svg" alt="My Site" width="120" height="40">`,
href: getHomePath(),
replacesHome: true,
linkClass: "logo-link",
});
} else {
nav = new LoveNav(navRoutes, {
logo: {
html: `<img src="/logo.svg" alt="My Site" width="120" height="40">`,
href: getHomePath(),
replacesHome: true,
linkClass: "logo-link",
},
});
document.body.appendChild(nav.render());
}
};
updateNavigation();
window.addEventListener("routeChanged", updateNavigation);
}interface LogoConfig {
html: string; // Logo HTML (img, svg, text...)
href?: string; // Destination URL (default: "/")
replacesHome?: boolean; // If true, replaces "Home" links (default: false)
containerClass?: string; // Container CSS class
linkClass?: string; // Link CSS class
}Available methods:
nav.render()- Normal rendering (logo integrated if configured)nav.renderSeparate()- Separate rendering{ logo?, nav }nav.updateLogo(logoConfig)- Dynamic logo update
If you're managing your logo separately from LoveNav, you can manually filter home routes:
const updateNavigation = () => {
const currentLanguageRoutes = filterRoutesByCurrentLanguage(
routes,
languages
);
// Manually filter out home routes
const navRoutes = currentLanguageRoutes
.filter((r) => {
const path = r.path.toLowerCase();
const title = r.title.toLowerCase();
// Exclude home routes
const isHomeRoute =
path === "/" ||
path.match(/^\/[a-z]{2}$/) || // /en, /fr, etc.
title === "home" ||
title === "accueil";
return !isHomeRoute;
})
.map((r) => ({ path: r.path, title: r.title }));
// Your separate logo management
logoLink.href = getHomePath();
if (nav) {
nav.updateRoutes(navRoutes);
} else {
nav = new LoveNav(navRoutes, {
containerClass: "main-navigation",
linkClass: "nav-link",
activeClass: "active",
});
navContainer.appendChild(nav.render());
}
};This approach is useful when:
- You have a complex logo setup
- You want full control over logo positioning
- You're using a separate navigation container
Access and react to language changes in your components with these 3 simple hooks:
import { getCurrentLanguage } from "love-on-the-route";
const lang = getCurrentLanguage();
console.log(lang); // 'fr', 'en', etc.import { detectCurrentLanguage } from "love-on-the-route";
const lang = detectCurrentLanguage(["en", "fr", "es"]);
console.log(lang); // With guaranteed fallbackimport { watchLanguageChanges } from "love-on-the-route";
// Listen to changes
const unsubscribe = watchLanguageChanges((newLang) => {
console.log("Language changed to:", newLang);
// Update your component...
});
// Stop listening
unsubscribe();- Ultra simple - 4 lines to start
- Zero imposed CSS - Your design, your classes
- File-based routing - Like Next.js but vanilla
- SEO ready - Automatic meta tags
- TypeScript - Full support
- Multilingual - Automatic detection
- Flexible names - No constraints on file names
Les 3 trucs chiants en moins pour vos SPAs vanilla JS/TS : routing, navigation, SEO, multi langue
- Routing file-based - Créez
pages/About.ts→ Route/aboutautomatique - Navigation automatique - Génération des menus sans CSS imposé
- SEO helpers - Meta tags en une ligne
npm install love-on-the-routeimport {
createRouter,
autoDiscoverPagesIntelligent,
generateRoutes,
} from "love-on-the-route";
const pages = import.meta.glob("./pages/**/*.ts", { eager: true }) as Record<
string,
{
default: () => HTMLElement;
}
>;
const { routes } = autoDiscoverPagesIntelligent(pages);
const router = createRouter(document.body);
generateRoutes(router, routes);
router.render();C'est tout ! Vos pages dans pages/ deviennent des routes navigables.
src/
├── pages/
│ ├── Home.ts # → Route "/"
│ ├── About.ts # → Route "/about"
│ └── Contact.ts # → Route "/contact"
└── main.ts
// pages/Services.ts
export default function Services(): HTMLElement {
const container = document.createElement("div");
container.innerHTML = `
<h1>Nos Services</h1>
<p>Liste de nos services...</p>
`;
return container;
}Automatiquement :
- Route
/servicescréée - Prêt pour navigation et SEO (si vous les ajoutez)
import {
createRouter,
autoDiscoverPagesIntelligent,
generateRoutes,
} from "love-on-the-route";
const pages = import.meta.glob("./pages/**/*.ts", { eager: true }) as Record<
string,
{
default: () => HTMLElement;
}
>;
const { routes } = autoDiscoverPagesIntelligent(pages);
const router = createRouter(document.body);
generateRoutes(router, routes);
router.render();âś… Ce que vous obtenez : Routing file-based
❌ Ce que vous n'obtenez pas : Navigation, SEO (vous les ajoutez si besoin)
import {
createRouter,
autoDiscoverPagesIntelligent,
generateRoutes,
LoveNav,
} from "love-on-the-route";
const pages = import.meta.glob("./pages/**/*.ts", { eager: true }) as Record<
string,
{
default: () => HTMLElement;
title?: string;
description?: string;
keywords?: string[];
}
>;
const { routes } = autoDiscoverPagesIntelligent(pages);
const router = createRouter(document.body);
const nav = new LoveNav(routes.map((r) => ({ path: r.path, title: r.title })));
document.body.insertBefore(nav.render(), document.querySelector("#content"));
generateRoutes(router, routes);
router.render();âś… Ce que vous obtenez : Routing file-based + Navigation automatique
❌ Ce que vous n'obtenez pas : SEO (vous l'ajoutez si besoin)
import {
createRouter,
autoDiscoverPagesIntelligent,
generateRoutes,
updateSEO,
} from "love-on-the-route";
const pages = import.meta.glob("./pages/**/*.ts", { eager: true }) as Record<
string,
{
default: () => HTMLElement;
title?: string;
description?: string;
keywords?: string[];
}
>;
const { routes } = autoDiscoverPagesIntelligent(pages);
const routesWithSEO = routes.map((route) => ({
...route,
component: () => {
const pageModule = Object.values(pages).find(
(module) => module.default.name === route.component.name
);
updateSEO({
title: pageModule?.title || route.title,
description: pageModule?.description,
url: window.location.href,
keywords: pageModule?.keywords,
});
return route.component();
},
}));
const router = createRouter(document.body);
generateRoutes(router, routesWithSEO);
router.render();Alternative plus simple - Ajouter SEO directement dans vos pages :
// pages/About.ts
import { updateSEO } from "love-on-the-route";
export const title = "Ă€ propos";
export const description = "En savoir plus sur notre entreprise";
export const keywords = ["entreprise", "histoire", "équipe"];
export default function About(): HTMLElement {
updateSEO({
title,
description,
url: window.location.href,
keywords,
});
const container = document.createElement("div");
container.innerHTML = `
<h1>${title}</h1>
<p>Découvrez notre histoire...</p>
`;
return container;
}âś… Ce que vous obtenez : Routing file-based + Manual SEO control
âś… Ce que vous contrĂ´lez : Exactement quand et comment le SEO est mis Ă jour
pages/
├── en/
│ ├── Home.ts # → Route "/en"
│ └── About.ts # → Route "/en/about"
└── fr/
├── Home.ts # → Route "/fr"
└── About.ts # → Route "/fr/about"
const { routes, languages, isMultilingual } =
autoDiscoverPagesIntelligent(pages);
if (isMultilingual) {
console.log("Langues détectées :", languages); // ["en", "fr"]
// Configuration automatique du sélecteur de langue
}import {
createRouter,
autoDiscoverPagesIntelligent,
generateRoutes,
updateSEO,
LoveNav,
LangSelector,
filterRoutesByCurrentLanguage,
} from "love-on-the-route";
const pages = import.meta.glob("./pages/**/*.ts", { eager: true }) as Record<
string,
{
default: () => HTMLElement;
title?: string;
description?: string;
keywords?: string[];
}
>;
const { routes, languages, isMultilingual } =
autoDiscoverPagesIntelligent(pages);
const routesWithSEO = routes.map((route) => ({
...route,
component: () => {
const pageModule = Object.values(pages).find(
(module) => module.default.name === route.component.name
);
updateSEO({
title: pageModule?.title || route.title,
description: pageModule?.description,
url: window.location.href,
keywords: pageModule?.keywords,
});
return route.component();
},
}));
const router = createRouter(document.body);
generateRoutes(router, routesWithSEO);
let nav: LoveNav;
const updateNavigation = () => {
const currentLanguageRoutes = filterRoutesByCurrentLanguage(
routes,
languages
);
const navRoutes = currentLanguageRoutes.map((r) => ({
path: r.path,
title: r.title,
}));
if (nav) {
nav.updateRoutes(navRoutes);
} else {
nav = new LoveNav(navRoutes);
document.body.insertBefore(
nav.render(),
document.querySelector("#content")
);
}
};
updateNavigation();
if (isMultilingual && languages) {
const langOptions = languages.map((lang) => ({
code: lang,
label: lang === "en" ? "English" : lang === "fr" ? "Français" : lang,
flag: lang === "en" ? "🇬🇧" : lang === "fr" ? "🇫🇷" : undefined,
}));
const langSelector = new LangSelector(langOptions, {
containerClass: "language-selector",
linkClass: "lang-link",
activeClass: "active",
showFlags: true,
});
document.body.insertBefore(
langSelector.render(),
document.querySelector("#content")
);
window.addEventListener("routeChanged", updateNavigation);
}
router.render();
if (isMultilingual && window.location.pathname === "/") {
const defaultLang = languages?.[0] || "en";
router.navigate(`/${defaultLang}`);
}Alternative avec SVG/images :
if (isMultilingual && languages) {
const langOptions = languages.map((lang) => ({
code: lang,
label: lang === "en" ? "English" : lang === "fr" ? "Français" : lang,
flag:
lang === "en"
? `<img src="/flags/en.svg" alt="EN" width="16" height="12" style="margin-right: 4px;">`
: lang === "fr"
? `<svg width="16" height="12" style="margin-right: 4px;"><rect width="16" height="4" fill="#0055A4"/><rect y="4" width="16" height="4" fill="white"/><rect y="8" width="16" height="4" fill="#EF4135"/></svg>`
: undefined,
}));
const langSelector = new LangSelector(langOptions, {
containerClass: "language-selector",
linkClass: "lang-link",
activeClass: "active",
showFlags: true,
});
document.body.insertBefore(
langSelector.render(),
document.querySelector("#content")
);
window.addEventListener("routeChanged", updateNavigation);
}Nouveau : LoveNav supporte maintenant l'intégration native du logo avec plusieurs options de configuration.
import { LoveNav } from "love-on-the-route";
const routes = [
{ path: "/", title: "Home" },
{ path: "/about", title: "About" },
{ path: "/contact", title: "Contact" },
];
const nav = new LoveNav(routes, {
containerClass: "main-navigation",
linkClass: "nav-link",
activeClass: "active",
logo: {
html: `<img src="/logo.svg" alt="Mon Site" width="120" height="40">`,
href: "/",
replacesHome: true, // Le logo remplace le lien "Home"
linkClass: "logo-link",
containerClass: "logo-container",
},
});
document.body.appendChild(nav.render());const nav = new LoveNav(routes, {
containerClass: "main-navigation",
linkClass: "nav-link",
activeClass: "active",
separateLogoFromNav: true, // Logo rendu séparément
logo: {
html: `<img src="/logo.svg" alt="Mon Site" width="120" height="40">`,
href: "/",
linkClass: "logo-link",
containerClass: "logo-container",
},
});
const { logo, nav: navElement } = nav.renderSeparate();
// ContrĂ´le total sur le placement
const header = document.createElement("header");
header.style.cssText =
"display: flex; justify-content: space-between; align-items: center; padding: 1rem;";
if (logo) header.appendChild(logo);
header.appendChild(navElement);
document.body.appendChild(header);const nav = new LoveNav(routes, {
logo: {
html: `<img src="/logo.svg" alt="Mon Site" width="80" height="30">`,
href: "/",
replacesHome: false, // Logo ET lien Home (défaut)
linkClass: "logo-link",
},
});Nouveau : Accédez à la langue courante pour localiser dynamiquement vos composants.
import {
getCurrentLanguage,
detectCurrentLanguage,
watchLanguageChanges,
} from "love-on-the-route";
// Détection simple de la langue courante
const currentLang = getCurrentLanguage();
console.log(currentLang); // 'fr', 'en', etc.
// Détection avec langues supportées
const lang = detectCurrentLanguage(["en", "fr", "es"]);
console.log(lang); // Avec fallback garanti
// Surveiller les changements de langue (composants réactifs)
const unsubscribe = watchLanguageChanges((newLang) => {
console.log("Langue changée vers :", newLang);
// Mettre Ă jour votre composant...
});
// ArrĂŞter la surveillance
unsubscribe();Exemple : Composant Localisé
import { getCurrentLanguage, watchLanguageChanges } from "love-on-the-route";
class ComposantLocalise {
private currentLang: string;
private translations = {
en: { welcome: "Welcome", button: "Click me" },
fr: { welcome: "Bienvenue", button: "Cliquez-moi" },
es: { welcome: "Bienvenido", button: "Haz clic" },
};
constructor() {
this.currentLang = getCurrentLanguage();
this.render();
// Mise Ă jour automatique lors des changements de langue
watchLanguageChanges((newLang) => {
this.currentLang = newLang;
this.render();
});
}
render() {
const t = this.translations[this.currentLang] || this.translations.fr;
const container = document.createElement("div");
container.innerHTML = `
<h1>${t.welcome}</h1>
<button>${t.button}</button>
`;
return container;
}
}const { routes, languages, isMultilingual } =
autoDiscoverPagesIntelligent(pages);
if (isMultilingual) {
const getHomePath = (): string => {
const currentPath = window.location.pathname;
const currentLang = languages.find((lang) =>
currentPath.startsWith(`/${lang}`)
);
return `/${currentLang || languages[0]}`;
};
let nav: LoveNav;
const updateNavigation = () => {
const currentLanguageRoutes = filterRoutesByCurrentLanguage(
routes,
languages
);
const navRoutes = currentLanguageRoutes.map((r) => ({
path: r.path,
title: r.title,
}));
if (nav) {
nav.updateRoutes(navRoutes);
// Mettre Ă jour le href du logo pour la langue courante
nav.updateLogo({
html: `<img src="/logo.svg" alt="Mon Site" width="120" height="40">`,
href: getHomePath(),
replacesHome: true,
linkClass: "logo-link",
});
} else {
nav = new LoveNav(navRoutes, {
logo: {
html: `<img src="/logo.svg" alt="Mon Site" width="120" height="40">`,
href: getHomePath(),
replacesHome: true,
linkClass: "logo-link",
},
});
document.body.appendChild(nav.render());
}
};
updateNavigation();
window.addEventListener("routeChanged", updateNavigation);
}interface LogoConfig {
html: string; // HTML du logo (img, svg, text...)
href?: string; // URL de destination (défaut: "/")
replacesHome?: boolean; // Si true, remplace les liens "Home"/"Accueil" (défaut: false)
containerClass?: string; // Classe CSS du conteneur
linkClass?: string; // Classe CSS du lien
}Méthodes disponibles :
nav.render()- Rendu normal (logo intégré si configuré)nav.renderSeparate()- Rendu séparé{ logo?, nav }nav.updateLogo(logoConfig)- Mise à jour dynamique du logo
Si vous gérez votre logo séparément de LoveNav, vous pouvez filtrer manuellement les routes home :
const updateNavigation = () => {
const currentLanguageRoutes = filterRoutesByCurrentLanguage(
routes,
languages
);
// Filtrer manuellement les routes home
const navRoutes = currentLanguageRoutes
.filter((r) => {
const path = r.path.toLowerCase();
const title = r.title.toLowerCase();
// Exclure les routes home
const isHomeRoute =
path === "/" ||
path.match(/^\/[a-z]{2}$/) || // /en, /fr, etc.
title === "home" ||
title === "accueil";
return !isHomeRoute;
})
.map((r) => ({ path: r.path, title: r.title }));
// Votre gestion séparée du logo
logoLink.href = getHomePath();
if (nav) {
nav.updateRoutes(navRoutes);
} else {
nav = new LoveNav(navRoutes, {
containerClass: "main-navigation",
linkClass: "nav-link",
activeClass: "active",
});
navContainer.appendChild(nav.render());
}
};Cette approche est utile quand :
- Vous avez une configuration de logo complexe
- Vous voulez un contrĂ´le total sur le positionnement du logo
- Vous utilisez un conteneur de navigation séparé
Accédez et réagissez aux changements de langue dans vos composants avec ces 3 hooks simples :
import { getCurrentLanguage } from "love-on-the-route";
const lang = getCurrentLanguage();
console.log(lang); // 'fr', 'en', etc.import { detectCurrentLanguage } from "love-on-the-route";
const lang = detectCurrentLanguage(["en", "fr", "es"]);
console.log(lang); // Avec fallback garantiimport { watchLanguageChanges } from "love-on-the-route";
// Écouter les changements
const unsubscribe = watchLanguageChanges((newLang) => {
console.log("Langue changée vers :", newLang);
// Mettre Ă jour votre composant...
});
// Arrêter l'écoute
unsubscribe();- Ultra simple - 4 lignes pour démarrer
- Zero CSS imposé - Votre design, vos classes
- File-based routing - Comme Next.js mais vanilla
- SEO ready - Meta tags automatiques
- TypeScript - Support complet
- Multilingue - Détection automatique
- Noms flexibles - Pas de contraintes sur les noms de fichiers
Love On The Route - The 3 annoying things solved! / Les 3 trucs chiants en moins !
From connection lines to routing lines 🎶 | Inspired by Kormak