Documents
Your deploy can carry files as well as code. Name a directory in wawesome-function.json and
everything under it goes up with the deploy. We serve those files out of storage. A file a browser
renders as a page is a Document, and most of the rules below are about those.
Nothing is built here. What your deploy declares is what gets served. Run your own bundler first and point us at the directory it wrote.
Declaring the directory
Section titled “Declaring the directory”{
"app": "letterpress",
"function": "root",
"entry": "src/index.js",
"assets": "public"
}
assets is a path relative to the directory you deploy from. Every file beneath it keeps the name
it has there: public/style.css is carried as style.css, and public/assets/index-B7f2a1.js as
assets/index-B7f2a1.js.
The CLI hashes each file and sends the paths, hashes and sizes ahead of any bytes. We answer with the hashes we do not already hold, and the CLI sends only those. A rebuild that changed one chunk uploads one chunk. Hashes are held per workspace, so a file two of your Functions carry is stored once and referenced twice.
A deploy may carry 2,000 files, and one path may be 512 characters.
What landed is in the deploy’s own output:
App: letterpress
Function: root
Version: 7
Handler: dist/index.js
Assets: 7 (2 pages)
Visibility: public
Files no address serves
Section titled “Files no address serves”{
"app": "letterpress",
"function": "root",
"entry": "src/index.js",
"assets": "public",
"files": "data"
}
files names a second directory, relative to the one you deploy from. Its files go up with the
deploy the same way, and the version keeps them. But no address ever serves them. data/rates.csv
is carried as rates.csv, and a request for /rates.csv gets your handler, or a 404 where the
version has none.
So the rule is one line: files in assets are public, files in files are private, and anything
else doesn’t ship. Private files covers reading them from your handler.
A private file with the same bytes as one of your public files is stored once and still answers at
no address. The two directories are separate, so one path can be in both, and only the public one
answers. Moving a file from assets to files makes a new version, even though its bytes didn’t
change, and so does a deploy that changed nothing but a private file.
A deploy that carries private files and nothing else is turned down, since nothing could read them. The deploy’s output counts them on a line of their own:
Assets: 7 (2 pages)
Private: 3
Where a file answers
Section titled “Where a file answers”A file answers beneath its Function’s address. A Function named root sits at the App’s own root,
so public/index.html there answers at https://{workspace}--{app}.wawesome.app. A Function named
anything else sits one segment down, and its files sit under that segment.
An address whose last segment carries no extension resolves three ways, in this order: the name
itself, the name with .html on the end, and index.html beneath it. The first one your version
carries answers.
Where that last segment is the word index, the middle one is left out. Adding .html to it would
name the page one level up, which already has an address of its own.
| The address | What it resolves to |
|---|---|
/ |
index.html |
/about |
about, then about.html, then about/index.html |
/about.html |
about.html, and nothing else |
/about/index |
about/index, then about/index/index.html |
/style.css |
style.css |
So a link to /about and a stylesheet at /style.css both resolve as written. Deploy the build
your site already has and nothing needs rewriting.
Resolving to a file is not the same as answering with one. A page resolves through several addresses and answers at one of them, which the next section is about.
A page answers at one address
Section titled “A page answers at one address”A page has one address, and it is the extension-less one with no trailing slash. Take the file
name, drop a trailing /index.html, then drop a trailing .html. Deploy about/index.html or
about.html and either way the page is at /about. Deploy index.html at the top and it is at
your Function’s own root.
Ask for the same page at any of its other addresses and we answer 308 and send you to that one.
Your query string comes with you.
| You asked for | What comes back |
|---|---|
/about |
200, the page |
/about/ |
308 to /about |
/about.html |
308 to /about |
/about/index.html |
308 to /about |
/index.html |
308 to / |
The move is only for pages, and only for a GET or a HEAD. A LICENSE or a stylesheet answers
at every address it resolves through, and a POST to a page’s address still reaches your handler.
The Location we send is a path rather than a full URL, so it is right on a custom domain and on a
preview link as well as on the address we minted. It is sent with the same no-cache the page
itself carries, so a deploy or a rollback changes where it points on the next request.
Carry two files for one address and neither is turned down. The deploy says which one is served, and the shadowed one keeps its own address rather than being moved:
Collision: about.html
This page and about/index.html both answer /about. about.html is
the one served there, and about/index.html still answers at its
own name.
That is the one case where two addresses stay. Sending you from /about/index.html to /about
would hand you the other file, so we leave both alone.
A page view runs no code
Section titled “A page view runs no code”We serve a GET or HEAD at an address a file claims straight out of storage. Your handler never
runs. Nothing opens an invocation, and no x-wawesome-invocation-id comes back. A page costs your
plan bytes rather than compute, and no cold start sits between a visitor and the markup.
Any other method at a page’s address reaches the handler, when the version has one. That is how a
form posts to the address its own page loaded at. One hostname, and no preflight, because there is
no second origin to reach across. Where the version carries no handler, that request is 405 with
Allow: GET, HEAD.
Addresses no file claims are the handler’s, exactly as they were before the deploy carried files.
The assets directory is reserved
Section titled “The assets directory is reserved”Everything beneath assets/ is static whatever your version carries. Ask for a file there that the
deploy did not carry and the answer is 404 rather than your handler. Point your bundler’s output
at it. Keep anything you may want the handler to answer for somewhere else.
How long a caller may hold a file follows from that:
| The file | What it is sent with |
|---|---|
Beneath assets/, not a page |
public, max-age=31536000, immutable |
| Anywhere else, not a page | public, max-age=300 |
| A page, wherever it sits | no-cache, with an ETag |
A hashed chunk beneath assets/ gets a new name the moment its bytes change. That is what makes a
year safe to promise. A page never gets that year, whichever directory it sits in, because a deploy
and a rollback have to show on the next request. A returning visitor sends the tag back, and we
answer 304 off the file’s row with no read of storage and no body.
Nothing is indexed until the domain is yours
Section titled “Nothing is indexed until the domain is yours”Every answer on the address we minted for you carries x-robots-tag: noindex, pages included. On a
custom hostname you attached, nothing does. What crawlers make of the site is yours to say. Custom
domains covers attaching one.
A deploy can carry no handler at all
Section titled “A deploy can carry no handler at all”A project says it has no handler by saying nothing: leave entry out of the configuration file,
have no src/index.ts once your own build has run, and carry pages. The deploy reports it:
Handler: none
This Version carries no handler at all. It serves the pages it
carries, and no code runs.
A missing entry you did declare is still a hard error, so a typo in entry never turns into a
site with no code behind it.
Three deploys are turned down, each because nothing could reach what it carries. One with no handler and no files has nothing to serve and nothing to run. One with no handler that declares the Function private withholds the only address its files could answer on. One with no handler that declares a Schedule is a timer with nothing to fire.
What answers an address no page claims
Section titled “What answers an address no page claims”With no handler, name the page that answers a miss and the status it answers at:
{
"assets": "public",
"fallback": { "path": "404.html", "status": 404 }
}
Two values, not a mode. 404.html at 404 is your own not-found page. index.html at 200 is a
single-page application’s shell, so a client-routed path reaches the shell rather than a 404. We
serve the pair you gave us and read no meaning into it.
A deploy whose fallback names a file it does not carry is turned down there and then, before
anything is stored. A miss beneath assets/ stays a 404. A shell served where a browser asked for
a script is markup answering a request for code.
With a handler, those addresses are the handler’s, and it can answer with a page the same deploy carried:
return new Response(null, { status: 404, headers: { "x-wawesome-document": "404.html" } });
We discard the body and stream that file in its place, at the status your code chose. So your
not-found page is one file, served at /404.html and at every address nobody deployed anything for.
Naming a file the deploy does not carry is a 500 rather than a quiet miss. A handler asking for a
page it never deployed is a bug, and a bug that renders as a missing page is invisible.
The pages and the handler are one version
Section titled “The pages and the handler are one version”A deploy’s manifest is the whole of what its version carries. Nothing is inherited from the version
before it. Drop the assets line and the new version serves no files at all. That is why the deploy
is turned down until you ask for it:
[wawesome] Error: This deploy declares no static files, and the version now serving carries 7. A deploy's manifest is total, so all 7 of them would stop resolving. Declare them again, with the content hashes a read of the Function's source answers, or deploy again confirming the drop.
[wawesome] Nothing was deployed. A version carries exactly the files its
[wawesome] deploy declared, so this one would take every file the live
[wawesome] version serves off the site at once.
[wawesome] Point "assets" in wawesome-function.json at the directory
[wawesome] holding them, or re-run with wawesome deploy --drop-assets to
[wawesome] remove them.
npx wawesome deploy --drop-assets confirms that one deploy. Nothing is remembered, so the next one
that declares no files is asked again.
Rolling back moves both halves at once:
npx wawesome version switch 6
Version 6’s pages and version 6’s handler go back on the address together. A broken price in the API and the markup quoting it are one thing to undo. There is no second vendor holding the pages while the code moves. Versions says what else one carries.
The .html file we used to refuse
Section titled “The .html file we used to refuse”For a while we refused any deploy carrying a path ending .html or .htm. The answer was to return
the markup from your handler instead. The argument was that a page on the App’s own origin is the
sharpest same-origin thing a file can be.
That refusal is gone. What protects the origin is the hostname the files are served on, not a list of extensions. Your files answer on your App’s hostname and on no other, and your handler is already answering there with whatever markup it likes. A page is same-origin with something that can already do everything the page could, so the extension list was never doing the work.
Returning markup from the handler still works, and it is still the better shape for a site small enough to keep in one file. The trade is that every view of it costs one invocation.
Start from a template
Section titled “Start from a template”contact-form is the shape this page describes: a page in public/, a
handler beside it, and the form posting to the address its page loaded at.
npx wawesome init --template contact-form
landing-page is the other one: no files at all, and a one-page site returned by the handler with its CSS inline.
npx wawesome init --template landing-page