mirror of
https://github.com/HeyPuter/puter.git
synced 2026-10-15 16:19:20 +00:00
fixes
This commit is contained in:
1 parent
815faaf07f
commit
74fed55958
10 files changed
+159
-304
No files matched your search
@@ -745,12 +745,6 @@ const examples = [
|
||||
slug: 'hosting-publish-a-website',
|
||||
source: '/playground/examples/hosting-publish-a-website.html',
|
||||
},
|
||||
{
|
||||
title: 'Release and roll back a site',
|
||||
description: 'Switch a website between folders to release or roll back a version with Puter.js hosting API. Run and modify this example directly in your browser.',
|
||||
slug: 'hosting-manage-websites-release',
|
||||
source: '/playground/examples/hosting-manage-websites-release.html',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -998,18 +992,6 @@ const examples = [
|
||||
slug: 'workers-exec',
|
||||
source: '/playground/examples/workers-exec.html',
|
||||
},
|
||||
{
|
||||
title: 'Build an API',
|
||||
description: 'Deploy a worker whose routes store notes in the calling user\'s own key-value store with user.puter, and call it with puter.workers.exec(). Run and modify this example directly in your browser.',
|
||||
slug: 'workers-build-an-api',
|
||||
source: '/playground/examples/workers-build-an-api.html',
|
||||
},
|
||||
{
|
||||
title: 'Update a worker in place',
|
||||
description: 'Ship new code to a deployed worker at the same URL by overwriting its source file with Puter.js workers API. Run and modify this example directly in your browser.',
|
||||
slug: 'workers-deploy-from-code-update',
|
||||
source: '/playground/examples/workers-deploy-from-code-update.html',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
<html>
|
||||
<body>
|
||||
<script src="https://js.puter.com/v2/"></script>
|
||||
<h3>Site dashboard</h3>
|
||||
<button id="release">Release version 2</button>
|
||||
<button id="rollback">Roll back to version 1</button>
|
||||
<button id="delete">Delete site</button>
|
||||
<script>
|
||||
const name = puter.randName();
|
||||
const v1 = `${name}-v1`;
|
||||
const v2 = `${name}-v2`;
|
||||
|
||||
const ready = (async () => {
|
||||
await puter.fs.mkdir(v1);
|
||||
await puter.fs.write(`${v1}/index.html`, '<h1>Version 1</h1>');
|
||||
await puter.fs.mkdir(v2);
|
||||
await puter.fs.write(`${v2}/index.html`, '<h1>Version 2</h1>');
|
||||
|
||||
const site = await puter.hosting.create(name, v1);
|
||||
const url = `https://${site.subdomain}.puter.site`;
|
||||
puter.print(`Serving version 1 at <a href="${url}" target="_blank">${url}</a><br>`);
|
||||
})();
|
||||
|
||||
async function serve (dir) {
|
||||
await ready;
|
||||
// Same name and URL, different folder.
|
||||
const site = await puter.hosting.update(name, dir);
|
||||
puter.print(`Now serving ${site.root_dir.path}<br>`);
|
||||
}
|
||||
|
||||
document.getElementById('release').addEventListener('click', () => serve(v2));
|
||||
document.getElementById('rollback').addEventListener('click', () => serve(v1));
|
||||
|
||||
document.getElementById('delete').addEventListener('click', async () => {
|
||||
await ready;
|
||||
await puter.hosting.delete(name);
|
||||
puter.print('Site deleted. Both folders are still in your account.<br>');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,62 +0,0 @@
|
||||
<html>
|
||||
<body>
|
||||
<script src="https://js.puter.com/v2/"></script>
|
||||
<h3>Notes</h3>
|
||||
<input id="text" placeholder="Write a note">
|
||||
<button id="save">Save note</button>
|
||||
<button id="list">Show my notes</button>
|
||||
<script>
|
||||
// Every note is stored in the calling user's own KV through user.puter,
|
||||
// so each user only ever sees their own notes.
|
||||
const workerCode = `
|
||||
router.post('/notes', async ({ request, user }) => {
|
||||
if (!user) return new Response('sign in required', { status: 401 });
|
||||
|
||||
const { text } = await request.json();
|
||||
if (typeof text !== 'string' || text.length === 0) {
|
||||
return new Response('text is required', { status: 400 });
|
||||
}
|
||||
|
||||
const note = { id: crypto.randomUUID(), text, at: Date.now() };
|
||||
await user.puter.kv.set('notes:' + note.id, note);
|
||||
return note;
|
||||
});
|
||||
|
||||
router.get('/notes', async ({ user }) => {
|
||||
if (!user) return new Response('sign in required', { status: 401 });
|
||||
|
||||
const rows = await user.puter.kv.list('notes:', true);
|
||||
return rows.map(row => row.value);
|
||||
});
|
||||
`;
|
||||
|
||||
let api;
|
||||
|
||||
async function deploy () {
|
||||
await puter.fs.write('notes-api.js', workerCode);
|
||||
const deployment = await puter.workers.create(puter.randName(), 'notes-api.js');
|
||||
puter.print(`Deployed to ${deployment.url}, waiting for it to go live...<br>`);
|
||||
// A new worker takes a few seconds to reach every edge server.
|
||||
await new Promise(resolve => setTimeout(resolve, 10000));
|
||||
return deployment.url;
|
||||
}
|
||||
|
||||
document.getElementById('save').addEventListener('click', async () => {
|
||||
api ??= deploy();
|
||||
// exec() sends the signed-in user, which is what gives the worker user.puter.
|
||||
const res = await puter.workers.exec(`${await api}/notes`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text: document.getElementById('text').value }),
|
||||
});
|
||||
puter.print(`${res.status}: ${await res.text()}<br>`);
|
||||
});
|
||||
|
||||
document.getElementById('list').addEventListener('click', async () => {
|
||||
api ??= deploy();
|
||||
const notes = await (await puter.workers.exec(`${await api}/notes`)).json();
|
||||
puter.print(`You have ${notes.length} note(s): ${notes.map(n => n.text).join(', ')}<br>`);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,41 +0,0 @@
|
||||
<html>
|
||||
<body>
|
||||
<script src="https://js.puter.com/v2/"></script>
|
||||
<h3>App builder: project API</h3>
|
||||
<input id="greeting" value="Welcome back">
|
||||
<button id="ship">Ship change</button>
|
||||
<button id="call">Call the worker</button>
|
||||
<script>
|
||||
const name = `project-${puter.randName()}`;
|
||||
// The route greets whoever calls it, using their own Puter account.
|
||||
const code = greeting => `
|
||||
router.get('/', async ({ user }) => {
|
||||
if (!user) return new Response('sign in required', { status: 401 });
|
||||
const { username } = await user.puter.getUser();
|
||||
return ${JSON.stringify(greeting)} + ', ' + username;
|
||||
});
|
||||
`;
|
||||
|
||||
const deployed = (async () => {
|
||||
await puter.fs.write(`${name}.js`, code('Hello'));
|
||||
const { url } = await puter.workers.create(name, `${name}.js`);
|
||||
puter.print(`Created ${url}, live within about 30 seconds<br>`);
|
||||
return url;
|
||||
})();
|
||||
|
||||
document.getElementById('ship').addEventListener('click', async () => {
|
||||
await deployed;
|
||||
const info = await puter.workers.get(name);
|
||||
|
||||
// Overwriting the source file redeploys at the same URL.
|
||||
await puter.fs.write(info.file_path, code(document.getElementById('greeting').value));
|
||||
puter.print(`Shipped. ${info.url} serves the new code within about 30 seconds.<br>`);
|
||||
});
|
||||
|
||||
document.getElementById('call').addEventListener('click', async () => {
|
||||
const res = await puter.workers.exec(await deployed);
|
||||
puter.print(`${res.status}: ${await res.text()}<br>`);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,121 @@
|
||||
---
|
||||
title: Add Website Publishing
|
||||
description: "Learn how to let users publish websites from your app with Puter.js."
|
||||
tags: [hosting, fs]
|
||||
order: 55
|
||||
---
|
||||
|
||||
Some apps let users make something that belongs on the web, like a site
|
||||
builder or a portfolio maker. When the user is done, they want it online at a
|
||||
link they can share. Usually that means your app runs its own hosting, with
|
||||
servers to store every user's files and a domain to serve them from.
|
||||
|
||||
With Puter.js, your app publishes the site straight from the browser, like a
|
||||
static host such as GitHub Pages. It writes the files into a folder in the
|
||||
user's Puter account with the [filesystem API](/FS/), then puts that folder
|
||||
online at `https://<name>.puter.site` with the [hosting API](/Hosting/). The
|
||||
site lives in the user's account, so there is no server for you to run. Anyone
|
||||
can open the link, with no sign in and no Puter.js.
|
||||
|
||||
## Publish What the User Made
|
||||
|
||||
Turn the user's project into HTML, write it as `index.html` in a folder, then
|
||||
pass a site name and the folder to
|
||||
[`puter.hosting.create()`](/Hosting/create/):
|
||||
|
||||
```js
|
||||
function renderPage (project) {
|
||||
return `<!doctype html>
|
||||
<title>${project.title}</title>
|
||||
<h1>${project.title}</h1>
|
||||
<p>${project.bio}</p>
|
||||
<img src="photo.jpg">`;
|
||||
}
|
||||
|
||||
const dir = `sites/${project.id}`;
|
||||
|
||||
await puter.fs.write(`${dir}/index.html`, renderPage(project), { createMissingParents: true });
|
||||
await puter.fs.write(`${dir}/photo.jpg`, project.photo);
|
||||
|
||||
const site = await puter.hosting.create('grace-portfolio', dir);
|
||||
|
||||
console.log(`https://${site.subdomain}.puter.site`);
|
||||
```
|
||||
|
||||
The site now serves the folder. A request for `/` gets `index.html`, and every
|
||||
other file is served at its path, such as `photo.jpg` at `/photo.jpg`. Everything
|
||||
in the folder is public, so write only what the user means to publish.
|
||||
|
||||
## Let the User Pick a Name
|
||||
|
||||
Site names are shared by every Puter user, so the name the user wants may
|
||||
already be taken. In that case [`puter.hosting.create()`](/Hosting/create/)
|
||||
rejects with a `conflict` error. Catch it to ask the user for another name, or
|
||||
let [`puter.randName()`](/Utils/randName/) pick one nobody has:
|
||||
|
||||
```js
|
||||
async function createSite (dir, name) {
|
||||
try {
|
||||
return await puter.hosting.create(name, dir);
|
||||
} catch (e) {
|
||||
if (e?.code !== 'conflict') throw e;
|
||||
return await puter.hosting.create(puter.randName(), dir);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
A name can use lowercase letters, digits and hyphens, and cannot start or end
|
||||
with a hyphen. Check these
|
||||
rules in your name field before calling
|
||||
[`puter.hosting.create()`](/Hosting/create/).
|
||||
|
||||
## Remember Which Site Belongs to Which Project
|
||||
|
||||
When the user comes back to a project, your app needs to know whether it was
|
||||
published and under which name. Save the site name with
|
||||
[`puter.kv.set()`](/KV/set/), keyed by the project:
|
||||
|
||||
```js
|
||||
await puter.kv.set(`site:${project.id}`, site.subdomain);
|
||||
```
|
||||
|
||||
To find the site later, read it back with [`puter.kv.get()`](/KV/get/). It
|
||||
returns `null` for a project that was never published:
|
||||
|
||||
```js
|
||||
const subdomain = await puter.kv.get(`site:${project.id}`);
|
||||
```
|
||||
|
||||
## Publish Again After Edits
|
||||
|
||||
A site serves whatever is in its folder, so publishing an edit means writing the
|
||||
files again. There is nothing to redeploy. This `publish()` function handles
|
||||
the first publish and every one after it:
|
||||
|
||||
```js
|
||||
async function publish (project, name) {
|
||||
const dir = `sites/${project.id}`;
|
||||
|
||||
await puter.fs.write(`${dir}/index.html`, renderPage(project), { createMissingParents: true });
|
||||
await puter.fs.write(`${dir}/photo.jpg`, project.photo);
|
||||
|
||||
let subdomain = await puter.kv.get(`site:${project.id}`);
|
||||
|
||||
if (!subdomain) {
|
||||
const site = await createSite(dir, name);
|
||||
subdomain = site.subdomain;
|
||||
await puter.kv.set(`site:${project.id}`, subdomain);
|
||||
}
|
||||
|
||||
return `https://${subdomain}.puter.site`;
|
||||
}
|
||||
```
|
||||
|
||||
To list the user's sites or take one offline, see
|
||||
[Manage websites](/recipes/manage-websites/).
|
||||
|
||||
## Notes
|
||||
|
||||
- To give single files a public URL, such as images or downloads, see
|
||||
[Host files online](/recipes/host-file/). One hosted folder serves every file,
|
||||
with no page or site name to manage.
|
||||
+14
-8
@@ -1,13 +1,19 @@
|
||||
---
|
||||
title: Build an API with a Worker
|
||||
description: "Learn how to write the routes of a serverless worker with Puter.js, reading requests, storing data in the calling user's own account and sending back JSON or errors."
|
||||
description: "Learn how to build a backend API for your app with Puter.js serverless workers."
|
||||
tags: [workers, kv, auth]
|
||||
order: 38
|
||||
---
|
||||
|
||||
A [serverless worker](/Workers/) is JavaScript that runs in the cloud instead of
|
||||
in the user's browser. It answers HTTP requests at its own URL, such as
|
||||
`https://notes-api.puter.work`, and you write it as a list of routes, each one a
|
||||
Some of your app's code belongs outside the user's browser, such as checking
|
||||
input before it is saved, receiving a webhook from another service, or calling
|
||||
another API. That code needs a backend, which usually means running and paying
|
||||
for a server.
|
||||
|
||||
A [serverless worker](/Workers/) gives you that backend with no server to run,
|
||||
like Cloudflare Workers or AWS Lambda. It is JavaScript that runs in the cloud
|
||||
and answers HTTP requests at its own URL, such as
|
||||
`https://notes-api.puter.work`. You write it as a list of routes, each one a
|
||||
path and the function that handles it.
|
||||
|
||||
When your app calls a worker with
|
||||
@@ -136,8 +142,8 @@ character and does not match anything else.
|
||||
## Routes Without a User
|
||||
|
||||
A worker is also an ordinary HTTP endpoint, so it can serve callers that have
|
||||
never heard of Puter: a webhook from another service, a `curl` in a script, or a
|
||||
page that doesn't load Puter.js. These routes never touch `user`.
|
||||
never heard of Puter. A webhook from another service, a `curl` in a script, or a
|
||||
page without Puter.js can all call it. These routes never touch `user`.
|
||||
|
||||
A route can do its work and answer, with nothing to store:
|
||||
|
||||
@@ -184,8 +190,8 @@ router.get('/weather/:city', async ({ params }) => {
|
||||
});
|
||||
```
|
||||
|
||||
Anyone who knows the URL can call a route like these, so don't put anything
|
||||
behind one that should be private.
|
||||
Anyone who knows the URL can call a route like these, so keep private data
|
||||
behind routes that check for `user`.
|
||||
|
||||
## Call It From Your App
|
||||
|
||||
+13
-15
@@ -1,17 +1,18 @@
|
||||
---
|
||||
title: Deploy a Worker from Your App
|
||||
description: "Learn how to create, update, list and delete serverless workers from your own code with Puter.js, instead of publishing them by hand."
|
||||
title: Deploy Workers Programmatically
|
||||
description: "Learn how to deploy serverless workers from your app with Puter.js, so each user or project gets its own backend."
|
||||
tags: [workers]
|
||||
order: 42
|
||||
---
|
||||
|
||||
You can publish a [serverless worker](/Workers/) by hand from puter.com. You
|
||||
can also do it from code, with the [Workers API](/Workers/#workers-api). Your
|
||||
app then creates a worker, updates its code and removes it, the same way it
|
||||
writes files.
|
||||
Some apps give their users a backend, like an app builder where each project
|
||||
gets its own API. For that, your app has to deploy the backend from code, at the
|
||||
moment the user needs it.
|
||||
|
||||
This lets you build tools that ship backends for their users, such as an app
|
||||
builder that gives each project its own API.
|
||||
With the [Workers API](/Workers/#workers-api), your app creates a
|
||||
[serverless worker](/Workers/), updates its code and removes it, the same way it
|
||||
writes files. Each worker answers at its own URL, and there is no server for you
|
||||
to run.
|
||||
|
||||
## Create a Worker
|
||||
|
||||
@@ -42,7 +43,7 @@ const res = await puter.workers.exec(`${deployment.url}/me`);
|
||||
await res.json(); // { username: 'grace' }
|
||||
```
|
||||
|
||||
[Build an API with a worker](/recipes/workers-build-an-api/) covers what to put
|
||||
[Build an API with a worker](/recipes/build-an-api/) covers what to put
|
||||
in the routes.
|
||||
|
||||
Worker names are global, like subdomains, and are stored in lowercase. A name
|
||||
@@ -66,9 +67,6 @@ await puter.fs.write(info.file_path, updatedCode);
|
||||
Writing the file redeploys the worker at the same URL, so anything already
|
||||
calling it keeps working.
|
||||
|
||||
Don't create a new worker with a new name to ship a change. The old one stays
|
||||
online at its old URL, and every caller has to be pointed at the new one.
|
||||
|
||||
## Find Your Workers
|
||||
|
||||
To show the workers in your account, call
|
||||
@@ -124,6 +122,6 @@ await puter.workers.delete('notes-api');
|
||||
Its URL stops answering, and the name is free to use again. The source file
|
||||
stays in your account.
|
||||
|
||||
The link also works the other way. Deleting a worker's source file with
|
||||
[`puter.fs.delete()`](/FS/delete/) deletes the worker too, so keep the file for
|
||||
as long as the worker should stay online.
|
||||
Deleting a worker's source file with [`puter.fs.delete()`](/FS/delete/) also
|
||||
deletes the worker, so keep the file for as long as the worker should stay
|
||||
online.
|
||||
@@ -101,3 +101,9 @@ To take a link offline, delete the file with the
|
||||
```js
|
||||
await puter.fs.delete('public/photo.png');
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- To publish a whole website under a name the user picks, such as a page each
|
||||
user builds in your app, see
|
||||
[Add website publishing](/recipes/add-website-publishing/).
|
||||
@@ -5,7 +5,7 @@ tags: [hosting]
|
||||
order: 56
|
||||
---
|
||||
|
||||
Once your app has [published a website](/recipes/publish-a-website/), the
|
||||
Once your app has [published a website](/recipes/add-website-publishing/), the
|
||||
[hosting API](/Hosting/) lets it find that site again, change which folder it
|
||||
serves, and take it offline. Each operation is one call that takes the site's
|
||||
name.
|
||||
@@ -28,6 +28,10 @@ for (const site of sites) {
|
||||
Each entry is a [`Subdomain`](/Objects/subdomain/) object. `root_dir` is the
|
||||
folder it serves, and is `null` when that folder no longer exists.
|
||||
|
||||
When your code runs as a Puter app, the list only has the sites that app
|
||||
created. Sites the user published from somewhere else are left out, and the
|
||||
other hosting calls cannot change them either.
|
||||
|
||||
## Get One Site
|
||||
|
||||
To look up a single site by name, call [`puter.hosting.get()`](/Hosting/get/):
|
||||
@@ -74,13 +78,3 @@ await puter.hosting.delete('grace-portfolio');
|
||||
|
||||
The address stops answering. The folder and its files stay in the user's
|
||||
account, so nothing is lost and you can publish them again later.
|
||||
|
||||
## Notes
|
||||
|
||||
- When your code runs as a Puter app, these calls only see the sites that app
|
||||
created. Sites the user published elsewhere are not listed and cannot be
|
||||
changed.
|
||||
- [`puter.hosting.list()`](/Hosting/list/) returns sites only. Workers have
|
||||
their own [`puter.workers.list()`](/Workers/list/).
|
||||
- For a user with many sites, page through them with the `limit` and `cursor`
|
||||
options of [`puter.hosting.list()`](/Hosting/list/).
|
||||
@@ -1,108 +0,0 @@
|
||||
---
|
||||
title: Publish a Website
|
||||
description: "Learn how to publish a website from your app with Puter.js, giving a folder of HTML files its own public puter.site address."
|
||||
tags: [hosting, fs]
|
||||
order: 55
|
||||
---
|
||||
|
||||
The [hosting API](/Hosting/) turns a folder in the user's Puter account into a
|
||||
public website at `https://<name>.puter.site`. Anyone can open it, with no sign
|
||||
in and no Puter.js. Your app writes the files with the
|
||||
[filesystem API](/FS/), then publishes the folder with one call.
|
||||
|
||||
This lets you build a website builder, a portfolio generator, or an "export as
|
||||
website" button, with the site hosted in the user's own account.
|
||||
|
||||
## Publish a One-Page Site
|
||||
|
||||
Write an `index.html` into a folder, then pass a name and the folder to
|
||||
[`puter.hosting.create()`](/Hosting/create/):
|
||||
|
||||
```js
|
||||
await puter.fs.mkdir('my-site');
|
||||
await puter.fs.write('my-site/index.html', '<h1>Hello, world!</h1>');
|
||||
|
||||
const site = await puter.hosting.create('grace-portfolio', 'my-site');
|
||||
|
||||
console.log(`https://${site.subdomain}.puter.site`);
|
||||
```
|
||||
|
||||
The site is now online at that address. A request for `/`, or for any folder,
|
||||
is answered with that folder's `index.html`.
|
||||
|
||||
## Pick a Name
|
||||
|
||||
Site names are shared by every Puter user, so a name someone already has is not
|
||||
free. [`puter.hosting.create()`](/Hosting/create/) then rejects with a
|
||||
`conflict` error. Catch it and offer another name, or let
|
||||
[`puter.randName()`](/Utils/randName/) pick one nobody has:
|
||||
|
||||
```js
|
||||
async function publish (dir, name) {
|
||||
try {
|
||||
return await puter.hosting.create(name, dir);
|
||||
} catch (e) {
|
||||
if (e?.code !== 'conflict') throw e;
|
||||
return await puter.hosting.create(puter.randName(), dir);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
A name can use lowercase letters, digits and hyphens, and cannot start or end
|
||||
with a hyphen. A few names such as `www` and `api` are reserved.
|
||||
|
||||
## Change What It Shows
|
||||
|
||||
The site serves whatever is in the folder right now. To change a page, write
|
||||
the file again. There is nothing to redeploy:
|
||||
|
||||
```js
|
||||
await puter.fs.write('my-site/index.html', '<h1>Hello again!</h1>');
|
||||
```
|
||||
|
||||
The same goes for new files. Anything you add to the folder is online at the
|
||||
matching path, such as `my-site/about.html` at `/about.html`.
|
||||
|
||||
## Serve a Single-Page App
|
||||
|
||||
An app with client-side routing needs every unknown path, such as
|
||||
`/dashboard`, to load `index.html`. Write a
|
||||
[`.puter_site_config`](/site-config/) file next to it:
|
||||
|
||||
```js
|
||||
await puter.fs.write('my-site/.puter_site_config', JSON.stringify({
|
||||
errors: { 404: { file: '/index.html', status: 200 } },
|
||||
}));
|
||||
```
|
||||
|
||||
The config file is never served to visitors. Changes to it take up to a minute
|
||||
to show.
|
||||
|
||||
## Add Dynamic Content
|
||||
|
||||
A hosted site is static: it serves the files in the folder as they are. Most
|
||||
bigger sites also need something that runs on each request, such as saving a
|
||||
form, loading data per user, or calling an AI model. Put that part in a
|
||||
[serverless worker](/Workers/) and call it from the site's pages with
|
||||
[`puter.workers.exec()`](/Workers/exec/):
|
||||
|
||||
```js
|
||||
const res = await puter.workers.exec('https://my-site-api.puter.work/notes');
|
||||
const notes = await res.json();
|
||||
```
|
||||
|
||||
[Build an API with a worker](/recipes/workers-build-an-api/) shows how to write
|
||||
those routes. To keep the backend in the same folder as the site, use a
|
||||
[dynamic worker](/Workers/dynamic/) instead: a file such as
|
||||
`my-site/__workers/api.worker.js` is served at
|
||||
`https://<name>.puter.site/__workers/api/`, with no separate deploy.
|
||||
|
||||
## Notes
|
||||
|
||||
- Everything in the folder is public, including files you add later. Keep
|
||||
private files somewhere else.
|
||||
- The user needs a verified email address to publish a site.
|
||||
- A site's name cannot be changed. To move to a new name, create a site with it
|
||||
and [delete](/recipes/manage-websites/#delete-a-site) the old one.
|
||||
- To hand out a link to one file rather than build a site, see
|
||||
[Host files online](/recipes/host-file/).
|
||||
Reference in new issue
Block a user