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.
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:
// 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 view | Renders via | Data passed to the view |
|---|---|---|---|
/ (home) | home | HomeController@index | $pageTitle, $sections, $seoContents |
/pricing (pricing) | pricing | FrontendController@pricing | $categories, $sections, $pageTitle |
/contact (contact) | contact | HomeController@contact | $pageTitle, $user, $sections, $seoContents |
/cookie-policy (cookie.policy) | cookie | HomeController@cookiePolicy | $cookie, $pageTitle, $seoContents |
/policy/{slug} (policy.pages) | policy | HomeController@policyPages | $policy, $pageTitle, $seoContents |
/{slug} (pages) | pages | HomeController@pages | $sections, $pageTitle, $seoContents |
/products/{slug} (products.category) | products.category | ProductController@category | $category, $products, $categories, $seoContents |
/products/{cat}/{prod}/order (products.order) | products.order | ProductController@order | $product, $category, $plans, $domains, $categories, $pricingData, $groupedFeatures |
/domains (domains.index) | domains.index | DomainController@index | $featuredTlds, $tlds, $sections |
/domains/search (domains.search) | domains.search | DomainController@search | $query, $results, $suggestions, $tlds, $categories |
/domains/transfer (domains.transfer) | domains.transfer | DomainController@transfer | $tlds, $sections |
/domains/pricing (domains.pricing) | domains.pricing | DomainController@pricing | $tlds, $categories |
/domains/configure (domains.configure) | domains.configure | DomainController@configure | $product, $tlds |
/cart (shopping.cart.index) | cart.index | CartController@index | $items, $total |
/checkout (user.checkout) | cart.checkout | CartController@checkout | $items, $total, $user, $automaticGateways, $manualGateways |
/order/confirmation (shopping.cart.confirmation) | cart.confirmation | CartController@confirmation | $order, $invoice |
/kb (kb.index) | kb.index | KnowledgeBaseController@index | $categories, $popularArticles |
/kb/category/{slug} (kb.category) | kb.category | KnowledgeBaseController@category | $category, $articles, $allCategories |
/kb/article/{slug} (kb.article) | kb.article | KnowledgeBaseController@article | $article, $relatedArticles, $allCategories, $pageTitle, $seoContents |
/kb/search (kb.search) | kb.search | KnowledgeBaseController@search | $query, $results, $categories |
/blog (blog.index) | blog.index | BlogController@index | $pageTitle, $blogs |
/blog/{slug} (blog.details) | blog.details | BlogController@details | $blog, $recentBlogs, $seoContents |
/compare (store.compare) | store.compare-page | App\Livewire\Store\ComparePage | Livewire component owns its state |
/affiliate-program (affiliate.program) | affiliate | FrontendController@affiliate | $commissionRate, $commissionType, $minPayoutAmount, $holdDays, $cookieDays |
/maintenance-mode (maintenance) | maintenance | HomeController@maintenance | $maintenance, $pageTitle — served 503 |
/unsubscribe/{token} (subscribe.unsubscribe) | unsubscribe | HomeController@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@categoryrenderspageswhen the category has a linked custompage_id; otherwiseproducts.category.BlogController@indexrenderspageswhen ablogpage exists in the builder; otherwiseblog.index.KnowledgeBaseController@indexrenderspageswhen akbpage exists; otherwisekb.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:
@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:
<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/compareroute rendersstore.compare-page(theme-owned) and the printable sheet renderscomponents.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:
$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 pageTwo fall-through rules matter:
- 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." - JSON requests → framework default. API/
Accept: application/jsonerrors are never themed.
Ship a view for each status the storefront can emit:
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.phpEach 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.