| 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.
Delete dev.ts and create a vite.config.ts file instead. Pass your custom
Fresh configuration to the fresh vite plugin.
import { defineConfig } from "vite";
import { fresh } from "@fresh/plugin-vite";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [
fresh(),
tailwindcss(),
],
});The client.ts file is the main entry file for client-side code. Since vite
requires everything to be part of the internal module graph to get hot module
reloading to work, this is also the place where you import CSS assets.
<project root>
├── routes/
+ ├── client.ts
├── vite.config.ts
└── main.ts// Import CSS files here for hot module reloading to work.
import "./assets/styles.css";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"
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "deno serve -A _fresh/server.js"
}
}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.request.
- 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 vite's createBuilder function. See the
testing page for more information.
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.