Edge Functions are now generally available! Learn some of the new ways you can cache responses and handle errors. Read our blog

Case Studies

Contenda builds with Netlify and Astro to share gated content previews

Netlify and Contenda

Case Studies

Contenda builds with Netlify and Astro to share gated content previews

Netlify and Contenda

Contenda is an AI platform for scaling your technical content marketing. Their platform and API takes in video content, and outputs blog posts, tutorials, and more.

When you work with your content on the Contenda platform, it's all authenticated and gated to your account only. Because they're a small team, finding a solution that could enable public sharing of drafts without major API changes was key. The key features to implement were:

  • The ability to see a blog draft
  • No need for passing access tokens to users we don't know to see a generated blog
  • Public URLs that share the content as read-only
  • No backend API changes

Ultimately, the team built a solution with Netlify On-demand Builders, Edge Functions, and Astro!

And thus, Share Pear was born!

Before we go further, the Contenda team open sourced the repository for this feature, lovingly called Share Pear.

But let's talk about how it's built! The overall architecture and user flow is boiled down into a few steps as seen in this diagram here:

Authenticated platform points to a user, which points to a web browser on a laptop, which points to share pear

Distributed Persistent Rendering and Netlify's On-demand Builders

There’s a concept called Distributed Persistent Rendering that allows you to create web pages in an interesting way. With this concept (similar to Incremental Static Regeneration or Deferred Static Generation, if you've seen it in the frontend world), what happens is:

  • You ping a server to create the page
  • Once that page is created, it just statically exists for whatever cache length we specify

The team chose to follow this approach because server-side rendered pages are vulnerable to DDoSing and other issues that could make one page take another down, and they don't want people to share their posts so widely that the ability to share is slow for other users at any point. Plus, it's cost-saving, because once the page is statically built, we don't have to query a server again!

The functions that create the new pages are called On-demand Builders (ODB), or builder functions. We can query that builder function with the blog content, and it will generate a page based on that content!

Astro and markdown rendering

The framework Astro is newer on the scene of web frameworks, but it is a powerful one. Out of the box, it's designed for speed and can use any of your favorite UI libraries with very minimal configuration.

In this project's case, what was appealing was that Astro natively supports rendering markdown as HTML, and Netlify On-demand Builders, right out of the box!

The configuration for setting up builders was a one-line change in the configuration file, thanks to the @astrojs/netlify integration:

// astro.config.mjs
import { defineConfig } from "astro/config";
import netlify from "@astrojs/netlify/functions";

export default defineConfig({
output: "server",
adapter: netlify({
builders: true // this is it

And for rendering markdown, in an Astro page component, you import their Markdown module in the frontmatter, and use the provided Markdown component:

// [slug].astro

import { Markdown } from "astro-remote";
// ...

// ...
sanitize={{ allowComments: true }}

// ...
{% raw %}

Gluing them all together with Edge Functions

On the authenticated platform, at a high level, this is what the behavior does when passing markdown to the sharing platform:

.then((md) => {
callOnDemandBuilder(md, randomKey())
// this creates a public-facing URL that
// anyone can access at share.contenda.co/randomKey,
// which has the md content rendered as HTML

On the Share Pear side, there is a dynamic route that looks for a markdown key in the headers of the API request with which to populate the blog.

Now to reiterate: when you first hit a URL in a website that uses Distributed Persistent Rendering, it generates that page, and then that page exists forever (...until the cache expires). So, you could go to share.contenda.co/blog/example and it'll be there, but the page won't be populated with real content unless you make a proper API request based on what it expects.

The Astro page parses the markdown header like so!

let markdown = Astro.request.headers.get("markdown");

Though you can typically set custom headers with a Netlify redirect proxy, or directly in Astro (I wrote a blog post about the different ways to set headers with Netlify and Astro) in this particular case, we can't, because our website isn't "truly" static. It is later, after the pages are generated, but before that, it's pinging a function to create the page. In our particular case, we needed to intercept the request before the page was made.

To handle this, bring in Netlify's Edge Functions! An edge function is kind of like a serverless function, but it can modify a request and response at the edge. It'll act as a middleware to add the custom headers to our requests coming in from the platform.

Here's a high level look at how the edge function implementation looks:

// /netlify/edge-functions/headers.js

export default async (request, context) => {
const response = await context.next();

// We need to include this OPTIONS request to handle CORS
// from requests inside the browser, because preflight
if (request.method === "OPTIONS") {
return new Response("ok", {
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "Content-Type, markdown",

// This line allows requests from anywhere, but on the live site
// it is restricted to Contenda's domains
response.headers.set("Access-Control-Allow-Origin", "*");

// This allows our custom markdown header that Astro consumes
"Content-Type, markdown"

// This sets a max-age for the cache of 30 days
response.headers.set("Cache-Control", "public, max-age=2592000, immutable");

return response;

Now you can successfully share gated content previews!

Now, when a user wants to share a blog from Contenda with their team, they can create a public URL (that expires eventually, by design), get their feedback, and publish!

The fact that we were able to put this together without having to include any backend API work was really exciting for us, and it was a really cool bundle of technologies to work with! Netlify made it so much simpler to build, debug, preview, test, and deploy, and we're so happy with the results.

  • Cassidy Williams, CTO at Contenda, Advisor at Netlify

Once again, the Share Pear repository is open source if you would like to look more at the implementation.

If you would like to try the workflow out for yourself, you can sign up for an early access account on Contenda, or build your own version by deploying to Netlify today, for free.

We can't wait to see what you make!

Keep reading

Recent posts

Book cover with the title Deliver web project 10 times faster with Jamstack enterprise

Deliver web projects 10× faster

Get the whitepaper