Migration Guide
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.
Applying automatic updates
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/update
This will apply most API changes made in Fresh 2 automatically update like
changing $fresh/server.ts
imports to fresh
.
Getting main.ts
and dev.ts
ready
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.ts
Fresh 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.
Replacing dev.ts
with vite.config.ts
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(),
],
});
Add a client.ts
file
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";
Updating main.ts
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();
Merging error pages
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>;
}
Updating tasks
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.js
You’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"
}
}
Update deployment settings
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.
Trailing slash handling
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"));
Automatic updates
InfoThe changes listed here are applied automatically when running the
@fresh/update
script and you shouldn’t need to have to do these yourself.
Unified middleware signatures
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 |
Context methods
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 |
ctx.render()
The meaning of ctx.render()
has changed. In Fresh 1.x this was used to pass
data from a handler to a component. In Fresh 2.x this function has been
generalized to render JSX.
Fresh 1.x:
export const handlers = {
async GET(req, ctx) {
const data = await Query();
await ctx.render({ value: data });
},
};
export default function Page({ data }) {
// ...
}
Fresh 2:
export const handlers = {
async GET(ctx) {
const data = await Query();
return { data: { value: data } };
},
};
export default function Page({ data }) {
// ...
}
To render JSX in general, use the ctx.render()
function:
const app = new App()
.get("/", () => ctx.render(<h1>hello</h1>));
createHandler
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.
Getting help
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.