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> componentThe 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
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
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().
<Routes> <Route path="/" element={<Index />} /> <Route path="/users" element={<UserList />} /></Routes><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.
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
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.
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>;}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.
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
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.
// No validation — crashes on invalid IDuseEffect(() => { supabase.from("products").select("*").eq("id", id).single() .then(({ data }) => setProduct(data));}, [id]);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.
Link to dynamic routes using the Link component
Using Link instead of anchor tags enables client-side navigation without a full page reload
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.
<ul> {users.map(user => ( <li key={user.id}> <a href={`/users/${user.id}`}>{user.name}</a> </li> ))}</ul>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
1import { useEffect, useState } from "react";2import { useParams, Link } from "react-router-dom";3import { supabase } from "@/integrations/supabase/client";45interface UserData {6 id: string;7 name: string;8 email: string;9 avatar_url: string | null;10 created_at: string;11}1213export default function UserProfile() {14 const { id } = useParams<{ id: string }>();15 const [user, setUser] = useState<UserData | null>(null);16 const [isLoading, setIsLoading] = useState(true);17 const [error, setError] = useState<string | null>(null);1819 useEffect(() => {20 if (!id) {21 setError("No user ID provided in the URL");22 setIsLoading(false);23 return;24 }2526 supabase27 .from("profiles")28 .select("id, name, email, avatar_url, created_at")29 .eq("id", id)30 .single()31 .then(({ data, error: dbError }) => {32 if (dbError || !data) {33 setError("User not found");34 } else {35 setUser(data);36 }37 setIsLoading(false);38 });39 }, [id]);4041 if (isLoading) {42 return <div className="p-8 text-muted-foreground">Loading profile...</div>;43 }4445 if (error) {46 return (47 <div className="p-8 text-center">48 <h2 className="text-xl font-semibold mb-2">{error}</h2>49 <Link to="/users" className="text-primary hover:underline">50 Back to user list51 </Link>52 </div>53 );54 }5556 return (57 <div className="p-8 max-w-2xl mx-auto">58 <Link to="/users" className="text-sm text-muted-foreground hover:underline mb-4 block">59 ← All users60 </Link>61 <h1 className="text-2xl font-bold mb-4">{user?.name}</h1>62 <p className="text-muted-foreground">{user?.email}</p>63 </div>64 );65}Best practices to prevent this
- 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
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I have a Lovable.dev project and I need to create a dynamic route that shows different content based on the URL. For example, /products/42 should show product #42. Here is my current App.tsx routing setup: [paste your Routes from App.tsx] Here is the component that should display the dynamic content: [paste your page component] Please help me: 1. Add the correct Route definition with a :param segment 2. Use useParams to read the parameter 3. Fetch the matching data from Supabase 4. Handle loading, not-found, and invalid parameter states
I need a dynamic route at /products/:id that loads a product from the products Supabase table and displays its name, price, and description. Add the Route definition in @src/App.tsx and create a new ProductDetail page component in @src/pages/ProductDetail.tsx. Include loading and not-found states. Use the Link component in @src/pages/Products.tsx to link each product to its detail page.
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.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your issue.
Book a free consultation