Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions docs/canary/advanced/app-wrapper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
description: |
Add a global app wrapper to provide common meta tags or context for application routes.
---

The app wrapper component is a Preact component that represents the outer
structure of the HTML document, typically up until the `<body>`-tag. It is only
rendered on the server and never on the client. The passed `Component` value
represents the children of this component.

```tsx
function AppWrapper({ Component }) {
return (
<html lang="en">
<head>
<meta charset="utf-8" />
<title>My App</title>
</head>
<body>
<Component />
</body>
</html>
);
}

app.appWrapper(AppWrapper);
```

Every `ctx.render()` call will include the app wrapper component by default,
unless opted out.

Note that only one app wrapper component is supported per
[`App`](/docs/canary/concepts/app) instance.
82 changes: 82 additions & 0 deletions docs/canary/advanced/error-handling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
description: |
Error pages can be used to customize the page that is shown when an error occurs in the application.
---

Fresh supports customizing the `404 Not Found`, and the
`500 Internal Server Error` pages. These are shown when a request is made but no
matching route exists, and when a middleware, route handler, or page component
throws an error respectively.

### 404: Not Found

The 404 page can be customized by creating a `_404.tsx` file in the `routes/`
folder. The file must have a default export that is a regular Preact component.
A props object of type `PageProps` is passed in as an argument.

```tsx routes/_404.tsx
import { PageProps } from "$fresh/server.ts";

export default function NotFoundPage({ url }: PageProps) {
return <p>404 not found: {url.pathname}</p>;
}
```

#### Manually render 404 pages

The `_404.tsx` file will be invoked automatically when no route matches the URL.
In some cases, one needs to manually trigger the rendering of the 404 page, for
example when the route did match, but the requested resource does not exist.
This can be achieved with `ctx.renderNotFound`.

```tsx routes/blog/[slug].tsx
import { Handlers, PageProps } from "$fresh/server.ts";

export const handler: Handlers = {
async GET(req, ctx) {
const blogpost = await fetchBlogpost(ctx.params.slug);
if (!blogpost) {
return ctx.renderNotFound({
custom: "prop",
});
}
return ctx.render({ blogpost });
},
};

export default function BlogpostPage({ data }) {
return (
<article>
<h1>{data.blogpost.title}</h1>
{/* rest of your page */}
</article>
);
}
```

This can also be achieved by throwing an error, if you're uninterested in
passing specific data to your 404 page:

```tsx
import { Handlers } from "$fresh/server.ts";

export const handler: Handlers = {
GET(_req, _ctx) {
throw new Deno.errors.NotFound();
},
};
```

### 500: Internal Server Error

The 500 page can be customized by creating a `_500.tsx` file in the `routes/`
folder. The file must have a default export that is a regular Preact component.
A props object of type `PageProps` is passed in as an argument.

```tsx routes/_500.tsx
import { PageProps } from "$fresh/server.ts";

export default function Error500Page({ error }: PageProps) {
return <p>500 internal error: {(error as Error).message}</p>;
}
```
120 changes: 120 additions & 0 deletions docs/canary/advanced/forms.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
---
description: |
Robustly handle user inputs using HTML `<form>` elements client side, and form
submission handlers server side.
---

For stronger resiliency and user experience, Fresh relies on native browser
support for form submissions with the HTML `<form>` element.

In the browser, a `<form>` submit will send an HTML action (usually `GET` or
`POST`) to the server, which responds with a new page to render.

## POST request with `application/x-www-form-urlencoded`

Forms typically submit as a `GET` request with data encoded in the URL's search
parameters, or as a `POST` request with either an
`application/x-www-form-urlencoded` or `multipart/form-data` body.

This example demonstrates how to handle `application/x-www-form-urlencoded`
`<form>` submissions:

```tsx routes/subscribe.tsx
import { Handlers } from "$fresh/server.ts";

export const handler: Handlers = {
async GET(req, ctx) {
return await ctx.render();
},
async POST(req, ctx) {
const form = await req.formData();
const email = form.get("email")?.toString();

// Add email to list.

// Redirect user to thank you page.
const headers = new Headers();
headers.set("location", "/thanks-for-subscribing");
return new Response(null, {
status: 303, // See Other
headers,
});
},
};

export default function Subscribe() {
return (
<>
<form method="post">
<input type="email" name="email" value="" />
<button type="submit">Subscribe</button>
</form>
</>
);
}
```

When the user submits the form, Deno will retrieve the `email` value using the
request's `formData()` method, add the email to a list, and redirect the user to
a thank you page.

## Handling file uploads

File uploads can be handled in a very similar manner to the example above. Note
that this time, we have to explicitly declare the form's encoding to be
`multipart/form-data`.

```tsx routes/subscribe.tsx
import { Handlers, type PageProps } from "$fresh/server.ts";

interface Props {
message: string | null;
}

export const handler: Handlers<Props> = {
async GET(req, ctx) {
return await ctx.render({
message: null,
});
},
async POST(req, ctx) {
const form = await req.formData();
const file = form.get("my-file") as File;

if (!file) {
return ctx.render({
message: `Please try again`,
});
}

const name = file.name;
const contents = await file.text();

console.log(contents);

return ctx.render({
message: `${name} uploaded!`,
});
},
};

export default function Upload(props: PageProps<Props>) {
const { message } = props.data;
return (
<>
<form method="post" encType="multipart/form-data">
<input type="file" name="my-file" />
<button type="submit">Upload</button>
</form>
{message ? <p>{message}</p> : null}
</>
);
}
```

## A note of caution

These examples are simplified to demonstrate how Deno and Fresh handle HTTP
requests. In the Real World™, you'll want to validate your data (_especially the
file type_) and protect against cross-site request forgery. Consider yourself
warned.
47 changes: 47 additions & 0 deletions docs/canary/advanced/layouts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
description: "Create re-usable layouts across routes"
---

Layouts are plain Preact components that are inherited based on the matching
pattern. When you have a section on your site where all pages share the same
HTML structure and only the content changes, a layout is a neat way to abstract
this. Layouts only ever render on the server. The passed `Component` value
represents the children of this component.

```tsx
function PageLayout({ Component }) {
return (
<div>
<Component />
<aside>Here is some sidebar content</aside>
</div>
);
}

const app = new App()
.layout("*", PageLayout)
.get("/", (ctx) => ctx.render(<h1>hello</h1>));
```

If you browse to the `/` route, Fresh will render the following HTML

```html
<div>
<h1>hello world</h1>
<aside>Here is some sidebar content</aside>
</div>
```

## Options

Add a layout and ignore all previously inherited ones.

```ts
app.layout("/foo/bar", MyComponent, { skipInheritedLayouts: true });
```

Ignore the app wrapper component:

```ts
app.layout("/foo/bar", MyComponent, { skipAppWrapper: true });
```
Loading
Loading