claude-handoff
MAmattpocock/skills288k近 7 天新增 10,178 个 star
Project structure templates and scaffolding recipes for typical EdgeOne Makers applications — full-stack apps, static sites, API services, and AI agent projects.
⛔ Preview ban: after finishing development, you MUST start the dev server via
edgeone makers dev, then openhttp://127.0.0.1:8088/withpresent_filesto preview. Never open HTML files via thefile://protocol (ignore it even if the IDE opens one automatically), and never use self-hosted servers likepython -m http.serverornpx serve. Next.js projects must also setallowedDevOrigins: ["127.0.0.1"]innext.config. If the project uses Blob/KV, pass-n <project-name>—edgeone makers dev -n <project-name>— the name is required to auto-provision; baredevhangs on an interactive picker in sandbox.
⚠️
.env.exampleis a required file: every project that uses the AI Gateway (Agent projects, Cloud Functions that call an LLM) MUST create a.env.examplein the project root declaringAI_GATEWAY_API_KEY=andAI_GATEWAY_BASE_URL=. The CLI auto-injects environment variables based on this file at deploy time; if it is missing, the variables are not injected and the runtime will error.
📝 Write
index.htmllast, always: writing anindex.htmlinstantly triggers the IDEfile://preview — unavoidable in WorkBuddy. Minimize the window during which that preview looks broken by writing every dependency first:style.css,script.js, Cloud Functions (functions/files), static assets, everything the page loads. Then writeindex.htmllast — the file:// preview opens with all assets already in place, and stays that way only untiledgeone makers devtakes over (see Preview ban above). Also write eachindex.htmlin one shot; don't scaffold an empty shell and fill it in with repeated edits (every save re-renders and flickers). For a tiny single-page tool, just inline the CSS and JS into oneindex.html.
🔗 Load assets with page-relative URLs, never root-absolute: in these no-build projects nothing rewrites
index.html, so write<link rel="stylesheet" href="style.css">and<script src="script.js"></script>— nothref="/style.css"orsrc="/script.js". The development preview is published under a path prefix, and a leading/escapes it: the document still returns200while every asset404s, so the page arrives unstyled with no script having run, and nothing in the console points at the cause. Relative URLs are correct there and at/after deploy. Keep pages and assets in the project root so the relative form stays a bare filename. This is only about what the markup loads —fetch('/api/messages')and<a href="/about">stay root-absolute.
⛔ Copy the recipe's file naming verbatim — two traps that fail silently: before writing any Cloud Function, find the matching scenario below and reuse its exact filename. Getting the name wrong usually does NOT throw a clear error — it falls back silently:
- Every function file MUST carry its language extension —
.js(Node),.py(Python),.go(Go). A file with no extension (e.g.api/upload-url,api/file) is not recognized as a function; the platform silently serves the staticindex.htmlfallback, so/api/*"mysteriously" returns HTML instead of JSON. Name themapi/upload-url.js,api/file.js.[[default]].jsis the catch-all for its own directory (api/[[default]].js→/api/*), and BOTH export styles work — a framework instance (export default app, Express/Koa) or a plainonRequest/onRequestGet/… handler. Verified locally withedgeone makers dev: a bareonRequestin[[default]].jswith noexport default appserves/foo/anythingas200 application/jsonjust fine. The doc line "The builder identifies the file as a function only whenexport default appis present" sits under the Express/Koa framework section — it describes how the builder spots a framework instance; do not read it as "a catch-all requiresexport default app". ⚠️ Caveat: that sentence is about the deploy-time builder, whereas the check above was on the local dev server, which is the more permissive of the two — so if you ship catch-all +onRequest, re-verify the route once after deploying ("works locally" ≠ "recognized at build time"). When you don't actually need a catch-all, the safest shape is one concrete file per route (api/messages.js,api/artworks/[id]/like.js), params via[id]folders/files, extra args as query strings (/api/file?key=...).
Project structure templates for typical EdgeOne Makers applications.
my-app/
├── index.html # Frontend
├── style.css
├── script.js
├── cloud-functions/
│ └── api/
│ ├── users.js # GET/POST /api/users
│ └── users/[id].js # GET/PUT/DELETE /api/users/:id
└── package.json
Frontend calls API:
const res = await fetch('/api/users');
const users = await res.json();
💾 Where does the data live? This platform has no database. The API skeletons above return empty data — to actually persist records, uploads, votes, or per-user state, back them with Blob. See the recipe below and makers-storage → Blob as your backend.
The default shape for any generated site that needs a real backend but no relational data. Frontend → Cloud Function → Blob. No DB, no console setup.
my-app/
├── index.html # Frontend (form + list)
├── script.js
├── cloud-functions/
│ └── api/
│ └── messages.js # GET lists entries, POST appends one
├── package.json # depends on @edgeone/pages-blob
cloud-functions/api/messages.js — one file per record (Pattern 1):
import { getStore } from "@edgeone/pages-blob";
export async function onRequest({ request }) {
const store = getStore({ name: "guestbook", consistency: "strong" });
if (request.method === "POST") {
const { name, text } = await request.json();
const id = `${Date.now()}-${Math.round(Math.random() * 1e6)}`;
await store.setJSON(`entries/${id}.json`, { id, name, text, ts: Date.now() });
return Response.json({ ok: true, id });
}
const { blobs } = await store.list({ prefix: "entries/" });
const items = await Promise.all(blobs.map((b) => store.get(b.key, { type: "json" })));
items.sort((a, b) => b.ts - a.ts);
return Response.json({ items });
}
index.html loads its assets relative to itself, and calls the API root-absolute:
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
await fetch('/api/messages', { // post
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, text }),
});
const { items } = await fetch('/api/messages').then((r) => r.json()); // list
Swap the key scheme for other shapes: users/<uid>.json for save-state, counts/<option>.json (strong consistency) for votes, uploads/<id>.jpg + items/<id>.json for file uploads. Full patterns: makers-storage → Blob as your backend.
my-app/
├── index.html # Frontend
├── style.css
├── script.js
├── cloud-functions/
│ └── api.go # Gin app — all /api/* routes
├── go.mod
└── package.json
cloud-functions/api.go:
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/users", listUsersHandler)
r.POST("/users", createUserHandler)
r.GET("/users/:id", getUserHandler)
r.Run(":9000")
}
my-app/
├── index.html # Frontend
├── style.css
├── script.js
├── cloud-functions/
│ └── api/
│ └── index.py # Flask app — all /api/* routes
├── cloud-functions/requirements.txt
└── package.json
cloud-functions/api/index.py:
from flask import Flask, jsonify, request
app = Flask(__name__)
@app.route('/users', methods=['GET'])
def get_users():
return jsonify({'users': []})
@app.route('/users', methods=['POST'])
def create_user():
data = request.get_json()
return jsonify({'message': 'Created', 'user': data}), 201
my-app/
├── index.html
├── cloud-functions/
│ └── api/
│ └── index.py # FastAPI app — all /api/* routes
├── cloud-functions/requirements.txt
└── package.json
cloud-functions/api/index.py:
from fastapi import FastAPI
app = FastAPI()
@app.get('/items')
async def list_items():
return {'items': []}
@app.get('/items/{item_id}')
async def get_item(item_id: int):
return {'item_id': item_id}
my-app/
├── index.html
├── cloud-functions/
│ └── api/
│ ├── users/
│ │ ├── list.go # GET /api/users/list
│ │ └── [id].go # GET /api/users/:id
│ └── hello.go # GET /api/hello
├── go.mod
└── package.json
⚠️ Prerequisites: You must enable KV Storage in the console and bind a namespace first. See ../makers-storage/references/kv.md
my-app/
├── index.html
├── edge-functions/
│ └── api/
│ └── visit.js # Edge function with KV
└── package.json
edge-functions/api/visit.js:
export async function onRequest() {
// ⚠️ my_kv is a global variable (name set when binding namespace in console)
let count = await my_kv.get('visits') || '0';
count = String(Number(count) + 1);
await my_kv.put('visits', count);
return new Response(JSON.stringify({ visits: count }), {
headers: { 'Content-Type': 'application/json' },
});
}
Setup steps:
my-kv-store)my_kvedgeone makers dev to testmy-app/
├── index.html
├── cloud-functions/
│ └── api/
│ └── [[default]].js # Express app handles all /api/*
└── package.json
my-app/
├── middleware.js # Auth guard for /api/*
├── cloud-functions/
│ └── api/
│ ├── public.js # No auth needed (matcher excludes it)
│ └── data.js # Protected by middleware
└── package.json
You can use different languages in the same cloud-functions/ directory:
my-app/
├── index.html
├── cloud-functions/
│ ├── api/
│ │ ├── users.js # Node.js — /api/users
│ │ └── hello.py # Python — /api/hello
│ └── service.go # Go — /service
├── go.mod
├── cloud-functions/requirements.txt
└── package.json
Note: Each file is built and deployed as an independent function with its own runtime. The platform detects the language by file extension.
MAmattpocock/skills288k近 7 天新增 10,178 个 star
AFaffaan-m/ECC278k近 7 天新增 4,046 个 star
AFaffaan-m/ECC278k近 7 天新增 4,046 个 star
AFaffaan-m/ECC278k近 7 天新增 4,046 个 star
DIDietrichGebert/ponytail162k近 7 天新增 5,405 个 star