惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

The Register - Security
The Register - Security
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
MyScale Blog
MyScale Blog
V
Visual Studio Blog
云风的 BLOG
云风的 BLOG
aimingoo的专栏
aimingoo的专栏
C
Check Point Blog
J
Java Code Geeks
大猫的无限游戏
大猫的无限游戏
L
LangChain Blog
Vercel News
Vercel News
阮一峰的网络日志
阮一峰的网络日志
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
S
Security @ Cisco Blogs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
人人都是产品经理
人人都是产品经理
H
Hacker News: Front Page
L
Lohrmann on Cybersecurity
T
Troy Hunt's Blog
T
Threat Research - Cisco Blogs
A
About on SuperTechFans
T
Threatpost
AWS News Blog
AWS News Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
T
Tor Project blog
Google Online Security Blog
Google Online Security Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Tenable Blog
W
WeLiveSecurity
博客园 - 叶小钗
K
Kaspersky official blog
Y
Y Combinator Blog
T
The Blog of Author Tim Ferriss
Hugging Face - Blog
Hugging Face - Blog
M
MIT News - Artificial intelligence
Hacker News - Newest:
Hacker News - Newest: "LLM"
Engineering at Meta
Engineering at Meta
有赞技术团队
有赞技术团队
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Secure Thoughts
小众软件
小众软件
D
Docker
爱范儿
爱范儿
C
Cyber Attacks, Cyber Crime and Cyber Security
N
News and Events Feed by Topic
S
Schneier on Security
博客园 - 三生石上(FineUI控件)
D
DataBreaches.Net

Deno

Deno 2.8 | Deno Claw Patrol: an open-source security firewall for agents | Deno Fresh 2.3: Zero JS by default, View Transitions, and Temporal support | Deno Deno 2.7: Temporal API, Windows ARM, and npm overrides | Deno Build a dinosaur runner game with Deno, pt. 6 | Deno Build a dinosaur runner game with Deno, pt. 5 | Deno Deno Deploy is Generally Available | Deno Introducing Deno Sandbox | Deno Build a dinosaur runner game with Deno, pt. 4 | Deno Build a dinosaur runner game with Deno, pt. 3 | Deno Build a dinosaur runner game with Deno, pt. 2 | Deno React / Next.js Denial-of-Service Vulnerability: Deno Deploy users protected | Deno Deno 2.6: dx is the new npx | Deno Build a dinosaur runner game with Deno, pt. 1 | Deno React Server Functions / Next.js Vulnerability: Deno Deploy users protected | Deno My highlights from the new Deno Deploy | Deno Deno's Other Open Source Projects | Deno How Deno protects against npm exploits | Deno Help Us Raise $200k to Free JavaScript from Oracle | Deno Deno 2.5: Permissions in the config file | Deno Fresh 2.0 Graduates to Beta, Adds Vite Support | Deno Deno 2.4: deno bundle is back | Deno JavaScript™ Trademark Update | Deno What's coming to JavaScript | Deno A brief history of JavaScript | Deno Reports of Deno's Demise Have Been Greatly Exaggerated | Deno An Update on Fresh | Deno How Plaid migrated 100 services to a new database platform 5x faster with Deno | Deno Deno 2.3: Improved deno compile, local npm packages, and more | Deno Add JSR packages with pnpm and Yarn | Deno Zero-config Debugging with Deno and OpenTelemetry | Deno Exploring Art with TypeScript, Jupyter, Polars, and Observable Plot | Deno Deno v Oracle Update 3: Fighting the JavaScript Trademark | Deno Build a custom RAG AI agent in TypeScript and Jupyter | Deno How to get deep traces in your Node.js backend with OTel and Deno | Deno toranoana.deno #20 登録受付中(2025年3月14日) | Deno Node just added TypeScript support. What does that mean for Deno? | Deno The Dino 🦕, the Llama 🦙, and the Whale 🐋 | Deno Publish a lint rule, get a prize | Deno Deno 2.2: OpenTelemetry, Lint Plugins, node:sqlite | Deno If you're not using npm specifiers, you're doing it wrong | Deno How Deno's documentation is evolving | Deno Oracle justified its JavaScript trademark with Node.js—now it wants that ignored | Deno Introducing the JSR open governance board | Deno Intro to Wasm in Deno | Deno Announcing OpenAI on JSR | Deno Deno in 2024 | Deno Goodbye WinterCG, welcome WinterTC | Deno Build a SolidJS app with Deno | Deno Run your Next.js SSR app on Deno Deploy | Deno Solve Advent of Code 2024 with Deno and Win Prizes! | Deno Deno v. Oracle: Canceling the JavaScript Trademark | Deno Deno 2.1: Wasm Imports and other enhancements | Deno Build a Typesafe API with tRPC and Deno | Deno Self-contained Executable Programs with Deno Compile | Deno Build a Database App with Drizzle ORM and Deno | Deno Introducing your new JavaScript package manager: Deno | Deno Announcing Growthbook on JSR | Deno Build an Astro site with Deno | Deno How to convert CommonJS to ESM | Deno Announcing Deno 2 | Deno The Final Touches: What’s New In v2.0.0-rc.10 | Deno Announcing Stable V8 Bindings for Rust | Deno Deno 2.0 Release Candidate | Deno Secure, efficient private npm registries with Cloudsmith and Deno | Deno Painting the Plane as We Fly It: Designing JSR | Deno Introducing Web Cache API support on Deno Deploy | Deno Deno 1.46: The Last 1.x Release | Deno Protect your cloud spend with new Deno Deploy spend limits | Deno What we got wrong about HTTP imports | Deno Benchmarking AWS Lambda Cold Starts Across JavaScript Runtimes | Deno Announcing Supabase on JSR | Deno Deno 1.45: Workspace and Monorepo Support | Deno Introducing KV Backup for Deno Subhosting | Deno A Gentle Intro to TypeScript | Deno Announcing Hono on JSR | Deno How We Made the Deno Language Server Ten Times Faster | Deno How the Guardian uses Deno to audit accessibility and performance across their 2.7 million articles | Deno Introducing More Flexible Domain Association for Deno Subhosting | Deno The stabilization process of the Standard Library has begun | Deno Deno 1.44: Private npm registries, improved Node.js compat, and performance boosts | Deno How we built a secure, performant, multi-tenant cloud platform to run untrusted code | Deno The Deno Standard Library is now available on JSR | Deno How to document your JavaScript package | Deno Your Low Code Solution Needs an Escape Hatch | Deno Deno 1.43: Improved Language Server performance | Deno How Slack used Deno to save months of engineering effort in launching their new platform | Deno JSR Is Not Another Package Manager | Deno Announcing the Hookdeck SDK on JSR | Deno Announcing the Neon Serverless Driver on JSR | Deno An intro to TSConfig for JavaScript Developers | Deno How we built JSR | Deno How Netlify used Deno Subhosting to build a successful edge functions product | Deno Introducing Simpler Project Creation in Deno Deploy | Deno Deno 1.42: Better dependency management with JSR | Deno Introducing deployctl, the command line interface for Deno Deploy | Deno Introducing JSR - the JavaScript Registry | Deno Survey Results and Roadmap | Deno Deno 1.41: smaller deno compile binaries | Deno Webhooks suck, but here are alternatives | Deno
How to add Monaco to a Next.js app and securely run untrusted user code | Deno
Andy Jiang · 2024-02-29 · via Deno

While many turn to SaaS platforms for their flexibility over on-prem software, the real value unlocked by SaaS is allowing users to create their own custom workflows, such as to transform or enrich a data stream, or to build bespoke automation, without needing to maintain production infrastructure.

Many SaaS platforms already offer user-driven customizability — Slack enables building custom modular workflows, Salesforce allows creating workflows based off sales and marketing activities, and Twilio offers manipulation of voice calls through a serverless environment. And these platforms minimize friction to create these workflows by offering their users an in-browser IDE that deploys and runs code — removing the need to maintain another piece of production infrastructure.

However, adding a fully featured in-browser editor to an app can be challenging, not to mention building a platform for securely deploying and executing untrusted user code on the web. Allowing anyone to deploy and run code welcomes potentially nefarious users deploying code that tries to access other deployments and your own systems, as well as exploit your resources for bitcoining mining.

In this blog post, we’ll go over solving for these key challenges, and by the end, you’ll have a simple browser IDE that can deploy and run code in the cloud.

A demo of the browser IDE in this tutorial.

Follow the steps below or skip directly to the source code.

  • Integrate Monaco and Next.js
  • Deploy and execute untrusted code
  • What’s next

Integrate Monaco and Next.js

If you are bringing a Next.js app to this tutorial, great. If not, you can create a new one from scratch and follow along. Note that we’ll be using Next.js version 14 in this tutorial.

Let’s create the page that shows the in-browser editor. Under the folder app, we’ll create the folder ide and, within that, a page.js file.

First, we’ll add the directive and necessary imports. We’ll use @monaco-editor/react, which is a nifty React component for Monaco. We’ll also import useState and useEffect hooks to help manage state.

"use client";

import Editor from "@monaco-editor/react";
import { useEffect, useState } from "react";

Below that, we’ll define the component IDE:

export default function IDE() {
  const handleSubmit = async () => {};

  return (
    <div className="flex justify-center items-start pt-10 h-screen">
      <div className="w-full max-w-4xl p-4 border">
        <form action="#" onSubmit={handleSubmit}>
          <div className="">
            <label htmlFor="comment" className="sr-only">
              Add your code
            </label>
            <Editor
              height="50vh"
              defaultLanguage="javascript"
              defaultValue='Deno.serve(req => new Response("Hello!"));'
            />
          </div>
          <div className="flex justify-between pt-2">
            <div className="flex items-center space-x-5"></div>
            <div className="flex-shrink-0">
              <button
                type="submit"
                className="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white hover:bg-indigo-500"
              >
                Run
              </button>
            </div>
          </div>
        </form>
      </div>
    </div>
  );
}

Note we currently stub out the functions handleSubmit — we’ll fill those in later. Now, if you start your server with npm run dev and point your browser to localhost:3000/ide, you should see:

browser ide

Great — you have the Monaco editor working in your Next.js app! While you can write code, you can’t deploy or execute it. Let’s fix that.

Deploy and execute untrusted code

Deploying and executing third party code is a complex engineering problem that requires provisioning multiple sandboxes on demand, preventing deployments from accessing other deployments as well as your own systems, and routing request traffic properly to maintain performance for all users.

While you can build that yourself, for this tutorial we’ll use the Deno Deploy Subhosting API, which allows you to programmatically deploy and execute code in the cloud. It’s designed to run untrusted code securely — you don’t need to worry about a single deployment attempting to access the processes of another or of your internal systems.

This section will expand on the above with the following:

Grab Subhosting API tokens

Before you begin, you’ll need to have a Deno Deploy access token and an ID for the Deno Deploy organization you’re using for Subhosting.

Once you have them, create a .env file that looks like the following:

DEPLOY_ACCESS_TOKEN=ddp_xxxxxxxxxxxxxxxxxxxxxxxxxx
DEPLOY_ORG_ID=536xxxxx-1111-1111-1111-111111111111

Create API routes to deploy code

Let’s define three new API routes in our Next.js app:

  • app/api/createproject/route.ts
  • app/api/createdeployment/route.ts
  • app/api/getdeployment/route.ts

Next, our createproject route will contain logic to create a new project given a Subhosting org.

import Subhosting from "subhosting";

const subhosting = new Subhosting();

export async function GET() {
  const orgId = process.env["DEPLOY_ORG_ID"];
  const project = await subhosting.organizations.projects.create(orgId, {
    name: null,
  });
  return Response.json(project);
}

Note that we explicitly pass name: null , which tells Deno Deploy to generate a globally unique project name.

Our createdeployment route will contain logic to submit a request. To interface with the Subhosting API, we’ll use the Subhosting npm client library. Install it with npm install --save subhosting, then import and use it like below:

import { NextRequest } from "next/server";
import Subhosting from "subhosting";

const subhosting = new Subhosting();

export async function POST(req: NextRequest) {
  const data = await req.json();
  const code = data["code"];
  const projectId = data["project"];

  const res = await subhosting.projects.deployments.create(projectId, {
    entryPointUrl: "main.ts",
    assets: {
      "main.ts": {
        kind: "file",
        content: code,
        encoding: "utf-8",
      },
    },
    envVars: {},
  });
  return Response.json(res);
}

For more details on the Subhosting API, refer to our documentation.

Finally, our getdeployment route will retrieve the status of the deployment:

import { NextRequest } from "next/server";
import Subhosting from "subhosting";

const subhosting = new Subhosting();

export async function POST(req: NextRequest) {
  const data = await req.json();
  const deploymentId = data["id"];
  const deployment = await subhosting.deployments.get(deploymentId);
  return Response.json(deployment);
}

Since the API to create a deployment will return a response with the deployment status as pending, we’ll need to poll the deployment details endpoint until the status changes to failed or success.

Connect app/ide/page.js to newly created API routes

Once we have the API routes wired up, we’ll fill out the rest of the functionality and state management needed in our main IDE page.

In our app/ide/page.js:



export default function IDE() {
  const [project, setProject] = useState("");

  useEffect(() => {
    const createProject = async () => {
      try {
        const response = await fetch("/api/createproject", {
          method: "GET",
        });
                if (!response.ok) {
                    throw new Error(`Error: ${response.status}`);
                }
                const responseData = await response.json();
                setProject(responseData);
            } catch (error) {
                console.error("Failed to create project: ", error);
            }
        };

        
        createProject();
    }, []);

    
    const project_id = project["id"];

    
    const sleep = ms => new Promise(res => setTimeout(res, ms));

    
    const pollDeploymentStatus = async (deploymentId) => {
        let response;
        try {
            response = await fetch("/api/getdeployment", {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                },
                body: JSON.stringify({ id: deploymentId }),
            });
        } catch (error) {
            console.log(error);
        }
        return await response.json();
    };

    const handleSubmit = async (event) => {
        event.preventDefault();

        
        const codeText = event.target.querySelector(".monaco-scrollable-element").textContent;

        try {
            const response = await fetch("/api/createdeployment", {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                },
                body: JSON.stringify({ code: codeText, project: project_id }),
            });
            if (!response.ok) {
                throw new Error(`Error: ${response.status}`);
            }

            let responseData = await response.json();
            
      while (responseData["status"] === "pending") {
        await delay(3000);
        responseData = await pollDeploymentStatus(responseData["id"]);
      }
            
            
        } catch (error) {
            console.log(error);
        }
    };

    return (
        
    );
}

Here’s what’s going on with the new code:

  • Anytime the page refreshes or loads, we call createProject and grab its project_id.
  • When we click “run” to submit the code, we grab the code from the text input, and a deployment is created.
  • We poll the deployment endpoint with deployment_id until its deployment status is no longer pending.

The deployment is created successfully, however, there’s nothing to show for it. And the browser IDE that doesn’t show the deployed code might as well be a local text editor. So next, we’ll add the success deployment in an iframe.

Show and run the deployed code

We’ll grab the deployment URL, add it as an iframe, and show it when the status of the deployment is success.

Let’s update app/ide/page.js to include the following:

  • add an iframe in JSX
  • add logic to show iframe when deployment status is success and iframe doesn’t error
  • add logic for showing and updating a status message (”Deploying code…”)

Here’s the updated app/ide/page.js (code from previous versions omitted for simplicity and readability):



export default function IDE() {
  

  const [URL, setURL] = useState("");
  const [isLoading, setIsLoading] = useState(true);

  
  const updateStatus = (message) => {
    if (document.querySelector(".ide-message")) {
      document.querySelector(".ide-message").textContent = message;
    }
  };

  
  const handleSubmit = async (event) => {
    event.preventDefault();

    updateStatus("Deploying code...");

    const codeText =
      event.target.querySelector(".monaco-scrollable-element").textContent;
    try {
      const response = await fetch("/api/createdeployment", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ code: codeText, project: project_id }),
      });

      if (!response.ok) {
        throw new Error(`Error: ${response.status}`);
      }

      
      let responseData = await response.json();
      while (responseData["status"] === "pending") {
        await delay(3000);
        responseData = await pollDeploymentStatus(responseData["id"]);
      }

      
      if (responseData["status"] === "success") {
        setURL(`http://${responseData.domains[0]}`);
        updateStatus("Successfully deployed.");
      } else {
        updateStatus("Deployment failed.");
        throw new Error("Deployment failed");
      }
    } catch (error) {
      console.log(error);
    }
  };

  const handleLoad = () => {
    setIsLoading(false);
  };

  const handleError = () => {
    setIsLoading(true);
  };

  return (
    <div className="flex justify-center items-start pt-10 h-screen">
      <div className="w-full max-w-4xl p-4 border">
        {}
        <div className="mt-4">
          {}
          <p className="ide-message mb-4"></p>
          {isLoading && (
            <p className="text-center">Deployed code will run here.</p>
          )}
          <iframe
            src={URL} 
            title="Deployed Project"
            width="100%"
            height="300px"
            onLoad={handleLoad}
            onError={handleError}
            style={{ display: isLoading ? "none" : "block" }} 
          >
          </iframe>
        </div>
      </div>
    </div>
  );
}

Note we’ve added two new useState hooks:

  • setURL for persisting the deployment’s URL
  • setIsLoading for persisting the the loading state of the iframe.

Now, when the deployment succeeds, we setURL to the domain retrieved from responseData and we update the status to read “Successfully deployed”. The iframe with src set to the deployment URL will then appear beneath the text editor.

Here it is in action:

You can also edit and re-deploy the code:

Voila! Now you have integrated Monaco into a browser IDE with Next.js that can also deploy and run code securely.

What’s next?

For the sake of education and simplicity, we pared down this tutorial for integrating a Monaco editor in Next.js to create a browser IDE. However, there are still many features that we can add to improve the user experience:

  • Instead of creating a new project whenever the page is refreshed, the user can view, edit, and deploy existing projects
  • Expand Monaco editor to include simple filesystem for more advanced code editing
  • User authentication and data storage, so users can access, edit, and deploy code from previous sessions
  • Host the app so that anyone can access it on the internet

Finally, if you found this helpful, have more questions, or want us to expand on this guide, please let us know on Twitter or Discord.

🚨️ Want to build a browser IDE, but concerned about security around deploying and executing untrusted code? Check out Subhosting.