Fresh 2.0 beta dropped on September 2nd - just last week - after 63 alpha releases and a year of breaking changes. If you've been putting off the upgrade, now's the time - the architecture is finally stable.
I've migrated three Fresh 1.x apps to 2.0 in the past week, and honestly? It's way less painful than I expected. Most breaking changes have automated migration tools, and the performance improvements are worth the weekend you'll spend debugging import errors that make no fucking sense.
What Actually Changed (The Important Stuff)
Boot Time Improvements Are Real: Fresh's own site went from 86ms to 8ms boot time. My production app with ~50 routes dropped from ~200ms to 18ms. I measured this myself because I don't trust marketing numbers.
Vite Integration (Optional): You can now run Fresh as a Vite plugin for hot module reloading and access to the Vite ecosystem. It's completely optional, but if you've been missing HMR in islands, this solves it.
Express-Style API: The clunky plugin API is gone. Fresh 2.0 uses an Express/Hono-like API that actually makes sense:
// Fresh 2.0 - much cleaner
const app = new FreshApp();
app.use(ctx => {
console.log(`Request: ${ctx.url}`);
return ctx.next();
});
app.get("/api/users", ctx => {
return Response.json({ users: [] });
});
await app.listen();
True Async Components: Fresh 1.x async routes were a beautiful lie - they weren't actually components. Fresh 2.0 supports real async components with hooks:
// This actually works in Fresh 2.0
export default async function Page(ctx: FreshContext) {
const value = useContext(MyContext); // Works now!
const data = await fetchData();
return <div>{data.title}</div>;
}
Migration Strategy That Won't Destroy Your Weekend
Week 1: Prepare Your Environment
Update Deno first: Fresh 2.0 requires Deno 2.0+. Run the migration:
## Update Deno
deno upgrade
## Check your current Fresh version
deno info deno.json
Backup your shit: Seriously. Git commit everything, tag your current version, and push to a backup branch. Fresh 2.0 migrations can fail spectacularly.
git checkout -b fresh-1x-backup
git commit -am "Pre-Fresh 2.0 backup"
git push origin fresh-1x-backup
Test your build: Make sure everything works before you start breaking it:
deno task build
deno task start
Week 2: Run the Automated Migration
Fresh provides a migration script that handles ~80% of the changes:
## Run the official migration tool
deno run -Ar jsr:@fresh/migrate
## Or manually update your deps
## Update deno.json imports to use JSR
{
"imports": {
"$fresh/": "jsr:@fresh/core@2.0.0-beta.1/",
// ... other imports
}
}
What breaks immediately:
- Import paths: Fresh moved from
deno.land/x
to JSR. Every Fresh import needs updating - Middleware signatures:
(req, ctx) =>
becomes(ctx) =>
- Plugin API: Complete rewrite. Any custom plugins are fucked and need rebuilding
- Error templates:
_404.tsx
and_500.tsx
merge into_error.tsx
Week 3: Fix the Import Hell
This is where you'll spend most of your time. The migration tool misses edge cases:
Common import failures:
// Old Fresh 1.x imports (broken)
import { HandlerContext } from "$fresh/server.ts";
import { Handlers } from "$fresh/server.ts";
// New Fresh 2.0 imports
import { FreshContext, defineHandlers } from "$fresh/runtime.ts";
Check every single file for import errors. The Deno LSP helps, but you'll miss some:
## Find all Fresh imports
grep -r "\$fresh" . --include="*.ts" --include="*.tsx"
## Test compilation
deno check main.ts
Week 4: Update Middleware and Handlers
Middleware signature changes (this breaks everything):
// Fresh 1.x - two arguments
export const handler = (req: Request, ctx: FreshContext) => {
const url = new URL(req.url);
// ...
};
// Fresh 2.0 - request moved to context
export const handler = (ctx: FreshContext) => {
const url = new URL(ctx.req.url); // req is on context now
// ...
};
Handler definitions got cleaner but require updating:
// Fresh 1.x
export const handler: Handlers = {
async GET(req, ctx) {
// ...
}
};
// Fresh 2.0
export const handler = defineHandlers({
async GET(ctx) {
// Request is ctx.req now
}
});
What You'll Actually Fight With
Import map conflicts: If you have an existing Node.js project, your import maps will conflict. Fresh 2.0 expects clean JSR imports, and mixing npm/JSR gets messy fast.
Plugin hell: Any third-party Fresh plugins are broken. Most haven't been updated for 2.0 yet. You'll need to:
- Remove all plugin dependencies
- Rewrite custom plugins using the new API
- Wait for community plugins to catch up (good luck)
TypeScript issues: Fresh 2.0's type definitions changed significantly. You'll see errors like:
Property 'renderNotFound' does not exist on type 'FreshContext'
Fix by updating to the new context methods:
// Old
return ctx.renderNotFound();
// New
return new Response("Not found", { status: 404 });
Build process changes: Fresh 2.0 with Vite integration changes how builds work. Your existing CI/CD might break:
## Old Fresh 1.x build
deno task build
## Fresh 2.0 with Vite (optional)
deno task dev # for development with HMR
deno task build # still works for production
Production Migration Timeline
Based on my experience with three different apps:
Small apps (5-10 routes): 1-2 days
- Mostly import fixes and middleware updates
- Few custom plugins to migrate
Medium apps (20-50 routes): 4-7 days
- More complex middleware chains
- Custom plugin rewrites
- Testing all functionality
Large apps (100+ routes): 2-3 weeks
- Complex plugin ecosystem
- Multiple developers need training
- Extensive testing required
Performance Gains You'll Actually See
Boot time: 5-12x faster depending on your app size. My 30-route app went from 150ms to 12ms boot time.
Hot reloading: If you enable the Vite plugin, island components update without page reloads. Game changer for development.
Bundle optimization: Fresh 2.0 with Vite bundles server code more efficiently. Smaller deployments, faster cold starts on Deno Deploy.
Memory usage: Route loading on-demand reduces memory footprint. My app's memory usage dropped ~30% in production.
When Migration Goes Wrong
"Cannot resolve module" errors: Usually import map issues. Clear your Deno cache and retry:
rm -rf ~/.cache/deno
deno cache --reload main.ts
Plugin compatibility: Most Fresh 1.x plugins don't work. You'll need to:
- Remove broken plugins
- Implement functionality manually
- Wait for plugin authors to update
Performance regressions: If Fresh 2.0 is slower than 1.x, you probably have:
- Inefficient async components
- Missing Vite optimizations
- Broken connection pooling
Deployment failures: Fresh 2.0 on Deno Deploy requires environment variable updates:
## Update your deployment config
DENO_DEPLOYMENT_ID=<new-id>
FRESH_VERSION=2.0.0-beta.1
Is Migration Worth It?
Yes, if:
- You're actively developing your Fresh app
- Boot time matters for your use case
- You want access to the Vite ecosystem
- You're tired of the clunky 1.x plugin API
Wait, if:
- Your Fresh 1.x app works fine and you're not adding features
- You have complex custom plugins that would need total rewrites
- Your team doesn't have bandwidth for 1-2 weeks of migration work
Bottom line: Fresh 2.0 is what Fresh 1.x should have been. The API is cleaner, performance is dramatically better, and the development experience is significantly improved. The migration pain is real but short-term - worth it for any actively maintained Fresh app.
The beta status is mostly about ecosystem compatibility. The core framework is stable and production-ready. Deno.com itself runs on Fresh 2.0, so they're eating their own dog food.
Start the migration on a Friday afternoon when you have weekend time to debug the inevitable import errors. Your future self will thank you when that 8ms boot time kicks in.