# Using Dynamic Routes in Lovable Without Errors

- Tool: Lovable
- Difficulty: Intermediate
- Fix time: ~15 min
- Compatibility: All Lovable projects using React Router
- Last updated: March 2026

## TL;DR

Dynamic routes in Lovable use React Router's :param syntax (like /users/:id) combined with the useParams hook to read the URL parameter. Common failures include missing the Route definition in App.tsx, not handling loading and not-found states for invalid IDs, and forgetting SPA fallback configuration on deployed hosting platforms.

## Why dynamic routes fail or show blank pages in Lovable

Dynamic routes let you use URL parameters (like /products/42 or /blog/my-first-post) to load different content from the same page component. Lovable projects use React Router with BrowserRouter, which handles dynamic segments via the :param syntax.

The most common failure is a missing Route definition. Your page component exists and uses useParams to read the ID, but App.tsx does not have a Route entry with the :param segment. React Router does not know this path exists, so it falls through to a catch-all or shows a blank page.

Another frequent issue is not handling invalid parameters. When someone visits /users/abc and your code expects a numeric ID, the Supabase query returns no results but the component tries to render the data anyway, causing 'cannot read property of undefined' errors. Always validate the parameter and show a 404 state when the data does not exist.

Finally, dynamic routes that work in Lovable's preview may break on deployed hosts (Vercel, Netlify) because the server does not know about /users/42 — it only knows about /index.html. This is the SPA routing fallback issue, covered in detail on our SPA fallback configuration page.

- Missing Route definition in App.tsx — the path with :param is not registered in the router
- useParams returns undefined — the component is rendered outside a Route or the param name does not match
- No loading state — the component renders before the data fetch completes, causing undefined errors
- Invalid parameter not handled — visiting /users/abc when only numeric IDs exist produces a broken page
- SPA fallback not configured on the hosting platform — direct URL access returns a server 404

## Error messages you might see

**TypeError: Cannot read properties of undefined (reading 'name')**

The component is trying to access a property on data that has not loaded yet, or the parameter does not match any record. Add a loading check and a not-found fallback.

**No routes matched location "/users/42"**

React Router cannot find a Route definition that matches this URL. Add a Route with path='/users/:id' in your App.tsx file.

**useParams() may only be used in the context of a <Router> component**

The component using useParams is rendered outside the BrowserRouter. Make sure all page components are inside the Routes component within BrowserRouter.

## Before you start

- A Lovable project with React Router configured (this is the default for all Lovable projects)
- A page component that should display data based on a URL parameter
- A data source (Supabase table or API) that the component will query using the URL parameter

## How to fix it

### 1. Add the dynamic Route definition in App.tsx

*React Router needs a Route entry with the :param syntax to know which component to render for dynamic URLs*

Open src/App.tsx in Dev Mode or prompt Lovable to edit it. Add a Route element with a path that includes the dynamic segment. The colon prefix (:id) tells React Router this is a variable. The component assigned to this route can then read the value using useParams().

Before:

```
<Routes>
  <Route path="/" element={<Index />} />
  <Route path="/users" element={<UserList />} />
</Routes>
```

After:

```
<Routes>
  <Route path="/" element={<Index />} />
  <Route path="/users" element={<UserList />} />
  <Route path="/users/:id" element={<UserProfile />} />
  <Route path="*" element={<NotFound />} />
</Routes>
```

**Expected result:** Visiting /users/42 renders the UserProfile component, and the wildcard * route catches any undefined paths.

### 2. Read the URL parameter with useParams and fetch data

*useParams extracts the dynamic segment from the URL so you can use it to query your data source*

In your page component, call useParams() to get the parameter value. The key name must match what you defined in the Route path — if the path is /users/:id, then useParams returns an object with an id property. Use this value to fetch the matching record from Supabase.

Before:

```
function UserProfile() {
  // No parameter reading — hardcoded user ID
  const [user, setUser] = useState(null);

  useEffect(() => {
    supabase.from("users").select("*").eq("id", 1).single();
  }, []);

  return <div>{user?.name}</div>;
}
```

After:

```
import { useParams } from "react-router-dom";

function UserProfile() {
  const { id } = useParams<{ id: string }>();
  const [user, setUser] = useState<User | null>(null);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    if (!id) return;

    supabase
      .from("users")
      .select("*")
      .eq("id", id)
      .single()
      .then(({ data, error }) => {
        if (!error) setUser(data);
        setIsLoading(false);
      });
  }, [id]); // Re-fetch when the URL parameter changes

  if (isLoading) return <p>Loading...</p>;
  if (!user) return <p>User not found.</p>;

  return <div>{user.name}</div>;
}
```

**Expected result:** The component reads the ID from the URL, fetches the matching record, and displays it. Loading and not-found states are handled.

### 3. Handle invalid parameters and show a 404 state

*Users can type anything in the URL — your component must handle IDs that do not match any record gracefully*

Add validation for the URL parameter before making the data request. If the parameter format is wrong (letters where you expect numbers) or the database query returns no results, show a user-friendly not-found message instead of a broken page. Link back to the list page so users can navigate away easily.

Before:

```
// No validation — crashes on invalid ID
useEffect(() => {
  supabase.from("products").select("*").eq("id", id).single()
    .then(({ data }) => setProduct(data));
}, [id]);
```

After:

```
useEffect(() => {
  if (!id) {
    setError("No product ID provided");
    setIsLoading(false);
    return;
  }

  // Validate that the ID looks like a UUID or number
  const isValidId = /^[0-9a-f-]{36}$/.test(id) || /^\d+$/.test(id);
  if (!isValidId) {
    setError("Invalid product ID format");
    setIsLoading(false);
    return;
  }

  supabase
    .from("products")
    .select("*")
    .eq("id", id)
    .single()
    .then(({ data, error }) => {
      if (error || !data) {
        setError("Product not found");
      } else {
        setProduct(data);
      }
      setIsLoading(false);
    });
}, [id]);
```

**Expected result:** Invalid IDs show a friendly 'not found' message instead of a crash. Valid IDs load the correct data.

### 4. Link to dynamic routes using the Link component

*Using Link instead of anchor tags enables client-side navigation without a full page reload*

In your list page, use React Router's Link component to navigate to dynamic routes. Construct the URL by interpolating the item's ID. This keeps navigation within the SPA and preserves app state. If you need programmatic navigation (like after a form submission), use the useNavigate hook instead. For projects with complex routing across many generated pages, RapidDev's engineers have built robust navigation patterns across 600+ Lovable projects.

Before:

```
<ul>
  {users.map(user => (
    <li key={user.id}>
      <a href={`/users/${user.id}`}>{user.name}</a>
    </li>
  ))}
</ul>
```

After:

```
import { Link } from "react-router-dom";

<ul>
  {users.map(user => (
    <li key={user.id}>
      <Link to={`/users/${user.id}`} className="text-primary hover:underline">
        {user.name}
      </Link>
    </li>
  ))}
</ul>
```

**Expected result:** Clicking a user name navigates to /users/[id] without a full page reload, and the browser back button works correctly.

## Complete code example

File: `src/pages/UserProfile.tsx`

```typescript
import { useEffect, useState } from "react";
import { useParams, Link } from "react-router-dom";
import { supabase } from "@/integrations/supabase/client";

interface UserData {
  id: string;
  name: string;
  email: string;
  avatar_url: string | null;
  created_at: string;
}

export default function UserProfile() {
  const { id } = useParams<{ id: string }>();
  const [user, setUser] = useState<UserData | null>(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    if (!id) {
      setError("No user ID provided in the URL");
      setIsLoading(false);
      return;
    }

    supabase
      .from("profiles")
      .select("id, name, email, avatar_url, created_at")
      .eq("id", id)
      .single()
      .then(({ data, error: dbError }) => {
        if (dbError || !data) {
          setError("User not found");
        } else {
          setUser(data);
        }
        setIsLoading(false);
      });
  }, [id]);

  if (isLoading) {
    return <div className="p-8 text-muted-foreground">Loading profile...</div>;
  }

  if (error) {
    return (
      <div className="p-8 text-center">
        <h2 className="text-xl font-semibold mb-2">{error}</h2>
        <Link to="/users" className="text-primary hover:underline">
          Back to user list
        </Link>
      </div>
    );
  }

  return (
    <div className="p-8 max-w-2xl mx-auto">
      <Link to="/users" className="text-sm text-muted-foreground hover:underline mb-4 block">
        &larr; All users
      </Link>
      <h1 className="text-2xl font-bold mb-4">{user?.name}</h1>
      <p className="text-muted-foreground">{user?.email}</p>
    </div>
  );
}
```

## Best practices

- Always define a catch-all Route (path='*') in App.tsx so unmatched URLs show a proper 404 page instead of a blank screen
- Use useParams with TypeScript generics (useParams<{ id: string }>()) for type safety on parameter names
- Add id to the useEffect dependency array so the component re-fetches when the URL parameter changes
- Validate URL parameters before making database queries — show a not-found state for invalid formats
- Use Link from react-router-dom instead of HTML anchor tags to keep navigation within the SPA
- Always include loading and error states in components that fetch data by URL parameter
- Configure SPA fallback routing on your hosting platform so direct URL access to dynamic routes works after deployment

## Frequently asked questions

### How do I create a dynamic route in Lovable?

Add a Route in App.tsx with a colon-prefixed parameter: <Route path='/users/:id' element={<UserProfile />} />. In the UserProfile component, call useParams() to read the id value from the URL. Use that value to fetch data from Supabase.

### Why does my dynamic route show a blank page?

The most likely cause is a missing Route definition in App.tsx. Check that you have a Route with the :param syntax matching the URL pattern. Also verify the component has proper loading and error states so it does not render before data is available.

### How do I handle invalid URL parameters?

Validate the parameter format before making a database query. If the format is wrong or the query returns no results, set an error state and display a not-found message with a link back to the list page. Never assume the parameter will always be valid.

### Can I use slugs instead of IDs in the URL?

Yes. Define the route as /blog/:slug and query your Supabase table using .eq('slug', slug) instead of .eq('id', id). Slugs are more user-friendly in URLs but must be unique in your database. Add a unique constraint on the slug column.

### Why do dynamic routes work in preview but 404 after deployment?

Lovable's preview has built-in SPA routing support, but external hosts (Vercel, Netlify) need explicit configuration. Add a vercel.json with rewrites or a _redirects file so the server routes all paths to index.html.

### How do I pass data between a list page and a detail page?

The recommended approach is to pass only the ID in the URL and fetch the full data on the detail page. This ensures the detail page works on direct access and page refresh. Avoid passing data through React Router state since it is lost on refresh.

### What if I can't fix this myself?

If your app needs complex routing with nested dynamic segments, authentication-gated routes, or data prefetching strategies, RapidDev's engineers have built these patterns across 600+ Lovable projects and can implement a robust solution.

---

Source: https://www.rapidevelopers.com/lovable-issues/using-dynamic-routes-in-lovable-without-errors
© RapidDev — https://www.rapidevelopers.com/lovable-issues/using-dynamic-routes-in-lovable-without-errors
