Skip to content

Pages and how they resolve

Every public page a Salieno theme must ship, the controller that renders each one, and the data it hands your Blade view.

7 min readUpdated Aug 13, 2026
On this page

Pages and how they resolve#

A Salieno theme is complete only when it ships every public view the storefront serves. Core privileges no theme and carries no bundled fallback: when a theme is installed but a page view is missing, view resolution throws — that is a bug its developer needs to see, not something Core papers over. (The only graceful degradation is the empty-themes-directory case, which renders a neutral Core holding page. See Theme overview.)

This article is the inventory. For each public surface it names the route, the theme view Core resolves, the controller method that renders it, and the data your Blade receives. Build a view for every row and your theme is page-complete.

One rendering path#

Almost every public page routes through the same helper. The frontend controllers extend App\Http\Controllers\Frontend\FrontendController, whose themeView() is a thin wrapper:

php
// FrontendController::themeView()
public function themeView($view, $data = []): View
{
    return view(themeViewName($view), $data);
}

themeViewName('home') expands to "themes.{active}.home", so themeView('domains.search', [...]) renders resources/views/themes/{your-theme}/domains/search.blade.php. Dotted names map to subdirectories. You never hardcode your theme name — the helper reads the operator's active theme. See Helper & template reference for the full helper set.

A few surfaces bypass the wrapper and call view() / response()->view() directly with activeTheme() (which returns the "themes.{active}." prefix): the maintenance page, the error pages, and the compare print sheet. They resolve theme views the same way — they just set a status code or headers the wrapper does not.

Every public page#

Each of these views is required for a page-complete theme. Data columns list the variables the controller passes into your Blade.

Route (name)Theme viewRenders viaData passed to the view
/ (home)homeHomeController@index$pageTitle, $sections, $seoContents
/pricing (pricing)pricingFrontendController@pricing$categories, $sections, $pageTitle
/contact (contact)contactHomeController@contact$pageTitle, $user, $sections, $seoContents
/cookie-policy (cookie.policy)cookieHomeController@cookiePolicy$cookie, $pageTitle, $seoContents
/policy/{slug} (policy.pages)policyHomeController@policyPages$policy, $pageTitle, $seoContents
/{slug} (pages)pagesHomeController@pages$sections, $pageTitle, $seoContents
/products/{slug} (products.category)products.categoryProductController@category$category, $products, $categories, $seoContents
/products/{cat}/{prod}/order (products.order)products.orderProductController@order$product, $category, $plans, $domains, $categories, $pricingData, $groupedFeatures
/domains (domains.index)domains.indexDomainController@index$featuredTlds, $tlds, $sections
/domains/search (domains.search)domains.searchDomainController@search$query, $results, $suggestions, $tlds, $categories
/domains/transfer (domains.transfer)domains.transferDomainController@transfer$tlds, $sections
/domains/pricing (domains.pricing)domains.pricingDomainController@pricing$tlds, $categories
/domains/configure (domains.configure)domains.configureDomainController@configure$product, $tlds
/cart (shopping.cart.index)cart.indexCartController@index$items, $total
/checkout (user.checkout)cart.checkoutCartController@checkout$items, $total, $user, $automaticGateways, $manualGateways
/order/confirmation (shopping.cart.confirmation)cart.confirmationCartController@confirmation$order, $invoice
/kb (kb.index)kb.indexKnowledgeBaseController@index$categories, $popularArticles
/kb/category/{slug} (kb.category)kb.categoryKnowledgeBaseController@category$category, $articles, $allCategories
/kb/article/{slug} (kb.article)kb.articleKnowledgeBaseController@article$article, $relatedArticles, $allCategories, $pageTitle, $seoContents
/kb/search (kb.search)kb.searchKnowledgeBaseController@search$query, $results, $categories
/blog (blog.index)blog.indexBlogController@index$pageTitle, $blogs
/blog/{slug} (blog.details)blog.detailsBlogController@details$blog, $recentBlogs, $seoContents
/compare (store.compare)store.compare-pageApp\Livewire\Store\ComparePageLivewire component owns its state
/affiliate-program (affiliate.program)affiliateFrontendController@affiliate$commissionRate, $commissionType, $minPayoutAmount, $holdDays, $cookieDays
/maintenance-mode (maintenance)maintenanceHomeController@maintenance$maintenance, $pageTitle — served 503
/unsubscribe/{token} (subscribe.unsubscribe)unsubscribeHomeController@unsubscribe$subscriber, $resubscribed

$sections is a Page model row (the admin page-builder content) or null; your section-rendering partials tolerate both. $seoContents feeds the meta tags in your layout. See Building sections for how $sections drives the render pipeline, and Layouts and the page shell for how $seoContents is consumed.

Views that double as the generic CMS renderer#

Three surfaces route to your pages view instead of a bespoke one when the operator has built a page in the admin CMS for that slug, and only fall back to their dedicated view otherwise:

  • ProductController@category renders pages when the category has a linked custom page_id; otherwise products.category.
  • BlogController@index renders pages when a blog page exists in the builder; otherwise blog.index.
  • KnowledgeBaseController@index renders pages when a kb page exists; otherwise kb.index.

So pages.blade.php is not optional trivia — it is the workhorse that renders any operator-built page (including every custom /{slug} route). It receives $sections and simply hands it to your section renderer. Ship it, and ship the dedicated fallbacks too.

Livewire widgets you style, not build#

Some pages embed Core-provided Livewire components. You do not implement these — you place the tag and style the markup around it (and, for a few, override a x-theme.* sub-component it uses; see Components). The bindings must be preserved verbatim.

  • Domain search — the search box and results grid. The reference theme places it in domains/search.blade.php:
blade
@livewire('store.domain-search-widget', ['mode' => 'full', 'prefilledQuery' => $query])

A compact inline variant ('mode' => 'inline') is dropped into heroes and the header via a partials/domain_search_form.blade.php.

  • Domain pricing table — the full TLD price grid on domains/pricing.blade.php:
blade
<livewire:frontend.domains.domain-pricing-list />
  • Compare bar + compare page — a global <livewire:store.compare-bar /> mounted once in your layout tracks the visitor's comparison selection across the site; the full-page /compare route renders store.compare-page (theme-owned) and the printable sheet renders components.theme.compare.print-view.

The plan tiles on pricing are not a Livewire widget — they are the x-theme.pricing.* component family rendered from your section blades, which you can restyle or override. Keep any wire:model / wire:click / wire:submit attributes intact wherever a widget appears.

Error pages#

Error views are resolved outside the controller layer, in bootstrap/app.php. On any HttpException, Core computes activeTheme() . 'errors.' . $code and renders it with the matching status code when the view exists:

php
$code = $e->getStatusCode();          // 404, 500, ...
$themeView = activeTheme() . 'errors.' . $code;

if (view()->exists($themeView)) {
    return response()->view($themeView, [
        'exception' => $e,
        'pageTitle' => $code . ' Error',
    ], $code);
}

return null; // fall through to the framework default error page

Two fall-through rules matter:

  1. No theme installed → framework default. When the themes directory is empty, Core short-circuits and returns the framework's own error page. This is deliberate: the fallback view finder would otherwise answer "yes, it exists" for every themes.* view and splice the holding page into every 419/500, which on the admin panel reads as "I can't get in."
  2. JSON requests → framework default. API/Accept: application/json errors are never themed.

Ship a view for each status the storefront can emit:

code
errors/403.blade.php
errors/404.blade.php
errors/419.blade.php   (expired CSRF / session)
errors/429.blade.php   (rate limited)
errors/500.blade.php
errors/503.blade.php

Each receives $exception and $pageTitle. Keep them self-contained and lightweight — a 500 view that itself depends on a heavy layout or a database read can fail while rendering the failure. Note that the customer-facing maintenance screen is a separate top-level maintenance.blade.php (served 503 with a Retry-After header), not errors/503; build both.

The completeness checklist#

For a theme that never throws in production, provide, at minimum, every view in the table above plus the six errors/* views, maintenance, and the x-theme.* primitives (or your own markup that preserves the Livewire bindings). The scaffolder gives you all of them pre-wired — php artisan template:scaffold <name> clones the reference theme as a complete starting point, so you are pruning and restyling rather than hunting for missing routes. Testing your theme walks the full checklist and the fastest way to exercise every route locally.

Next: Authentication pages — the login, register, and password-reset screens, which are Livewire components rather than plain controller views and have their own binding rules.

theme-developmentpagesroutingcontrollers
Was this article helpful?
Still stuck?Contact support
Pages and how they resolve · Salieno Docs