Set up a custom domain with Bento and Cloudflare Workers

Jay FrancoJay Franco
6 min read

If you're a creative professional, Bento is a great way to showcase your portfolio and best work. It's easy to set up, free and can be customized to fit many needs. The team is amazing and I've seen so many great designs made on it.

Its only downside, as you may know, is that you can't natively set up a custom domain.

The good news is that it's very easy to set up with your custom domain for free.


Section 1: Prerequisites

Before diving into the setup process, you need:

  • Bento page

  • Cloudflare account

  • Registered domain name (Can be with any registrar)

  • Optional but useful: A basic understanding of JavaScript/TypeScript


Section 2: Initial setup

In our Terminal, let's create a basic Cloudflare Worker:

npm create cloudflare@latest

Now let's just follow the instructions:

  • In which directory do you want to create your application? Your choice

  • What type of application do you want to create? "Hello World" Worker

  • Do you want to use TypeScript? --> No (For this tutorial I went with JavaScript for the sake of simplicity, but I recommend TypeScript if you're comfortable).

  • Do you want to use git for version control? Yes

  • Do you want to deploy your application? Yes (it'll ask you to login if you're not logged in already).


Section 3: Setting our environment variables

We'll need to set up our environment variable for development and production.

For production, we need to edit wrangler.toml:

# Below the current configuration
[vars]
BENTO_USERNAME = "jayfranco" # replace with the name of your Bento page
BASE_URL = "https://jayfran.co" # the custom domain you'll use

For development, we need to create a .dev.vars file at the root of our project:

BENTO_USERNAME="jayfranco" # replace with the name of your Bento page
BASE_URL="http://127.0.0.1:8787" # one of the default addresses

Section 4: Creating our worker

Our worker will be made of 3 functions all written in our index.js file

  • An event listener

      addEventListener('fetch', event => {
        // When a fetch event occurs, responds with the result of the handleRequest function
        event.respondWith(handleRequest(event.request));
      });
    
  • A function to parse responses according to their content type

      async function parseResponseByContentType(response, contentType) {
        // If there's no content type, the response is returned as text
        if (!contentType) return await response.text();
    
        // Depending on the content type, different actions are taken
        switch (true) {
          case contentType.includes('application/json'):
            // If the content type is JSON, the response is returned as a JSON string
            return JSON.stringify(await response.json());
          case contentType.includes('text/html'):
            // If the content type is HTML, the response is transformed using HTMLRewriter
            const transformedResponse = new HTMLRewriter()
              .on('body', {
                element(element) {
                  // Custom CSS and JS can be added into the body of the HTML
                  element.append(
                    `
                      <style>
                        // Custom CSS you can add to
                        // modify the styling of your page
                      </style>
                      `,
                    { html: true },
                  );
                  element.append(
                    `
                      <script>
                        // Custom JS you can add to
                        // modify something on your page
                      </script>
                      `,
                    { html: true },
                  );
                },
              })
              .transform(response);
            // The transformed response is returned as text
            return await transformedResponse.text();
    
          case contentType.includes('font'):
            // If the content type is a font, the response is returned as an ArrayBuffer
            return await response.arrayBuffer();
    
          case contentType.includes('image'):
            // If the content type is an image, the response is returned as an ArrayBuffer
            return await response.arrayBuffer()
    
          default:
            // If the content type is anything else, the response is returned as text
            return await response.text();
        }
      }
    
  • A function to handle all requests

async function handleRequest(request) {
  // Extracts the path from the request URL
  const path = new URL(request.url).pathname;
  // By default, the URL is set to 'https://bento.me'
  // appended with the path
  let url = 'https://bento.me' + path;

  // If the path includes 'v1', the URL is changed to
  // 'https://api.bento.me' appended with the path
  if (path.includes('v1')) {
    url = 'https://api.bento.me' + path;
  }

  // If the URL is 'https://bento.me/' the URL is changed to
  // 'https://bento.me/' appended with the BENTO_USERNAME
  if (url === 'https://bento.me/') {
    url = 'https://bento.me/' + BENTO_USERNAME;
  }

  // Basic headers for the fetch request are defined
  let headers = {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'GET,HEAD,POST,OPTIONS',
  };

  // The URL is fetched with the defined headers
  const response = await fetch(url, { headers });

  // The content type is extracted from the response headers
  const contentType = response.headers.get('content-type');

  // The response is parsed based on its content type
  let results = await parseResponseByContentType(response, contentType);

  // If the results are not an ArrayBuffer
  // all calls to the bento API are replaced with our BASE_URL
  // This is a workaround to fix CORS errors that occur otherwise
  if (!(results instanceof ArrayBuffer)) {
    results = results.replaceAll('https://api.bento.me', BASE_URL);
  }

  // The content type is added to the headers
  headers['content-type'] = contentType;

  // A new response is returned with the results and headers
  return new Response(results, { headers });
}

Let's now run our development server and check that everything is working correctly.

npm run dev

Now that everything is working great, let's deploy it to production.

npm run deploy

Section 5: Connecting our custom domain

Last but not least, we need to connect our custom domain.

Once logged into your Cloudflare Dashboard you need to "Add a site" to your Cloudflare account:

  • a) Choose a plan: Select the plan that you want, the Free plan should work fine for most use cases.

  • b) Review DNS records: If you have DNS records, it is your time to add them. For example, in my case, I had MX records to set up.

  • c) Change your nameservers: This step may vary depending on your registrar, but you will want to set your DNS to Custom Nameserver and enter the 2 nameservers given to you by Cloudflare. After that, you can go through with the basic setup given to you by Cloudflare and you just have to wait for everything to propagate. You'll receive an email when it's ready.

  • d) Set your custom domain: Once everything is propagated and you see your DNS as Active on Cloudflare:

    • Select Workers & Pages and in Overview, select your Worker.

    • Go to Triggers > Custom Domains > Add Custom Domain.

    • Enter the domain you want to configure for your Worker.

    • Select Add Custom Domain.

You can now test your domain and it should redirect directly to your Bento page ๐ŸŽ‰


Conclusion

In conclusion, setting up a custom domain with Bento and Cloudflare Workers offers developers a great combination of flexibility, power, and convenience.

If you have any questions/issues, don't hesitate to DM me on X and I'll assist you with pleasure!

0
Subscribe to my newsletter

Read articles from Jay Franco directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Jay Franco
Jay Franco