LogoFreestyle

Building an AI App Builder

How to make an AI App Builder on Freestyle

Freestyle is the cloud for AI App Builders. We provide all the tools you need to build, preview, deploy and manage the code your AI writes. This guide shows you how to build with Freestyle, how we think about our tools, and how to get the most out of them.

Architecture

Managing the Code with Git

The life blood of every AI App is the code that powers it. To make the most of it, we provide a Git API for creating and managing repositories.

We recommend using Git to manage the code your AI writes because:

  • Version Control: You get a free version history, along with reverting, branching, and merging.
  • Collaboration: You can have multiple agents working on different prototypes and merging them together.
  • Debugability: Your AI's process is tracked over time, and you can clone any of its prototypes locally anytime.
  • Portability: You can sync your repo's to Github, or give your users the ability to clone them locally and work alongside your AI.
  • Integration: By working through Git, we can provide live previews based on the current versions of the code, along with automatic deployments — the same way you get with the Continuous Integration and Continuous Deployment (CI/CD) tools you're used to.

Developing with Dev Servers

As your AI writes code, it needs some form of development server to run it on. This development server has jobs like linting the code, running it (or running tests), and serving it to the browser to preview it for your users. We provide a Dev Servers API for creating and managing these servers.

The Dev Server API is not a generic container API, it is specialized for lifecycle management of JavaScript/TypeScript apps. Instead of making you manage the lifecycle, it runs through Git and is synced to the latest version of a given git repository. The Dev Server automatically keeps your dev server healthy, routes traffic to a given url to provide a live preview, manages npm installs, and shuts down when the code is not being used. It's also controllable via both an HTTP API and an MCP service that lets you or your agents control it.

This api is not the most powerful container API in the market — this is by design. We believe AI App Builders should be able to focus on building apps, not managing container lifecycle and health. The Dev Server API is designed to take this off your plate. It is designed to be extensible, but if you are looking for a generic container API, this is not it.

Deploying Previews

Once your AI has written code, it needs to be deployed to a live server. Dev Server's are slow, expensive to run and non-scalable. We provide a Deployments API for deploying your code to our production serverless infrastructure.

This API is extremely customizable, you can build that app yourself, have it detect the framework and build for you, or configure your own build, then you can add any number of domains, environment variables, advanced security features like network permissions and more. It also manages the related DNS, routing and TLS Certificates without you needing to do anything.

Any Freestyle user can deploy their code to any *.style.dev domain, and can also deploy to their own custom domains.

AI App Builders should have their own *.yourapp.com domain that you use to deploy initial production versions of your app to. You can set this up with the following guide and point the domain at us following the DNS Instructions. This should not be a subdomain of any domains you use for security reasons.

The simplest way to deploy if you're using Git is to set the deployment source to your git repository itself. This way, we'll automatically pull the latest version of your code, build it and deploy it. You can do this yourself, or you can set up a Git Trigger to automatically deploy your code whenever you push to a given branch. This is the same way you would do it with any other CI/CD tool.

Production Deployments

Once your users see the live preview of the app, they'll want to deploy it to their own domains. This is where the domains API comes in. This API allows you to deploy your app to any custom domain.

Domains on Freestyle are completely decoupled from deployments, allowing you to attach/detach them at will.

In order to do this, you should create an API that takes your users through the same verification process for managing your own domain you went through to set up your own domain. Then tell them to point their domain at us following the DNS Instructions. Once they do that, you can deploy their app to their domain using the Deployments API.

All together

All these together make up an AI App Builder built on Freestyle. Our goal is to take the pain of infrastructure off of building an AI App Builder to let you focus on everything else. If this architecture is compelling to you, check out our example repository here.

Guide

This guide will take you through the process of building an AI App Builder on Freestyle. While we will use an opinionated tech stack, we've intentionally segmented the guide into different sections so you can take the parts you like and leave the rest. The goal is to give you a starting point for building your own AI App Builder.

Tech Stack

  • TypeScript - This AI App Builder will be built 100% in Full Stack TypeScript.
  • NextJS
  • Vercel AI SDK - We will use the Vercel AI SDK to handle our Chat UI and message streaming.
  • Freestyle - We will use Freestyle to manage our Git Repositories, Dev Servers and Deployments.
  • Anthropic - We will use Anthropic's Claude to power our AI. You can use any LLM you want, but we like Claude.

Setup

Setting up the Project

npx create-next-app@latest --ts --tailwind --yes  freestyle-ai-app-builder
cd freestyle-ai-app-builder
npm install freestyle-sandboxes ai @ai-sdk/react @ai-sdk/anthropic @modelcontextprotocol/sdk

Environment

  • Create a .env file in the root of your project with the following contents:
FREESTYLE_API_KEY=your_freestyle_api_key
ANTHROPIC_API_KEY=your_anthropic_api_key

Mechanics of the Chat

The following sections are the pieces that we will later put together to make the AI App Builder chat.

Setting up a Git Repository

In order to start developing your AI App Builder, you'll need a Git repo to build the app in.

You'll want one Git repo for every chat in your AI App Builder, this way the chat has a place to manage its code. However you store your chats, you should include a repoId on the chat object to refer to the code linked to it.

setup.ts
import { FreestyleSandboxes } from "freestyle-sandboxes";
 
const freestyle = new FreestyleSandboxes({
  apiKey: process.env.FREESTYLE_API_KEY,
});
 
const { repoId } = await freestyle.createGitRepository({
  name: "Test Repository",
  // This will make it easy for us to clone the repo during testing.
  public: true,
  source: {
    url: "https://github.com/freestyle-sh/freestyle-next", // can be any public git repo, or any git repo you own on Freestyle
    type: "git",
  },
});

We have a series of prebuild templates for you to base your AI Apps on

We recommend forking one of them to make your own custom one. Your template should include everything custom you might want your AI to use. For example, if you want it to process payments, you should install the SDK into your template repo and create the setup files.

Running a the Dev Server

Dev Servers exist as short lived previews and dev environments to work with your Freestyle Git Repositories. To use one, provision it like:

dev.ts
import { FreestyleSandboxes } from "freestyle-sandboxes";
 
export const freestyle = new FreestyleSandboxes({
  apiKey: process.env.FREESTYLE_API_KEY!,
});
 
const {
  ephemeralUrl, // The URL of the preview of the dev server
  mcpEphemeralUrl, // The URL of the mcp service for the dev server
} = await freestyle.requestDevServer({
  repoId: repoId, // the repoId from the previous step
});

The ephemeralUrl is a URL that you can use to preview the dev server. The mcpEphemeralUrl is a URL that you can use to connect to the mcp service for the dev server. While we also offer a Rest API to control the dev server, we recommend using the mcp service to start, and using the Rest API when you have specific use cases that the MCP can't handle.

Integrating with AI

In order to integrate with AI, we'll use the Vercel AI SDK and Anthropic Claude to create a simple ReAct agent that works with the Dev Server for the repository.

Setup the AI + MCP

In order to connect the AI to the Dev Server, we'll first instantiate the model and connect the MCP client to the dev server.

mcp.ts
import { anthropic } from "@ai-sdk/anthropic";
import { experimental_createMCPClient as createMCPClient } from "ai";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
 
export const ANTHROPIC_MODEL = anthropic("claude-3-7-sonnet-20250219");
 
const devServerMcp = await createMCPClient({
  transport: new StreamableHTTPClientTransport(new URL(mcpUrl)), // mcpUrl is the url of the dev server
});
 
const tools = await devServerMcp.getTools();

Run the AI

Then, we can use the streamText function to run the AI. This function takes a model, a prompt, and a set of tools to use. We set steps to 100 to give the AI lots of time to iterate, and toolCallStreaming to true to get the AI to call the tools as it goes. This is important, as it lets you see as the AI is writing a file, instead of waiting for it to be done.

run.ts
import { streamText } from "@ai-sdk/react";
 
streamText({
  model: ANTHROPIC_MODEL, // the model from the previous step
  maxSteps: 100,
  tools: tools, // the tools from the previous step
  toolCallStreaming: true,
  messages: [
    {
      role: "system",
      content: `
      You are an AI App Builder. The existing app is in the /template directory. Please edit the app how the user wants and commit the changes incrementally.
      `,
    },
    {
      role: "user",
      content: `Make me Tic Tac Toe`, // Put your prompt here
    },
  ],
});

Now, if you visit the url of the dev server, you should see the changes to your app live as the AI makes them.

Putting the pieces together

Now that we have all the pieces in place, we can create the two functions that manage the chat:

  1. createChat - This function will create a new git repository for the chat and request a dev server for it.
  2. respond - This function will take the user messages and run the AI with the dev server to build the app.
lib/create-chat.ts
import { FreestyleSandboxes } from "freestyle-sandboxes";
 
const freestyle = new FreestyleSandboxes({
  apiKey: process.env.FREESTYLE_API_KEY!,
});
 
export async function createChat() {
  const { repoId } = await freestyle.createGitRepository({
    name: "Test Repository",
    public: true,
    source: {
      url: "https://github.com/freestyle-sh/freestyle-next", // replace this with your own template repo
      type: "git",
    },
  });
 
  const { ephemeralUrl, mcpEphemeralUrl } = await freestyle.requestDevServer({
    repoId: repoId,
  });
 
  return {
    repoId,
    ephemeralUrl,
  };
}

We'll put the respond function at /api/chat/route.ts, because this is the default route the Vercel AI SDK uses to get responses in chats built on it.

app/api/chat/route.ts
export async function POST(req: Request) {
  const repoId = req.headers.get("Repo-Id");
  const { messages } = await req.json();
 
  const { ephemeralUrl, mcpEphemeralUrl } = await freestyle.requestDevServer({
    repoId: repoId,
  });
 
  const devServerMcp = await createMCPClient({
    transport: new StreamableHTTPClientTransport(new URL(mcpEphemeralUrl)),
  });
  const tools = await devServerMcp.getTools();
 
  const response = await streamText({
    model: ANTHROPIC_MODEL,
    maxSteps: 100,
    tools: tools,
    toolCallStreaming: true,
    messages: [
      {
        role: "system",
        content: `
        You are an AI App Builder. The existing app is in the /template directory. Please edit the app how the user wants and commit the changes incrementally.
        `,
      },
      ...messages,
    ],
  });
 
  result.consumeStream(); // keep going even if the client disconnects
 
  return result.toDataStreamResponse();
}

Making the Homepage UI

To start, we can make a homepage with a Create App button. This will create a new chat and redirect the user to it.

app/page.tsx
import { useState } from "react";
import { useRouter } from "next/navigation";
 
import { createChat } from "../lib/create-chat";
 
export default function Home() {
  const router = useRouter();
  const [loading, setLoading] = useState(false);
 
  async function handleClick() {
    setLoading(true);
    const { repoId } = await createChat();
    router.push(`/app/${repoId}`);
  }
 
  return (
    <div className="flex flex-col items-center justify-center h-screen">
      <h1 className="text-4xl font-bold">AI App Builder</h1>
      <button
        className="px-4 py-2 mt-4 text-white bg-blue-500 rounded"
        onClick={handleClick}
        disabled={loading}
      >
        {loading ? "Creating..." : "Create App"}
      </button>
    </div>
  );
}

Making the Chat UI

Then, for each chat, we can create a page that shows the chat UI and the dev server preview.

First we need to create a server action to allow our frontend to request a dev server and get it's status. We'll pass this server action to the FreestyleDevServer component, which will continuously ensure the dev server is healthy.

lib/server-actions.ts
"use server";
 
export async function requestDevServer({ repoId }: { repoId: string }) {
  return await freestyle.requestDevServer({ repoId });
}
app/apps/[repoId]/page.tsx
import { useChat } from "@ai-sdk/react";
import { FreestyleDevServer } from "freestyle-sandboxes/react/dev-server";
import { requestDevServer } from "@/lib/server-actions";
 
export default async function Chat(
  { params }: Promise<{ repoId: string }>;
) {
  const { repoId } = await params; // the repoId from the URL
 
  const { messages, input, handleInputChange, handleSubmit } = useChat({
    headers: {
      "Repo-Id": id, // the repoId from the URL, so the API knows which chat to use
    }
  });
 
  return (
    <div className="grid grid-cols-4 h-full">
      <div className="flex flex-col w-full h-full max-w-md py-24 mx-auto stretch">
        // This is where the chat UI will be
        {messages.map((message) => (
          <div key={message.id} className="whitespace-pre-wrap">
            {message.role === "user" ? "User: " : "AI: "}
            {message.parts.map((part, i) => {
              switch (part.type) {
                case "text":
                  return <div key={`${message.id}-${i}`}>{part.text}</div>;
                default:
                  return (
                    <div key={`${message.id}-${i}`}>{JSON.stringify(part)}</div>
                  );
              }
            })}
          </div>
        ))}
        <form onSubmit={handleSubmit}>
          <input
            className="fixed dark:bg-zinc-900 bottom-0 w-full max-w-md p-2 mb-8 border border-zinc-300 dark:border-zinc-800 rounded shadow-xl"
            value={input}
            placeholder="Build"
            onChange={handleInputChange}
          />
        </form>
      </div>
      <div className="col-span-3">
        // This is where the dev server will be previewed
        <FreestyleDevServer actions={{ requestDevServer }} repoId={repoId} />;
      </div>
    </div>
  );
}

The builder part of the AI App Builder is now complete 🎉. You now have an AI connected to your dev server, that is able to iterate on the app, show a live preview to the user, and commit its code as it goes.

Adding Deploys

Now that the AI can build apps, your users will want to deploy and share them. This is where the Deployments API comes in.

In order to make deploys easy, we can deploy your code by referencing the git repository. This will automatically get the latest version of your code, build it, and deploy it to the domain of your choice.

Using this technique also helps you with debugging. As you grow, some of your deploys will fail. This is natural, AI writes lots of bad code. By deploying through git, you're able to know the exact version of the code that was built to be deployed, and see what got messed up.

deploy.ts
"use server";
import { FreestyleSandboxes } from "freestyle-sandboxes";
 
const freestyle = new FreestyleSandboxes({
  apiKey: process.env.FREESTYLE_API_KEY,
});
 
export async function deployAction(
  repoId: string, // the repoId for your app
  domains: string[] // the domains you want to deploy to
) {
  const { domains } = await freestyle.deployWeb(
    {
      kind: "git",
      repoUrl: `https://git.freestyle.sh/${repoId}`,
    },
    {
      domains,
      envVars: {
        // ... any environment variables you want to set
      },
    }
  );
}

Now, your AI App Builder has deploys 🎉🎉🎉, stick a button on the frontend that calls this server action and you're good to go.

Adding Domains

For custom domains, you'll want to follow this guide to set up a verification process for your users to verify their domains. This allows you to deploy to their domains.

Closing

This guide shows you the simplest possible way to get going with an AI App Builder. It should serve as a good starting point and reference for making your own. The code in it is intentionally over-simplified, and will need to be modified to make something production ready.

For a complete template, check out the adorable repository. This repository not only comes with everything we went over here, but also chunking, a better Chat UI, and a bunch of other features that make it a great starting point for your own AI App Builder.