Skip to main content

Routing Overview

The VENCOL Front Template uses React Router v6 for client-side routing, providing a seamless single-page application experience.

Router Configuration

Main Router Setup

The application uses BrowserRouter as the main router component in App.tsx:
App.tsx
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';

function App() {
  return (
    <Router>
      <ScrollToTop />
      <div className="min-h-screen bg-brand-dark">
        <BackgroundBlobs />
        <Navbar />
        <main className="flex-grow">
          <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/nosotros" element={<About />} />
            <Route path="/soluciones/:slug" element={<SolutionDetail />} />
            <Route path="/blog" element={<Blog />} />
            <Route path="/blog/:slug" element={<BlogDetail />} />
            <Route path="/contacto" element={<Contact />} />
            {/* Catch-all: WordPress pages by slug — must be last */}
            <Route path="/:slug" element={<PageDetail />} />
          </Routes>
        </main>
        <Footer />
      </div>
    </Router>
  );
}
The catch-all route /:slug must be defined last to prevent it from intercepting other routes. This route handles dynamic WordPress pages.

Route Definitions

Static Routes

Static routes map directly to specific page components:
RouteComponentDescription
/HomeLanding page with hero, about section, FAQs
/nosotrosAboutCompany information page
/blogBlogBlog listing page
/contactoContactContact form page

Dynamic Routes

Dynamic routes use URL parameters to load specific content:

Solution Details

<Route path="/soluciones/:slug" element={<SolutionDetail />} />
Usage:
  • /soluciones/refrigeracion-comercial
  • /soluciones/sistemas-hvac
Accessing the parameter:
SolutionDetail.tsx
import { useParams } from 'react-router-dom';

const { slug } = useParams<{ slug: string }>();
const solution = siteContent.solutions.items.find(s => s.slug === slug);

Blog Post Details

<Route path="/blog/:slug" element={<BlogDetail />} />
Usage:
  • /blog/post-title-here

WordPress Pages (Catch-all)

<Route path="/:slug" element={<PageDetail />} />
This route fetches pages from WordPress CMS by slug:
PageDetail.tsx
import { useParams } from 'react-router-dom';
import { fetchWPPageBySlug } from '../lib/wordpress';

const { slug } = useParams<{ slug: string }>();

useEffect(() => {
  fetchWPPageBySlug(slug)
    .then((wpPage) => {
      if (wpPage) setPage(wpPage);
      else setNotFound(true);
    })
    .catch(() => setNotFound(true));
}, [slug]);
The Navbar component uses NavLink for active route highlighting:
Navbar.tsx
import { NavLink as RouterNavLink, Link } from 'react-router-dom';

<RouterNavLink
  to={link.path}
  className={({ isActive }) => `
    px-4 py-1.5 rounded-full text-md font-medium transition-all duration-300
    ${isActive 
      ? 'text-brand-green bg-white/10 shadow-[0_0_10px_rgba(74,222,128,0.1)]' 
      : 'text-gray-300 hover:text-white hover:bg-white/5'}
  `}
>
  {link.label}
</RouterNavLink>
NavLink automatically receives an isActive boolean in its className function, enabling active state styling.

Solutions Dropdown

The navbar includes a dynamic dropdown for solutions:
Navbar.tsx
<div className="relative group">
  <button className="flex items-center px-4 py-1.5 rounded-full">
    Soluciones
    <ChevronDown className="w-4 h-4 ml-1 group-hover:rotate-180 transition-transform" />
  </button>
  
  <div className="dropdown-menu">
    {solutionsData.map((solution) => (
      <Link 
        key={solution.id}
        to={`/soluciones/${solution.slug}`}
        className="block px-4 py-3 hover:text-white hover:bg-white/10"
      >
        <div className="font-semibold">{solution.title}</div>
        <div className="text-xs text-glass-muted">{solution.description}</div>
      </Link>
    ))}
  </div>
</div>

Programmatic Navigation

Use the Link component for navigation:
import { Link } from 'react-router-dom';

// Basic link
<Link to="/contacto" className="button">
  Contact Us
</Link>

// Dynamic link
<Link to={`/blog/${post.slug}`}>
  Read More
</Link>

// Link with state
<Link to="/contact" state={{ from: 'blog' }}>
  Contact
</Link>

Scroll Behavior

Scroll to Top on Route Change

The application implements automatic scroll-to-top on navigation:
App.tsx
function ScrollToTop() {
  const { pathname } = useLocation();
  
  useEffect(() => {
    window.scrollTo(0, 0);
  }, [pathname]);
  
  return null;
}
This component is placed inside the Router to access routing context:
<Router>
  <ScrollToTop />
  {/* Rest of app */}
</Router>

Mobile Navigation

Mobile Menu State

The navbar manages mobile menu state with automatic closing on route change:
Navbar.tsx
const [isOpen, setIsOpen] = useState(false);
const [mobileSolutionsOpen, setMobileSolutionsOpen] = useState(false);
const location = useLocation();

// Close mobile menu on route change
useEffect(() => {
  setIsOpen(false);
  setMobileSolutionsOpen(false);
}, [location]);

Mobile Dropdown

The mobile menu includes an expandable solutions section:
Navbar.tsx
<button 
  onClick={() => setMobileSolutionsOpen(!mobileSolutionsOpen)}
  className="w-full flex items-center justify-between"
>
  Soluciones
  <ChevronDown className={`transition-transform ${
    mobileSolutionsOpen ? 'rotate-180' : ''
  }`} />
</button>

<div className={`overflow-hidden transition-all ${
  mobileSolutionsOpen ? 'max-h-[500px] opacity-100' : 'max-h-0 opacity-0'
}`}>
  {solutionsData.map((solution) => (
    <Link to={`/soluciones/${solution.slug}`}>
      {solution.menuTitle}
    </Link>
  ))}
</div>

Loading States

Dynamic pages implement loading states during data fetching:
PageDetail.tsx
if (loading) {
  return (
    <div className="pt-48 pb-20">
      <div className="animate-pulse space-y-8">
        <div className="h-8 bg-white/10 rounded w-1/3" />
        <div className="h-[40vh] bg-white/5 rounded-2xl" />
      </div>
    </div>
  );
}

404 Handling

WordPress Page Not Found

The PageDetail component handles missing pages:
PageDetail.tsx
if (notFound || !page) {
  return (
    <div className="min-h-screen flex items-center justify-center">
      <SEO title="Página no encontrada" />
      <div className="text-center">
        <h2 className="text-3xl font-bold mb-4">Página no encontrada</h2>
        <p className="text-glass-muted mb-6">
          La página que buscas no existe o ha sido movida.
        </p>
        <Link to="/" className="text-brand-green hover:underline">
          <ArrowLeft className="w-4 h-4" />
          Volver al Inicio
        </Link>
      </div>
    </div>
  );
}
Extract route parameters using useParams hook:
const { slug } = useParams<{ slug: string }>();
Always provide TypeScript types for parameters.
Place catch-all routes last in the Routes configuration to prevent them from intercepting more specific routes.
Use NavLink instead of Link when you need to style the active route:
<NavLink 
  to="/blog"
  className={({ isActive }) => 
    isActive ? 'active-class' : 'inactive-class'
  }
/>

Architecture

Learn about the overall application structure

WordPress Integration

Understand how dynamic pages are loaded from WordPress

Build docs developers (and LLMs) love