Building with Blade
The source tree behind every website — how pages become URLs, how layouts and components fit together, and how the templates compile.
Every Max AI website is written in Blade, the template language that ships with the Laravel framework. If you have never met it, the short version is: Blade is HTML with a small set of extra instructions for printing values, repeating markup, and pulling reusable pieces into a page.
The official Blade documentation is a good companion to this page. Read it knowing one thing up front: your website uses the static part of Blade, described below and listed exhaustively in the syntax reference.
Templates in, static website out
Your website has no server running behind it. It is a folder of finished files sitting on a fast global network, which is why it loads quickly, never goes down under traffic, and cannot be hacked through a login form it does not have.
So Blade runs at build time, not when a visitor arrives:
- You (or the assistant) save a template.
- The platform compiles every page in your website.
- The result — plain HTML, CSS, images — replaces the previous build.
- Your preview and your free dev address show it immediately. Your custom domain keeps serving the last version you published until you press Publish.
The compile is fast enough to be invisible; saving a file and looking at the preview is the whole loop.
The source tree
There are exactly two top-level folders, and everything you write lives in one of them.
resources/
views/
pages/ ← one file per URL
index.blade.php
about.blade.php
contact.blade.php
404.blade.php
components/
layouts/
main.blade.php ← the document every page uses
sections/
nav.blade.php ← reusable page sections
hero.blade.php
footer.blade.php
data/
site.json ← read as $site
collections/
services.json ← read as $services
posts/*.json ← a large collection, one file per entry, read as $posts
css/
site.css ← the stylesheet
public/
images/ ← Media panel uploads
robots.txt
favicon.svg
resources/holds sources. Nothing in it is served directly; it is compiled.public/is served verbatim from the root of your website.public/robots.txtis/robots.txt;public/images/hero.webpis/images/hero.webp.
Every template file ends in .blade.php — pages, layouts, and sections alike. Do not
create index.html or any other file at the root of the tree: the build generates those
from your sources and overwrites anything you put there by hand.
Pages and URLs
A file under resources/views/pages/ becomes a URL. The mapping is entirely mechanical:
| Page file | URL |
|---|---|
pages/index.blade.php |
/ |
pages/about.blade.php |
/about |
pages/services/plumbing.blade.php |
/services/plumbing |
pages/blog/index.blade.php |
/blog |
pages/404.blade.php |
the "page not found" page |
Addresses are clean — /about, not /about.html. There is one more kind of page, named
[collection.field].blade.php, which generates one URL per row of your data; that is
covered in Site data and collections.
The quickest way to add a page is still New page… in the builder menu: it asks for a name and a URL, writes a correctly wrapped Blade page for you, and opens it. You can then edit it, or describe what it should contain to the assistant.
Printing values
Three pieces of syntax cover everything you output:
{{ $site->name }} {{-- escaped: safe for any text --}}
{!! $post->content !!} {{-- raw: only for HTML you trust --}}
{{-- a comment; never reaches the browser --}}
Use {{ }} for all text and attribute values — it escapes special characters, so a
business name containing & or < renders correctly instead of breaking the markup.
Reach for {!! !!} only when the value is deliberately HTML, such as an inline SVG icon
stored in your data.
Layouts
A layout is the full HTML document your pages are poured into: the <head>, the
navigation, the footer, the closing tags. Writing it once means a change to the navigation
is a change to every page.
resources/views/components/layouts/main.blade.php:
@props(['title' => 'Home', 'description' => ''])
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ $title }}</title>
<meta name="description" content="{{ $description }}">
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
@vite(['resources/css/site.css'])
</head>
<body class="bg-canvas text-ink antialiased">
<a href="#main-content" class="sr-only focus:not-sr-only">Skip to content</a>
<x-sections.nav/>
<main id="main-content">{{ $slot }}</main>
<x-sections.footer/>
</body>
</html>
Two things are doing the work:
@props([...])declares what the layout accepts and what each value defaults to. Here, a page can pass atitleand adescription; if it passes neither, the defaults apply.{{ $slot }}is where the page's own content is dropped in.
A page then wraps itself in that layout:
<x-layouts.main title="About us" description="Licensed local pros since 2009.">
<section class="mx-auto max-w-5xl px-6 py-20">
<h1 class="text-4xl font-bold">About {{ $site->name }}</h1>
<p class="mt-4 text-lg text-muted">{{ $site->tagline }}</p>
</section>
</x-layouts.main>
Everything between the opening and closing tag becomes $slot. Attributes on the tag —
title, description — arrive as the props the layout declared.
Blade in a full Laravel application also offers
@extends,@section, and@yield. Those are not part of the static subset. Layouts here are always components.
Section components
Anything you would otherwise copy between pages belongs in a component under
resources/views/components/. The convention is components/sections/ for the visible
building blocks of a page — a hero, a services grid, a testimonial band, a call to action.
A component is addressed by its path with dots instead of slashes:
| File | Tag |
|---|---|
components/layouts/main.blade.php |
<x-layouts.main> |
components/sections/hero.blade.php |
<x-sections.hero/> |
components/sections/feature-grid.blade.php |
<x-sections.feature-grid/> |
A page becomes a short, readable list of the sections it is made of:
<x-layouts.main title="Keystone Home Services" description="Booked in minutes.">
<x-sections.hero/>
<x-sections.services/>
<x-sections.reviews/>
<x-sections.cta/>
</x-layouts.main>
Passing values in
Components take props exactly as layouts do:
{{-- components/sections/cta.blade.php --}}
@props(['heading' => 'Ready when you are', 'label' => 'Get a free estimate'])
<section class="bg-primary py-20 text-primary-foreground">
<div class="mx-auto max-w-3xl px-6 text-center">
<h2 class="text-3xl font-semibold">{{ $heading }}</h2>
<a href="/contact" class="mt-8 inline-block rounded-full px-6 py-3">
{{ $label }}
</a>
</div>
</section>
<x-sections.cta heading="Book your visit today"/>
A prop default may be a plain value — a string, a number, true, false — or an empty
array [] or a flat list. Nested or keyed array defaults are not supported; put
structured data in a collection instead.
What a component can see
A component sees three things and nothing else:
- The props passed to it.
$site, your global site data.- Every data collection, by name — whether it is one file or a folder of entry files.
It does not inherit the variables of the page that used it. That is deliberate: a section behaves the same wherever you drop it.
Control flow
Blade's conditionals and loops work as you would expect:
@if ($site->phone)
<a href="{{ $site->phone_href }}">{{ $site->phone }}</a>
@elseif ($site->email)
<a href="mailto:{{ $site->email }}">{{ $site->email }}</a>
@else
<a href="/contact">Contact us</a>
@endif
@foreach ($services as $service)
<article class="border-t border-line py-6">
<h3 class="font-semibold">{{ $service->name }}</h3>
<p class="mt-2 text-muted">{{ $service->blurb }}</p>
</article>
@endforeach
A property your data does not contain is a compile error rather than an empty value, so
use ?? when something is genuinely optional: {{ $site->tagline ?? '' }}.
Inside a loop, $loop describes where you are — $loop->first, $loop->last,
$loop->index, $loop->iteration, $loop->count, $loop->even, $loop->odd:
@foreach ($reviews as $review)
@if ($loop->first)
<blockquote class="text-2xl font-medium">{{ $review->quote }}</blockquote>
@else
<blockquote class="text-base text-muted">{{ $review->quote }}</blockquote>
@endif
@endforeach
@break and @continue accept a condition, which is how you show "the first three of
something":
@foreach ($posts as $post)
@break($loop->iteration == 4)
<a href="/blog/{{ $post->slug }}">{{ $post->title }}</a>
@endforeach
Expressions are deliberately simple: variables, property access ($post->title),
comparisons, &&, ||, !, parentheses, literals, and ?? for a fallback. There are no
function calls. The full list is in the syntax reference.
Styling
Your website is styled with Tailwind CSS utility classes
written directly in the markup, plus one stylesheet at resources/css/site.css:
@import 'tailwindcss';
@theme {
--color-canvas: #ffffff;
--color-ink: oklch(23% 0.012 277);
--color-muted: oklch(46% 0.012 277);
--color-line: oklch(91.5% 0.005 277);
--font-sans: 'Inter', ui-sans-serif, system-ui, sans-serif;
}
Every name you declare in @theme becomes a real utility: --color-canvas gives you
bg-canvas, text-canvas, border-canvas, and the rest. Retuning a handful of tokens
recolors the whole website without touching a single page.
The layout links the stylesheet with @vite(['resources/css/site.css']). You do not have
to configure anything: the build compiles your stylesheet down to exactly the utilities
your pages actually use and links the result.
Your brand colors
Four tokens are guaranteed to exist on every website, whether or not the stylesheet mentions them:
--color-primaryand--color-primary-foreground--color-secondaryand--color-secondary-foreground
They carry the colors chosen on the website's Branding page, which means
bg-primary text-primary-foreground on a button keeps following the brand when someone
changes it there — while a hard-coded bg-indigo-600 does not. Prefer the tokens for
buttons, links, active states, and accents.
Write class names out in full
The build scans your markup for class names, so only classes that appear literally in a
template exist in the finished stylesheet. class="bg-primary" works. Assembling a class
name from pieces does not.
Images and other files
Anything you upload through the builder's Media panel lands in public/images/, and
you reference it by its served address:
<img src="/images/storefront.webp" alt="Our shop on Alder Street" class="rounded-xl">
Uploads are already resized and compressed for the web. Give every meaningful image a
purposeful alt; use alt="" aria-hidden="true" for images that are pure decoration.
Other public/ files — robots.txt, a favicon, a PDF menu — work exactly the same way:
put them in public/, link them from the root.
JavaScript
Client-side JavaScript is fine. Put a single <script> at the end of the layout's
<body> — one copy for the whole website, not one inside each section — and keep it
optional: pages must render completely with JavaScript disabled. Gate any reveal
animation behind a class the script itself adds, so a script that fails to run costs you
the animation and never the content.
Link between pages with plain <a href="/about">. Never navigate with JavaScript.
What happens when you save
Every save recompiles the website, and the build does more than render templates:
- Every page under
pages/is compiled, andpublic/is copied over the top. sitemap.xmlandllms.txtare generated from the pages that exist, unless you ship your own.- Your stylesheet is compiled with only the utilities your pages use.
- Internal links that point nowhere, images whose files are missing, and fonts used but never loaded are all reported.
- The platform's health-check file is written, so publishing always works.
Problems are reported, not fatal: a template with a mistake in it still produces a page, which is why a broken template usually shows up as a section that renders wrong or goes missing rather than as an error screen. See what to do when a template will not compile.
Learn more
The Laravel documentation covers Blade in depth. These sections are the ones that map onto what you can use here:
- Blade templates — the language overall.
- Displaying data —
{{ }}and{!! !!}. - Blade directives —
@if,@foreach, and the loop variable. - Components — the
<x-…>syntax, props, and slots.
Anything in those pages that is not in the syntax reference needs a running application, and will not compile here.