| description | Migration guide for Fresh 2.x |
|---|
We tried to keep breaking changes in Fresh 2 as minimal as possible, but some changes need to be updated manually. Fresh 2 comes with many quality of life improvements that make it easier to extend and adapt Fresh. We've created this upgrade guide as part of upgrading our own apps here at Deno.
Use this guide to migrate a Fresh 1.x app to Fresh 2.
Most changes can be applied automatically with the update script. Start the update by running it in your project directory:
deno run -Ar jsr:@fresh/updateThis will apply most API changes made in Fresh 2 automatically update like
changing $fresh/server.ts imports to fresh.
Configuring Fresh doesn't require a dedicated config file anymore. You can
delete the fresh.config.ts file. The fresh.gen.ts manifest file isn't needed
anymore either.
<project root>
├── routes/
├── dev.ts
- ├── fresh.gen.ts
- ├── fresh.config.ts
└── main.tsFresh 2 takes great care in ensuring that code that's only needed during development is separate from production code. This split makes deployments much smaller, quicker to upload and allows them to boot up much quicker in production.
Development related configuration can be passed to the Builder class instance
in dev.ts. This file is also where you typically set up development-only
plugins like tailwindcss.
The full dev.ts file for newly generated Fresh 2 projects looks like this:
import { Builder } from "fresh/dev";
import { tailwind } from "@fresh/plugin-tailwind";
// Pass development only configuration here
const builder = new Builder({ target: "safari12" });
// Example: Enabling the tailwind plugin for Fresh
tailwind(builder);
// Create optimized assets for the browser when
// running `deno run -A dev.ts build`
if (Deno.args.includes("build")) {
await builder.build();
} else {
// ...otherwise start the development server
await builder.listen(() => import("./main.ts"));
}[info]: Fresh 1.x used Tailwind CSS v3. To keep using v3 use the
@fresh/plugin-tailwind-v3instead.
Similarly, configuration related to running Fresh in production can be passed to
new App():
import { App, staticFiles } from "fresh";
export const app = new App()
// Add static file serving middleware
.use(staticFiles())
// Enable file-system based routing
.fsRoutes();Both the _500.tsx and _404.tsx template have been unified into a single
_error.tsx template.
└── <root>/routes/
- ├── _404.tsx
- ├── _500.tsx
+ ├── _error.tsx
└── ...Inside the _error.tsx template you can show different content based on errors
or status codes with the following code:
export default function ErrorPage(props: PageProps) {
const error = props.error; // Contains the thrown Error or HTTPError
if (error instanceof HttpError) {
const status = error.status; // HTTP status code
// Render a 404 not found page
if (status === 404) {
return <h1>404 - Page not found</h1>;
}
}
return <h1>Oh no...</h1>;
}The server entrypoint is now generated by Fresh for more optimal startup times. This means you need to update your task when launching Fresh in production mode.
To launch Fresh in production mode:
- deno run -A main.ts
+ deno serve -A _fresh/server.jsYou'll likely have that command inside your deno.json as a task. Update it
accordingly.
{
"tasks":
"dev": "deno run -A dev.ts",
"build": "deno run -A dev.ts build",
- "preview": "deno run -A main.ts"
+ "preview": "deno serve -A _fresh/server.js"
}
}The <Head> component was used in Fresh 1.x to add additional tags to the
<head> portion of an HTML document from anywhere on the page. This feature was
removed in preparation and due to performance concerns as it required a complex
machinery in the background to work.
Instead, passing head-related data is best done via ctx.state, which can be
easily set through the define helper.
// utils.ts
export interface State {
title?: string;
}
export const define = createDefine<State>();
// routes/about.tsx
import { define } from "../utils.ts";
export default define.page(function AboutPage(ctx) {
// Set a route specific data in a handler
ctx.state.title = "About Me";
return (
<div>
<h1>About Me</h1>
</div>
);
});
// Render that in _app.tsx
export default define.page(function App({ Component, state }) {
return (
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
{state.title ? <title>{state.title}</title> : null}
</head>
<body>
<Component />
</body>
</html>
);
});Fresh 2 requires assets to be build during deployment instead of building them
on demand. Run the deno task build command as part of your deployment process.
If you have already set up Fresh's 1.x "Ahead-of-Time Builds", then no changes
are necessary.
The handling trailing slashes has been extracted to an optional middleware that you can add if needed. This middleware can be used to ensure that URLs always have a trailing slash at the end or that they will never have one.
- import { App, staticFiles } from "fresh";
+ import { App, staticFiles, trailingSlashes } from "fresh";
export const app = new App({ root: import.meta.url })
.use(staticFiles())
+ .use(trailingSlashes("never"));[info]: The changes listed here are applied automatically when running the
@fresh/updatescript and you shouldn't need to have to do these yourself.
Middleware, handler and route component signatures have been unified to all look
the same. Instead of receiving two arguments, they receive one. The Request
object is stored on the context object as ctx.req.
- const middleware = (req, ctx) => new Response("ok");
+ const middleware = (ctx) => new Response("ok");Same is true for handlers:
export const handler = {
- GET(req, ctx) {
+ GET(ctx) {
return new Response("ok");
},
};...and async route components:
- export default async function MyPage(req: Request, ctx: RouteContext) {
+ export default async function MyPage(props: PageProps) {
const value = await loadFooValue();
return <p>foo is: {value}</p>;
}All the various context interfaces have been consolidated and simplified:
| Fresh 1.x | Fresh 2.x |
|---|---|
AppContext, LayoutContext, RouteContext |
Context |
The ctx.renderNotFound() method has been removed in favor of throwing an
HttpError instance. This allows all middlewares to optionally participate in
error handling. Other properties have been moved or renamed to make it easier to
re-use existing objects internally as a minor performance optimization.
| Fresh 1.x | Fresh 2.x |
|---|---|
ctx.renderNotFound() |
throw new HttpError(404) |
ctx.basePath |
ctx.config.basePath |
ctx.remoteAddr |
ctx.info.remoteAddr |
The createHandler function was often used to launch Fresh for tests. This can
be now done via the Builder.
// Best to do this once instead of for every test case for
// performance reasons.
const builder = new Builder();
const applySnapshot = await builder.build({ snapshot: "memory" });
function testApp() {
const app = new App()
.get("/", () => new Response("hello"));
// Applies build snapshot to this app instance.
applySnapshot(app);
return app;
}
Deno.test("My Test", async () => {
const handler = testApp().handler();
const response = await handler(new Request("http://localhost"));
const text = await response.text();
if (text !== "hello") {
throw new Error("fail");
}
});If you run into problems with upgrading your app, reach out to us by creating an issue here https://github.com/denoland/fresh/issues/new . That way we can improve this migration guide for everyone.