Skip to content

Astro Guide

Astro is a edge friendly rendering framework commonly deployed with Altitude. Astro promotes a powerful combination of an client-side island architecture that efficiently tree-shakes your chosen UI framework.


Get started

You can find full documentation on Astro’s website.

A common way to get started is to run:

Terminal window
npm create astro@latest

Adapter

Altitude requires the use of Astro’s cloudflare adapter to deploy.

You can install this adapter with this command:

Terminal window
npx astro add cloudflare

Helpful Plugins

Tailwind (Optional)
Terminal window
npx astro add tailwind
GraphQL (Optional)

A Rollup plugin which Converts .gql/.graphql(s) files to ES6 modules.

Terminal window
npm i @rollup/plugin-graphql
astro.config.js
export default defineConfig({
//...
vite: {
plugins: [graphql()],
},
//...
});

Integration

The Astro Integration package facilitates the efficient management of multi-tenancy and single-tenancy setups for commerce sites. The integration offers an enhanced layer of middleware abstraction, removing tech debt of managing common patterns and functions required to build and run sites into a single easy to use package.

Endpoints

When requesting our commerce, blog or tracking APIs in Astro.

Commerce API Endpoint

By utilising Astro Endpoints you can create an API endpoint to act as the proxy for request to GraphQL APIs.

Here is an example of a Commerce API (Horizon) focused GraphQL proxy. This proxy also ensures that required headers are passed to Horizon and that cookies set by Horizon are also set in the browser.

pages/api/horizon.js
import axios from "axios";
export async function POST({ request, locals }) {
const { tenantConfig } = locals;
const opaqueCookieDomain = "yourdomain.com";
const horizonEndpoint = "https://horizon-api.www.allsole.com";
let response;
let cookieHeaders;
const runtime = locals.runtime;
if (import.meta.env.DEV) {
const data = await request.json();
response = await axios({
method: "post",
url: `${horizonEndpoint}/graphql`,
data,
headers: {
cookie: request.headers.get("cookie"),
"user-agent": request.headers.get("user-agent"),
},
});
cookieHeaders = response.headers.get("set-cookie");
response = new Response(JSON.stringify(response.data), response);
} else {
const clientIp = request.headers.get("fastly-client-ip");
let newRequest = request.clone();
const init = {
headers: {
...Object.fromEntries(newRequest.headers),
...{ "X-Trusted-Client-Ip": clientIp },
...{
"X-Trust-Client-Ip-Key":
runtime.env.HORIZON_KEY_TO_TRUST_PROVIDED_CLIENT_IP,
},
},
method: newRequest.method,
body: newRequest.body,
};
response = await fetch(`${horizonEndpoint}/graphql`, init);
cookieHeaders = response.headers.getSetCookie();
response = new Response(response.body, response);
}
if (response.headers.get("set-cookie")) {
let domainReplacement = opaqueCookieDomain
? `domain=${opaqueCookieDomain}`
: "";
response.headers.delete("set-cookie");
for (const header of cookieHeaders) {
if (header.indexOf("domain") === -1 && header.indexOf("Domain") === -1) {
response.headers.append(
"set-cookie",
`${header}; ${domainReplacement}`
);
} else {
response.headers.append(
"set-cookie",
header.replace(/domain=[^;]+/gi, domainReplacement)
);
}
}
}
return response;
}

Once this is implemented you can send your client-side GraphQL queries via the /api/horizon/ URL in the browser.

Dynamic Robots Endpoint

Here is an example of how to create an endpoint that dynamically creates a robots.txt file. This can be useful when developing a multi-tenancy codebase where robots.txt files change by domain/tenant.

pages/robots.txt.js
import getRobots from "../../local/getRobots";
export async function GET({ locals }) {
return new Response(getRobots(locals.tenantKey));
}

Optimisations

Disable Streaming

Astro has a streaming behaviour which doesn’t work well with Cloudflare runtimes (and by proxy Altitude) a significant latency overhead as a result. We suggest disabling streaming by using a suspense pattern. This pattern when used in your layout file will force Astro to wait for the entire HTML response of its contents before responding.

Create a suspense component:

Suspense.astro
---
let html = ''
if (Astro.slots.has('default')) {
html = await Astro.slots.render('default')
}
---
<Fragment set:html={html} />

This can be used to wrap child components using a <slot />:

Layout.astro
---
import Suspense from '@components/Suspense.astro'
---
<Suspense>
<slot />
</Suspense>

Set HTTP Cache Headers

HTTP cache stores web content to reduce server load, decrease loading times, and enhance overall web browsing efficiency for users.

Within Astro you can do this by setting the header on the response.

Astro.response.headers.set("Cache-Control", `max-age=0; s-maxage=${cacheTTL}`);

Routing

Internationalisation (i18N)

For single tenant commerce websites you will be able to utilise Astro’s built in build time i18N methods to define your localised routes. Refer to the guide below for how to set this up.

For multi-tenancy platforms, as localisation varys per tenant and tenancy is often calculated at runtime. Achieving internationalisation requires an alternative approach.