Fully customizable bun server for Astro



This content originally appeared on DEV Community and was authored by Vlad

I want to use bun with astro, but there’re outdated adapters. In this article I’ll show you how easy you can setup SSR of astro project with bun with full control on your server.

Custom adapter

Why don’t we just build our own adapter? Some might think that it’s a difficult task, but I’ll show you that it’s very simple and straightforward.

Firstly have a look at docs.

Let’s make “adapter.ts”

import type { AstroAdapter, AstroIntegration } from "astro";
import path from "path";

export function getAdapter(...args: unknown[]): AstroAdapter {
  return {
    args,
    // from Astro we only need manifest file and that's it.
    exports: ["manifest"],
    // Custom name for our adapter
    name: "bun-custom-adapter",
    // Here's the name of the file with server entrypoint
    // See the details below
    serverEntrypoint: path.join(import.meta.dirname, "bun-adapter.js"),
    // Here you can set supported astro features
    supportedAstroFeatures: {
      envGetSecret: "unsupported",
      hybridOutput: "unsupported",
      i18nDomains: "unsupported",
      serverOutput: "stable",
      sharpImageService: "stable",
      staticOutput: "stable",
    },
  };
}

export function astroBunAdapter(): AstroIntegration {
  return {
    name: "bun-custom-adapter",
    hooks: {
      "astro:config:done": (params) => {
        params.setAdapter(
          getAdapter({
            assets: params.config.build.assets,
            client: params.config.build.client?.toString(),
            host: params.config.server.host,
            port: params.config.server.port,
            server: params.config.build.server?.toString(),
          }),
        );
      },
    },
  };
}

Now we need to create bun-adapter.ts. The only thing it will export is manifest with static files:

import type { SSRManifest } from "astro";

// All we need from astro-adapter is ssr manifest
// Adapter is ready!
export function createExports(manifest: SSRManifest): {
  manifest: SSRManifest;
} {
  return { manifest };
}

That’s it, you’re done! But wait, what’s next? How to work with this?

Server setup

I want to have a full control on my server, that’s why I’ll use hono with bun (but you can use any server framework that you like – even bare bun).

import { App } from "astro/app";
import Bun from "bun";
import { Hono } from "hono";
import { serveStatic } from "hono/bun";

// Import our built manifest
import { manifest } from "./dist/server/entry.mjs";

// create apps
const app = new Hono();
const astroApp = new App(manifest);

// Serve static files from _astro folder
app.get(
  "/_astro/*",
  serveStatic({
    precompressed: true,
    root: "./dist/client",
    onFound(_, context) {
      // Here you can customize headers and do whatever you want
      // I just added caching headers for static files
      context.res.headers.set("Cache-Control", "public, max-age=31536000, immutable");
    },
  }),
);

// Here we serve files from /public folder (they're just copied)
app.get("/:filename{.+\\.(png|ico|txt)}", serveStatic({ root: "./dist/client" }));

// Here we render astro app
// If you use API routes, you may also want to allow other methods
app.on(["GET"], "*", (context) => {
  const routeData = astroApp.match(context.req.raw);

  if (!routeData) return new Response("Not Found", { status: 404 });

  return astroApp.render(context.request.raw, {
    addCookieHeader: true,
    routeData,
    // you can also pass client address here from
    // request headers if you need it
  });
});

// run our server
const server = Bun.serve({
  hostname: YOUR_HOST,
  port: YOUR_PORT,
  fetch: app.fetch,
});

const exit = () => server.stop();

process.once("SIGINT", exit);
process.once("SIGTERM", exit);
process.once("exit", exit);

That’s it! With this minimum code you get full control over your server. You can easily add anything you want. Of course, if you want to have everything working out of the box, I recommend you using node adapter with express.


This content originally appeared on DEV Community and was authored by Vlad