PDP is the checkout entry point. Left column scrolls with product content. Right column sticks with the booking widget. Pre-checkout dialog eliminated.
URL: /all-pro-waste-services-spring-tx/dumpster-rental/20yd-roll-off-dumpster
All Pro Waste Services · Spring, TX · ★ 4.8 (42)
✓ Accepted
✗ Not accepted
The mobile PDP mirrors the desktop sticky column. Booking form IS the page. CTA above the fold. Supporting content scrolls below. No drawer, no sticky bar.
Above the fold (~750px viewport)
All Pro Waste Services · Spring, TX
Below the fold (scrolled)
Product cards on the hauler profile become list items that link to the PDP. Minimal info: container icon, size + type, lowest price. Details live on the PDP.
/hauler-slug/dumpster-rental/20yd-roll-off-dumpster — container-level page showing this product.20yd-roll-off-dumpster). That page shows both products as
separate booking sections. The card that was clicked could scroll-to or
highlight the relevant product via a hash or query param.
Product slug changes from waste-stream-based to container-type-based. Each waste stream is a separate product — not a variant, not a selector. Container-level URL groups products when multiple exist (0.9% of cases).
| Current (waste-stream in URL) | Proposed (container-type in URL) | |
|---|---|---|
| Format | {size}-yard-{wasteStream} |
{size}yd-{containerType} |
| Example 1 | 10-yard-msw |
10yd-roll-off-dumpster |
| Example 2 | 20-yard-c-and-d |
20yd-roll-off-dumpster |
| Example 3 | 10-yard-msw (trailer) |
10yd-dumpster-trailer |
| Waste stream | In URL path (one URL per product) | Separate products on same container page (no selector) |
| Pages per container | One per waste stream (20yd + 3 waste streams = 3 URLs) |
One per container type (20yd roll-off = 1 URL) |
| DB containerType | URL slug suffix | Full example |
|---|---|---|
roll_off |
roll-off-dumpster |
20yd-roll-off-dumpster |
trailer |
dumpster-trailer |
10yd-dumpster-trailer |
front_load |
front-load-dumpster |
2yd-front-load-dumpster |
rear_load |
rear-load-dumpster |
4yd-rear-load-dumpster |
wheeled_cart |
wheeled-cart |
96gal-wheeled-cart |
// packages/db/src/utils/product-slug.ts const CONTAINER_TYPE_SLUGS: Record<string, string> = { roll_off: "roll-off-dumpster", trailer: "dumpster-trailer", front_load: "front-load-dumpster", rear_load: "rear-load-dumpster", wheeled_cart: "wheeled-cart", } export function generateContainerSlug( size: string | number, volumeUnit: string, containerType: string, ): string { const n = parseFloat(String(size)) const sizeStr = n % 1 === 0 ? String(n) : n.toFixed(1) const unit = volumeUnit === "yard" ? "yd" : "gal" const typeSuffix = CONTAINER_TYPE_SLUGS[containerType] ?? containerType.replace(/_/g, "-") return `${sizeStr}${unit}-${typeSuffix}` } // "20yd-roll-off-dumpster" // "10yd-dumpster-trailer" // "96gal-wheeled-cart"
/hauler/lob/10-yard-msw → /hauler/lob/10yd-roll-off-dumpster?waste=msw
redirects in next.config.mjs
or middleware.
?waste=msw param pre-selects the waste stream on the PDP so inbound links
from search or directory pages land on the right context.
Every decision in this wireframe traced to a framework or validated pattern.
size-directory-view.tsx. Industry-standard sizing guidance.
No deep research needed. Refine with booking data when available.
?waste= param needed. PDP defaults to first available waste stream.