diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml
index 8ad97dea..2f662f22 100644
--- a/.github/workflows/integration-test.yml
+++ b/.github/workflows/integration-test.yml
@@ -40,6 +40,17 @@ jobs:
         uses: actions/setup-node@v2
         with:
           node-version: 20.x
+          
+      - name: Ubuntu AppArmor fix
+        if: ${{ matrix.os == 'ubuntu-latest' }}
+        # Ubuntu >= 23 has AppArmor enabled by default, which breaks Puppeteer.
+        # See https://github.com/puppeteer/puppeteer/issues/12818 "No usable sandbox!"
+        # this is taken from the solution used in Puppeteer's own CI: https://github.com/puppeteer/puppeteer/pull/13196
+        # The alternative is to pin Ubuntu 22 or to use aa-exec to disable AppArmor for commands that need Puppeteer.
+        # This is also suggested by Chromium https://chromium.googlesource.com/chromium/src/+/main/docs/security/apparmor-userns-restrictions.md
+        run: |
+          echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns
+        shell: bash
       # TODO: Remove when possible (https://github.com/actions/setup-node/issues/515)
       - name: Windows Node fix
         if: ${{ matrix.os == 'windows-latest' }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index fc9b4715..300e4981 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -38,6 +38,16 @@ jobs:
         uses: actions/setup-node@v2
         with:
           node-version: 20.x
+      - name: Ubuntu AppArmor fix
+        if: ${{ matrix.os == 'ubuntu-latest' }}
+        # Ubuntu >= 23 has AppArmor enabled by default, which breaks Puppeteer.
+        # See https://github.com/puppeteer/puppeteer/issues/12818 "No usable sandbox!"
+        # this is taken from the solution used in Puppeteer's own CI: https://github.com/puppeteer/puppeteer/pull/13196
+        # The alternative is to pin Ubuntu 22 or to use aa-exec to disable AppArmor for commands that need Puppeteer.
+        # This is also suggested by Chromium https://chromium.googlesource.com/chromium/src/+/main/docs/security/apparmor-userns-restrictions.md
+        run: |
+          echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns
+        shell: bash
       # TODO: Remove when possible (https://github.com/actions/setup-node/issues/515)
       - name: Windows Node fix
         if: ${{ matrix.os == 'windows-latest' }}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d6b9c37e..b4d8f413 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,11 @@
 
 ## Unreleased
 
+* Added support for Astro 5.
+* Added support for `astro:env` and public environment variables in Astro components.
+* Added support for the `getImage` function in Astro components.
+* Added fallbacks for the `astro:actions`, `astro:i18n`, `astro middleware`, and `astro:transitions` virtual modules.
+
 ## v3.12.0 (October 28, 2024)
 
 * Added a `--disable-bindings` flag to the `@bookshop/generate` command.
diff --git a/javascript-modules/engines/astro-engine/lib/builder.js b/javascript-modules/engines/astro-engine/lib/builder.js
index f696db34..3c92f5c5 100644
--- a/javascript-modules/engines/astro-engine/lib/builder.js
+++ b/javascript-modules/engines/astro-engine/lib/builder.js
@@ -13,6 +13,15 @@ const { transform: bookshopTransform } = AstroPluginVite({
   __removeClientDirectives: true,
 });
 
+const getEnvironmentDefines = () => {
+  return Object.entries(process.env ?? {}).reduce((acc, [key, value]) => {
+    if(key.startsWith("PUBLIC_")){
+      acc[`import.meta.env.${key}`] = JSON.stringify(value);
+    }
+    return acc;
+  }, {})
+}
+
 export const buildPlugins = [
   sassPlugin({
     filter: /\.module\.(s[ac]ss|css)$/,
@@ -83,7 +92,13 @@ export const buildPlugins = [
 
       build.onResolve({ filter: /^astro:.*$/ }, async (args) => {
         const type = args.path.replace("astro:", "");
-        if (type !== "content" && type !== "assets") {
+        if (type === "env/client" || type === "env"){
+          return { path: 'env', namespace: 'virtual' };
+        }
+        if (type === "transitions/client" || type === "transitions"){
+          return { path: join(dir, "modules", "transitions.js").replace("file:", "") };
+        }
+        if (!["content", "assets", "i18n", "actions", "middleware"].includes(type)) {
           console.error(
             `Error: The 'astro:${type}' module is not supported inside Bookshop components.`
           );
@@ -118,6 +133,35 @@ export const buildPlugins = [
         }
       });
 
+      build.onLoad({ filter: /^env$/, namespace: 'virtual' }, async (args) => {
+        let contents = "";
+        Object.entries(astroConfig?.env?.schema ?? {}).forEach(([key, schema]) => {
+          if(schema.context !== "client" || schema.access !== "public"){
+            return;
+          }
+
+          try{
+            switch(schema.type){
+              case "boolean":
+                contents += `export const ${key} = ${!!process.env[key]};\n`
+                break;
+              case "number":
+                contents += `export const ${key} = ${Number(process.env[key])};\n`
+                break;
+              default:
+                contents += `export const ${key} = ${JSON.stringify(process.env[key] ?? "")};\n`
+            }
+          } catch(e){
+            //Error intentionally ignored
+          }
+        });
+        contents += "export const getSecret = () => console.warn(\"[Bookshop] getSecret is not supported in Bookshop. Please use an editing fallback instead.\");"
+        return {
+          contents,
+          loader: "js",
+        };
+      });
+
       build.onLoad({ filter: /\.astro$/, namespace: "style" }, async (args) => {
         let text = await fs.promises.readFile(args.path, "utf8");
         let transformed = await transform(text, {
@@ -155,9 +199,9 @@ export const buildPlugins = [
           loader: "ts",
           target: "esnext",
           sourcemap: false,
-          define: { ENV_BOOKSHOP_LIVE: "true" },
+          define: { ...getEnvironmentDefines(), ENV_BOOKSHOP_LIVE: "true"},
         });
-        let result = await bookshopTransform(
+        let result = bookshopTransform(
           jsResult.code,
           args.path.replace(process.cwd(), "")
         );
@@ -189,10 +233,10 @@ export const buildPlugins = [
           jsxFragment: "__React.Fragment",
           target: "esnext",
           sourcemap: false,
-          define: { ENV_BOOKSHOP_LIVE: "true" },
+          define: { ...getEnvironmentDefines(), ENV_BOOKSHOP_LIVE: "true" },
         });
 
-        let result = await bookshopTransform(
+        let result = bookshopTransform(
           jsResult.code,
           args.path.replace(process.cwd(), "")
         );
@@ -218,26 +262,16 @@ export const buildPlugins = [
           loader: "ts",
           target: "esnext",
           sourcemap: false,
-          define: { ENV_BOOKSHOP_LIVE: "true" },
+          define: { ...getEnvironmentDefines(), ENV_BOOKSHOP_LIVE: "true" },
         });
 
-        let result = await bookshopTransform(
-          jsResult.code,
-          args.path.replace(process.cwd(), "")
-        );
-
-        if (!result) {
-          console.warn("Bookshop transform failed:", args.path);
-          result = jsResult;
-        }
-
         let importTransform = (await resolveConfig({}, "build")).plugins.find(
           ({ name }) => name === "vite:import-glob"
         ).transform;
-        let viteResult = await importTransform(result.code, args.path);
+        let viteResult = await importTransform(jsResult.code, args.path);
 
         return {
-          contents: viteResult?.code ?? result.code,
+          contents: viteResult?.code ?? jsResult.code,
           loader: "js",
         };
       });
diff --git a/javascript-modules/engines/astro-engine/lib/engine.js b/javascript-modules/engines/astro-engine/lib/engine.js
index 852abe70..91b7ca72 100644
--- a/javascript-modules/engines/astro-engine/lib/engine.js
+++ b/javascript-modules/engines/astro-engine/lib/engine.js
@@ -35,7 +35,7 @@ export class Engine {
             this.reactRoots.push({Component, props});
             return { html: `<div data-react-root=${this.reactRoots.length-1}></div>` };
           }
-  
+
           const reactNode = await Component(props);
           return { html: renderToStaticMarkup(reactNode) };
         },
@@ -118,12 +118,30 @@ export class Engine {
 
   async renderAstroComponent(target, key, props, globals) {
     const component = this.files?.[key];
+
+    let encryptionKey;
+    try{
+      encryptionKey = window.crypto.subtle.generateKey(
+        {
+          name: "AES-GCM",
+          length: 256,
+        },
+        true,
+        ["encrypt", "decrypt"],
+      )
+    } catch(err){
+      console.warn("[Bookshop] Could not generate a key for Astro component. This may cause issues with Astro components that use server-islands")
+    }
+
     const SSRResult = {
       styles: new Set(),
       scripts: new Set(),
       links: new Set(),
       propagation: new Map(),
       propagators: new Map(),
+      serverIslandNameMap: { get: () => "Bookshop" },
+      key: encryptionKey,
+      base: "/",
       extraHead: [],
       componentMetadata: new Map(),
       renderers: this.renderers,
@@ -166,6 +184,9 @@ export class Engine {
       flushSync(() => root.render(reactNode));
     });
     this.reactRoots = [];
+    target.querySelectorAll("link, [data-island-id]").forEach((node) => {
+      node.remove();
+    });
   }
 
   async eval(str, props = [{}]) {
diff --git a/javascript-modules/engines/astro-engine/lib/modules/actions.js b/javascript-modules/engines/astro-engine/lib/modules/actions.js
new file mode 100644
index 00000000..f89ea33a
--- /dev/null
+++ b/javascript-modules/engines/astro-engine/lib/modules/actions.js
@@ -0,0 +1,51 @@
+export const actions = new Proxy({}, {
+  get() {
+    console.warn("[Bookshop] actions is not supported in Bookshop. Please use an editing fallback instead.");
+    return () => {};
+  }
+});
+
+export const defineAction = () => {
+  console.warn("[Bookshop] defineAction is not supported in Bookshop. Please use an editing fallback instead.");
+  return {
+    handler: () => {},
+    input: null
+  };
+};
+
+export const isInputError = (error) => {
+  console.warn("[Bookshop] isInputError is not supported in Bookshop. Please use an editing fallback instead.");
+  return false;
+};
+
+export const isActionError = (error) => {
+  console.warn("[Bookshop] isActionError is not supported in Bookshop. Please use an editing fallback instead.");
+  return false;
+};
+
+export class ActionError extends Error {
+  constructor(code, message) {
+    super(message);
+    console.warn("[Bookshop] ActionError is not supported in Bookshop. Please use an editing fallback instead.");
+    this.code = code;
+  }
+}
+
+export const getActionContext = (context) => {
+  console.warn("[Bookshop] getActionContext is not supported in Bookshop. Please use an editing fallback instead.");
+  return {
+    action: undefined,
+    setActionResult: () => {},
+    serializeActionResult: () => ({})
+  };
+};
+
+export const deserializeActionResult = (result) => {
+  console.warn("[Bookshop] deserializeActionResult is not supported in Bookshop. Please use an editing fallback instead.");
+  return {};
+};
+
+export const getActionPath = (action) => {
+  console.warn("[Bookshop] getActionPath is not supported in Bookshop. Please use an editing fallback instead.");
+  return '';
+};
diff --git a/javascript-modules/engines/astro-engine/lib/modules/assets.js b/javascript-modules/engines/astro-engine/lib/modules/assets.js
index 12fce2e0..3d8c24b5 100644
--- a/javascript-modules/engines/astro-engine/lib/modules/assets.js
+++ b/javascript-modules/engines/astro-engine/lib/modules/assets.js
@@ -3,3 +3,30 @@ import PictureInternal from './picture.astro';
 
 export const Image = ImageInternal;
 export const Picture = PictureInternal;
+
+export const getImage = async (options) => {
+  const resolvedSrc =
+    typeof options.src === "object" && "then" in options.src
+      ? (await options.src).default ?? (await options.src)
+      : options.src;
+  return {
+    rawOptions: {
+      src: {
+        src: resolvedSrc,
+      },
+    },
+    options: {
+      src: {
+        src: resolvedSrc,
+      },
+    },
+    src: resolvedSrc,
+    srcSet: { values: [] },
+    attributes: { }
+  }
+}
+
+export const inferRemoteSize = async () => {
+  console.warn("[Bookshop] inferRemoteSize is not supported in Bookshop. Please use an editing fallback instead.");
+  return {};
+}
diff --git a/javascript-modules/engines/astro-engine/lib/modules/client-router.astro b/javascript-modules/engines/astro-engine/lib/modules/client-router.astro
new file mode 100644
index 00000000..bbb35286
--- /dev/null
+++ b/javascript-modules/engines/astro-engine/lib/modules/client-router.astro
@@ -0,0 +1,3 @@
+---
+console.warn("[Bookshop] view transitions are not supported in Bookshop. Please use an editing fallback instead.");
+---
diff --git a/javascript-modules/engines/astro-engine/lib/modules/content.js b/javascript-modules/engines/astro-engine/lib/modules/content.js
index 7a8053fe..4a15f0be 100644
--- a/javascript-modules/engines/astro-engine/lib/modules/content.js
+++ b/javascript-modules/engines/astro-engine/lib/modules/content.js
@@ -43,3 +43,8 @@ export const getEntries = (entries) => {
 export const getEntryBySlug = (collection, slug) => {
   return getEntry({ collection, slug });
 };
+
+export const render = async () => ({ Content: () => "Content is not available when live editing", headings: [], remarkPluginFrontmatter: {} });
+
+export const defineCollection = () => console.warn("[Bookshop] defineCollection is not supported in Bookshop. Make sure you're not importing your config in a component file by mistake.");
+export const reference = () => console.warn("[Bookshop] reference is not supported in Bookshop. Make sure you're not importing your config in a component file by mistake.");
diff --git a/javascript-modules/engines/astro-engine/lib/modules/i18n.js b/javascript-modules/engines/astro-engine/lib/modules/i18n.js
new file mode 100644
index 00000000..bf168ae6
--- /dev/null
+++ b/javascript-modules/engines/astro-engine/lib/modules/i18n.js
@@ -0,0 +1,54 @@
+export const getRelativeLocaleUrl = (locale, path, options) => {
+  console.warn("[Bookshop] i18n routing is not supported in Bookshop. Please use an editing fallback instead.");
+  return '';
+};
+
+export const getAbsoluteLocaleUrl = (locale, path, options) => {
+  console.warn("[Bookshop] i18n routing is not supported in Bookshop. Please use an editing fallback instead.");
+  return '';
+};
+
+export const getRelativeLocaleUrlList = (path, options) => {
+  console.warn("[Bookshop] i18n routing is not supported in Bookshop. Please use an editing fallback instead.");
+  return [];
+};
+
+export const getAbsoluteLocaleUrlList = (path, options) => {
+  console.warn("[Bookshop] i18n routing is not supported in Bookshop. Please use an editing fallback instead.");
+  return [];
+};
+
+export const getPathByLocale = (locale) => {
+  console.warn("[Bookshop] i18n routing is not supported in Bookshop. Please use an editing fallback instead.");
+  return '';
+};
+
+export const getLocaleByPath = (path) => {
+  console.warn("[Bookshop] i18n routing is not supported in Bookshop. Please use an editing fallback instead.");
+  return '';
+};
+
+export const redirectToDefaultLocale = (context, statusCode) => {
+  console.warn("[Bookshop] i18n routing is not supported in Bookshop. Please use an editing fallback instead.");
+  return Promise.resolve(new Response());
+};
+
+export const redirectToFallback = (context, response) => {
+  console.warn("[Bookshop] i18n routing is not supported in Bookshop. Please use an editing fallback instead.");
+  return Promise.resolve(new Response());
+};
+
+export const notFound = (context, response) => {
+  console.warn("[Bookshop] i18n routing is not supported in Bookshop. Please use an editing fallback instead.");
+  return Promise.resolve(new Response());
+};
+
+export const middleware = (options) => {
+  console.warn("[Bookshop] i18n routing is not supported in Bookshop. Please use an editing fallback instead.");
+  return () => {};
+};
+
+export const requestHasLocale = (context) => {
+  console.warn("[Bookshop] i18n routing is not supported in Bookshop. Please use an editing fallback instead.");
+  return false;
+};
diff --git a/javascript-modules/engines/astro-engine/lib/modules/middleware.js b/javascript-modules/engines/astro-engine/lib/modules/middleware.js
new file mode 100644
index 00000000..d62405b1
--- /dev/null
+++ b/javascript-modules/engines/astro-engine/lib/modules/middleware.js
@@ -0,0 +1,19 @@
+export const sequence = (...handlers) => {
+  console.warn("[Bookshop] middleware is not supported in Bookshop. Please use an editing fallback instead.");
+  return () => {};
+};
+
+export const defineMiddleware = (fn) => {
+  console.warn("[Bookshop] middleware is not supported in Bookshop. Please use an editing fallback instead.");
+  return () => {};
+};
+
+export const createContext = (context) => {
+  console.warn("[Bookshop] middleware is not supported in Bookshop. Please use an editing fallback instead.");
+  return {};
+};
+
+export const trySerializeLocals = (value) => {
+  console.warn("[Bookshop] middleware is not supported in Bookshop. Please use an editing fallback instead.");
+  return '';
+};
diff --git a/javascript-modules/engines/astro-engine/lib/modules/transitions.js b/javascript-modules/engines/astro-engine/lib/modules/transitions.js
new file mode 100644
index 00000000..0ca07b26
--- /dev/null
+++ b/javascript-modules/engines/astro-engine/lib/modules/transitions.js
@@ -0,0 +1,45 @@
+import ClientRouterInternal from './client-router.astro';
+
+export const ClientRouter = ClientRouterInternal;
+
+export const fade = (opts) => {
+  console.warn("[Bookshop] view transitions are not supported in Bookshop. Please use an editing fallback instead.");
+  return {};
+};
+
+export const slide = (opts) => {
+  console.warn("[Bookshop] view transitions are not supported in Bookshop. Please use an editing fallback instead.");
+  return {};
+};
+
+export const navigate = (href, options) => {
+  console.warn("[Bookshop] view transitions are not supported in Bookshop. Please use an editing fallback instead.");
+};
+
+export const supportsViewTransitions = false;
+
+export const transitionEnabledOnThisPage = false;
+
+export const getFallback = () => {
+  console.warn("[Bookshop] view transitions are not supported in Bookshop. Please use an editing fallback instead.");
+  return 'none';
+};
+
+export const swapFunctions = {
+  deselectScripts: () => {
+    console.warn("[Bookshop] view transitions are not supported in Bookshop. Please use an editing fallback instead.");
+  },
+  swapRootAttributes: () => {
+    console.warn("[Bookshop] view transitions are not supported in Bookshop. Please use an editing fallback instead.");
+  },
+  swapHeadElements: () => {
+    console.warn("[Bookshop] view transitions are not supported in Bookshop. Please use an editing fallback instead.");
+  },
+  saveFocus: () => {
+    console.warn("[Bookshop] view transitions are not supported in Bookshop. Please use an editing fallback instead.");
+    return () => {};
+  },
+  swapBodyElement: () => {
+    console.warn("[Bookshop] view transitions are not supported in Bookshop. Please use an editing fallback instead.");
+  }
+};
diff --git a/javascript-modules/engines/astro-engine/package.json b/javascript-modules/engines/astro-engine/package.json
index 76512425..886debd8 100644
--- a/javascript-modules/engines/astro-engine/package.json
+++ b/javascript-modules/engines/astro-engine/package.json
@@ -41,7 +41,7 @@
     "vite": "^4.2.1"
   },
   "peerDependencies": {
-    "astro": "^2.0.0 || ^3.0.0 || ^4.0.0",
+    "astro": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0",
     "react": "^17.0.2 || ^18.0.0",
     "react-dom": "^17.0.2 || ^18.0.0"
   },
diff --git a/javascript-modules/yarn.lock b/javascript-modules/yarn.lock
index 98e37b5c..f21a88db 100644
--- a/javascript-modules/yarn.lock
+++ b/javascript-modules/yarn.lock
@@ -575,7 +575,7 @@ __metadata:
     postcss-modules: ^6.0.0
     vite: ^4.2.1
   peerDependencies:
-    astro: ^2.0.0 || ^3.0.0 || ^4.0.0
+    astro: ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0
     react: ^17.0.2 || ^18.0.0
     react-dom: ^17.0.2 || ^18.0.0
   languageName: unknown