Astro
Your client edits in the Studio. Astro builds the site.
The client’s editor publishes the spring menu. Half a minute later, your host starts one build with it. You read what each page needs in its frontmatter, with one client and a key that only reads.
the journal, in Astro
---
// src/pages/journal/index.astro
import { moteur } from '../../lib/moteur';
const articles = (await moteur
.channel('website')
.entries.list('article', { limit: 10, sort: '-publishedAt', resolveUrl: true })) as Article[];
---
<ul>
{articles.map(a => <li><a href={a.resolvedUrl}>{a.data.title.en}</a></li>)}
</ul>Setup
Five steps to a client’s site
Choose what the site reads
In Setup › API channels, the site channel is already there: pick the models and fields it shows. It reads published content only.
Make its key
A read-only key for that channel, limited to the client’s domains if you like. Put it in MOTEUR_KEY.
Add the client
npm install @moteurio/client, then one file that makes the client for the whole site.
Read in your pages
In frontmatter or getStaticPaths. Each page gets entries, pages, menus and addresses as JSON.
Rebuild on publish
Paste your host’s build hook in Setup › Webhooks. Vercel, Netlify and Cloudflare Pages all work.
One file
The client, made once
Every page imports it. The key stays in your build, never in the browser.
- The same file works with your own server: change baseURL
- Types for every model and block come from moteur types
src/lib/moteur.ts
// src/lib/moteur.ts
import { createMoteurPublicClient } from '@moteurio/client';
export const moteur = createMoteurPublicClient({
baseURL: 'https://api.moteur.io',
auth: { type: 'apiKey', apiKey: import.meta.env.MOTEUR_KEY, projectId: 'client-site' },
});Up close
What a client site needs
Turn a card over for the code.
Addresses
Every page, in every language
The Studio gives each page an address per language. getStaticPaths builds them all, and each knows its alternates for hreflang.
- /fr/a-propos and /en/about, one page
- Breadcrumbs and menus in the same language
src/pages/[...path].astro
--- // src/pages/[...path].astro: every page, in every language import { moteur } from '../lib/moteur'; export async function getStaticPaths() { const urls = (await moteur.site.urls()) as Address[]; return urls.map(u => ({ params: { path: u.url.replace(/^\/|\/$/g, '') || undefined }, props: u, })); } const { nodeId, entryId, locale, alternates } = Astro.props; ---Redirects
Old addresses keep working
When a live page moves, Moteur adds the redirect. Write them where your host reads them, before each build.
- 301s made for you, chains collapsed
- Loops refused before they’re saved
scripts/redirects.ts
// scripts/redirects.ts: run before astro build import { writeFile } from 'node:fs/promises'; import { moteur } from '../src/lib/moteur'; // Netlify and Cloudflare Pages read public/_redirects. const redirects = await moteur.site.redirects(); await writeFile( 'public/_redirects', redirects.map(r => `${r.from} ${r.to} ${r.status}`).join('\n'), );Images
Images in every size
Crops and widths come from the image’s address. imageAttributes writes the src, the srcset for the widths the server makes, the size and the alt text in the visitor’s language.
- Eight crop ratios and a focal point
- WebP or AVIF, never wider than the original
src/components/Cover.astro
--- import { imageAttributes } from '@moteurio/client'; const { cover, locale } = Astro.props; --- <img {...imageAttributes(cover, { ratio: '16:9', sizes: '(min-width: 60rem) 60rem, 100vw', locale })} />Forms
Forms in the client’s languages
Ask for a token when the form shows, then send it. Answers come back checked, with what to fix in the visitor’s language.
- The server checks every answer
- Spam goes to its own folder, quietly
src/components/Contact.astro
<script> import { createMoteurPublicClient } from '@moteurio/client'; const moteur = createMoteurPublicClient({ baseURL: 'https://api.moteur.io', auth: { type: 'apiKey', apiKey: import.meta.env.PUBLIC_MOTEUR_FORMS_KEY, projectId: 'client-site' }, }); const form = document.querySelector('form')!; const token = await moteur.forms.token('contact'); form.addEventListener('submit', async event => { event.preventDefault(); const answers = Object.fromEntries(new FormData(form)); const result = await moteur.forms.submit('contact', answers, { locale: 'fr', token }); // ok, or the answers to fix, each in French, or the captcha to answer again }); </script>
Previews
Previews of what isn’t live yet
The Studio opens the client’s site with a short-lived token in the address. A static page can’t read it, so add one route that renders on request, and make it the project’s preview address in Setup › Project. Everything else stays static.
- The token reads drafts of one entry or one channel, for half an hour
- Needs an Astro adapter for your host
src/pages/preview/journal/[id].astro
---
// Under the project's preview address, rendered on request
export const prerender = false;
import { createPreviewSession } from '@moteurio/client';
import { moteur } from '../../../lib/moteur';
// The Studio opens your site with ?preview=… on the address.
const { token } = createPreviewSession({ search: Astro.url.search });
const reader = token ? moteur.withPreview(token) : moteur;
const article = await reader.channel('website').entries.get('article', Astro.params.id!);
---Questions about Astro
An agency developer asksIs there an Astro starter?
MoteurComing. A starter, and an @moteurio/astro package that renders blocks for you, aren’t published yet. Until then, @moteurio/client and the code on this page are all you need.
An agency developer asksCan I use Astro’s content collections?
MoteurNot through a Moteur loader yet. Read in frontmatter or getStaticPaths, as above. Each answer is plain JSON.
An agency owner asksDoes the site rebuild for every save?
MoteurNo. Only when something goes live: an entry, a page, a menu, a redirect or a file. Moteur waits half a minute for the rest, then asks for one build.
Setup › Webhooks › Rebuild the site
{
"name": "Rebuild the client site",
"url": "https://api.netlify.com/build_hooks/…",
"kind": "deploy",
"batchSeconds": 30
}An agency developer asksCan the client’s site be fully static?
MoteurYes, all but one preview route. If the client doesn’t need to see drafts on the site itself, nothing has to render on request.
Build your next client site with Astro and Moteur.
Start for free, make a key for the site channel, and read your first entry.