404 Page Design and Navigation Recovery
Create helpful 404 error pages that guide lost visitors back to your store with search, popular products, and clear navigation paths.
A good 404 page transforms a frustrating dead end into a helpful waypoint. Instead of losing customers to broken links, guide them back to your store with useful content and clear navigation.
The 404 Template
Unlike other templates, the 404 template is always .liquid (not JSON):
{# templates/404.liquid #}
{% section '404-content' %}This allows you to render a section with customizable content.
Basic 404 Section
{# sections/404-content.liquid #}
<section class="error-page section-{{ section.id }}"> <div class="container"> <div class="error-page__content"> <h1 class="error-page__code">404</h1> <h2 class="error-page__heading">{{ section.settings.heading }}</h2> <p class="error-page__message">{{ section.settings.message }}</p>
{# Search form for recovery #} <div class="error-page__search"> <form action="{{ routes.search_url }}" method="get" role="search"> <label for="error-search" class="visually-hidden">Search</label> <div class="error-page__search-field"> <input type="search" id="error-search" name="q" placeholder="{{ section.settings.search_placeholder }}" class="error-page__search-input" > <button type="submit" class="error-page__search-button"> Search </button> </div> </form> </div>
{# Primary navigation #} <div class="error-page__links"> <a href="{{ routes.root_url }}" class="button button--primary"> {{ section.settings.home_button_text }} </a> <a href="{{ routes.collections_url }}" class="button button--secondary"> {{ section.settings.shop_button_text }} </a> </div> </div> </div></section>
{% schema %}{ "name": "404 Content", "settings": [ { "type": "text", "id": "heading", "label": "Heading", "default": "Page not found" }, { "type": "textarea", "id": "message", "label": "Message", "default": "Sorry, we couldn't find the page you're looking for. Try searching or explore our collections." }, { "type": "text", "id": "search_placeholder", "label": "Search placeholder", "default": "Search our store" }, { "type": "text", "id": "home_button_text", "label": "Home button text", "default": "Go to Homepage" }, { "type": "text", "id": "shop_button_text", "label": "Shop button text", "default": "Browse Collections" } ]}{% endschema %}Enhanced 404 with Popular Products
Help visitors discover products they might want:
{# sections/404-content.liquid (enhanced) #}
<section class="error-page section-{{ section.id }}"> <div class="container"> <div class="error-page__content"> <h1 class="error-page__code">404</h1> <h2 class="error-page__heading">{{ section.settings.heading }}</h2> <p class="error-page__message">{{ section.settings.message }}</p>
{# Search #} <form action="{{ routes.search_url }}" method="get" class="error-page__search"> <input type="search" name="q" placeholder="{{ section.settings.search_placeholder }}" aria-label="Search" > <button type="submit">Search</button> </form>
{# Quick links #} <div class="error-page__actions"> <a href="{{ routes.root_url }}" class="button button--primary"> Back to Home </a> <a href="{{ routes.collections_url }}" class="button button--secondary"> Shop All </a> </div> </div>
{# Popular products #} {%- if section.settings.show_products -%} {%- assign featured_collection = collections[section.settings.collection] -%} {%- if featured_collection.products.size > 0 -%} <div class="error-page__products"> <h3 class="error-page__products-heading">{{ section.settings.products_heading }}</h3>
<div class="error-page__products-grid"> {%- for product in featured_collection.products limit: section.settings.products_count -%} <div class="error-page__product"> <a href="{{ product.url }}" class="error-page__product-link"> {%- if product.featured_image -%} {{ product.featured_image | image_url: width: 300 | image_tag: loading: 'lazy', class: 'error-page__product-image' }} {%- endif -%} <span class="error-page__product-title">{{ product.title }}</span> <span class="error-page__product-price">{{ product.price | money }}</span> </a> </div> {%- endfor -%} </div> </div> {%- endif -%} {%- endif -%}
{# Popular collections #} {%- if section.settings.show_collections -%} <div class="error-page__collections"> <h3 class="error-page__collections-heading">{{ section.settings.collections_heading }}</h3>
<div class="error-page__collections-grid"> {%- for block in section.blocks -%} {%- assign linked_collection = collections[block.settings.collection] -%} {%- if linked_collection -%} <a href="{{ linked_collection.url }}" class="error-page__collection-link" {{ block.shopify_attributes }}> {%- if linked_collection.image -%} {{ linked_collection.image | image_url: width: 400 | image_tag: loading: 'lazy', class: 'error-page__collection-image' }} {%- endif -%} <span class="error-page__collection-title">{{ linked_collection.title }}</span> </a> {%- endif -%} {%- endfor -%} </div> </div> {%- endif -%} </div></section>
{% schema %}{ "name": "404 Content", "settings": [ { "type": "header", "content": "Content" }, { "type": "text", "id": "heading", "label": "Heading", "default": "Page not found" }, { "type": "textarea", "id": "message", "label": "Message", "default": "Sorry, we couldn't find what you're looking for." }, { "type": "text", "id": "search_placeholder", "label": "Search placeholder", "default": "Try searching..." }, { "type": "header", "content": "Products" }, { "type": "checkbox", "id": "show_products", "label": "Show popular products", "default": true }, { "type": "collection", "id": "collection", "label": "Collection" }, { "type": "text", "id": "products_heading", "label": "Products heading", "default": "You might like these" }, { "type": "range", "id": "products_count", "label": "Number of products", "min": 2, "max": 8, "default": 4 }, { "type": "header", "content": "Collections" }, { "type": "checkbox", "id": "show_collections", "label": "Show collections", "default": true }, { "type": "text", "id": "collections_heading", "label": "Collections heading", "default": "Browse our collections" } ], "blocks": [ { "type": "collection", "name": "Collection", "settings": [ { "type": "collection", "id": "collection", "label": "Collection" } ] } ], "presets": [ { "name": "404 Content", "blocks": [ { "type": "collection" }, { "type": "collection" }, { "type": "collection" } ] } ]}{% endschema %}Creative 404 Designs
Minimal Style
<section class="error-minimal"> <div class="error-minimal__content"> <span class="error-minimal__404">404</span> <p class="error-minimal__text">This page has wandered off.</p> <a href="{{ routes.root_url }}" class="error-minimal__link"> Take me home </a> </div></section>Illustrated Style
<section class="error-illustrated"> <div class="error-illustrated__graphic"> {# Custom SVG or image #} {{ section.settings.image | image_url: width: 600 | image_tag }} </div> <h1>{{ section.settings.heading }}</h1> <p>{{ section.settings.message }}</p> <a href="{{ routes.root_url }}" class="button">{{ section.settings.button_text }}</a></section>Interactive Style
<section class="error-interactive" x-data="{ searching: false }"> <h1>Lost? Let's find your way.</h1>
<div class="error-interactive__options"> <button @click="searching = true" class="error-interactive__option"> Search for something </button> <a href="{{ routes.root_url }}" class="error-interactive__option"> Go to homepage </a> <a href="{{ routes.collections_url }}" class="error-interactive__option"> Browse all products </a> </div>
<div class="error-interactive__search" x-show="searching" x-transition> <form action="{{ routes.search_url }}" method="get"> <input type="search" name="q" placeholder="What are you looking for?" autofocus> <button type="submit">Search</button> </form> </div></section>404 Styles
.error-page { min-height: 60vh; display: flex; align-items: center; justify-content: center; text-align: center; padding: var(--spacing-2xl) 0;}
.error-page__content { max-width: 600px;}
.error-page__code { font-size: clamp(6rem, 20vw, 12rem); font-weight: 700; line-height: 1; color: var(--color-primary); opacity: 0.2; margin-bottom: var(--spacing-md);}
.error-page__heading { font-size: clamp(1.5rem, 4vw, 2.5rem); margin-bottom: var(--spacing-md);}
.error-page__message { font-size: 1.125rem; color: var(--color-text-light); margin-bottom: var(--spacing-xl);}
.error-page__search { max-width: 400px; margin: 0 auto var(--spacing-lg);}
.error-page__search-field { display: flex; gap: var(--spacing-xs);}
.error-page__search-input { flex: 1; padding: var(--spacing-sm) var(--spacing-md); border: 1px solid var(--color-border); border-radius: var(--border-radius);}
.error-page__search-button { padding: var(--spacing-sm) var(--spacing-lg); background: var(--color-primary); color: white; border: none; border-radius: var(--border-radius); cursor: pointer;}
.error-page__actions { display: flex; gap: var(--spacing-md); justify-content: center; flex-wrap: wrap;}
/* Products section */.error-page__products { margin-top: var(--spacing-2xl); padding-top: var(--spacing-2xl); border-top: 1px solid var(--color-border);}
.error-page__products-heading { margin-bottom: var(--spacing-lg);}
.error-page__products-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--spacing-lg);}
.error-page__product-link { display: block; text-decoration: none; color: inherit;}
.error-page__product-image { width: 100%; aspect-ratio: 1; object-fit: cover; border-radius: var(--border-radius); margin-bottom: var(--spacing-sm);}
.error-page__product-title { display: block; font-weight: 500; margin-bottom: var(--spacing-xs);}
.error-page__product-price { color: var(--color-text-light);}
/* Collections section */.error-page__collections-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--spacing-md);}
.error-page__collection-link { position: relative; display: block; overflow: hidden; border-radius: var(--border-radius); text-decoration: none;}
.error-page__collection-image { width: 100%; aspect-ratio: 3/2; object-fit: cover; transition: transform 0.3s ease;}
.error-page__collection-link:hover .error-page__collection-image { transform: scale(1.05);}
.error-page__collection-title { position: absolute; bottom: 0; left: 0; right: 0; padding: var(--spacing-md); background: linear-gradient(transparent, rgba(0, 0, 0, 0.7)); color: white; font-weight: 500;}Best Practices
Do:
- Provide a clear search option
- Link to popular pages and collections
- Keep the design on brand
- Use friendly, helpful language
- Include navigation back to safety
Avoid:
- Technical jargon (“HTTP Error 404”)
- Blaming the user
- Dead ends with no options
- Boring generic pages
- Missing search functionality
Helpful Language Examples
Instead of this:
“Error 404: Page Not Found”
Try this:
“Oops! This page has moved or no longer exists.”
Instead of this:
“The requested URL was not found on this server.”
Try this:
“We can’t find what you’re looking for, but here’s how to get back on track.”
Analytics and Monitoring
Track 404 errors to identify broken links:
{# In 404 template #}<script> // Log 404 for analytics if (typeof gtag !== 'undefined') { gtag('event', 'page_not_found', { 'page_path': window.location.pathname, 'page_referrer': document.referrer }); }</script>Practice Exercise
Create a 404 page that includes:
- Friendly error message
- Search form
- Featured products from a bestsellers collection
- Links to main collections
- On-brand styling
Test by:
- Visiting a non-existent URL
- Checking mobile responsiveness
- Verifying search works
- Clicking all links
Key Takeaways
- 404 template must be
.liquid, not JSON - Search form is essential for recovery
- Product suggestions can save the sale
- Friendly language reduces frustration
- Multiple exit paths (home, shop, search)
- On-brand design maintains trust
- Analytics help identify broken links
What’s Next?
Congratulations on completing the Content Pages module! The next module covers Customer Account Pages for building login, registration, and account management experiences.
Finished this lesson?
Mark it complete to track your progress.
Discussion
Loading comments...