From a2343f01f66603e51294c664d3785a0aa2a6fb3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 22 Apr 2026 19:57:11 +0200 Subject: [PATCH 01/13] feat: refresh landing page for Fresh 2.3 release Update HelloBar to announce Fresh 2.3 with feature highlights. Add new sections: View Transitions, API Routes/Handlers, WebSockets, element, and OpenTelemetry. Fix syntax highlighting in Simple section, fix broken Deco showcase image, update RenderingSection to use Temporal API, fix cursor styles on interactive demos, and move WebSocket docs from Examples to Advanced. --- .../{examples => advanced}/websockets.md | 0 docs/toc.ts | 2 +- www/components/homepage/APIRoutesSection.tsx | 79 ++++++++++++++++++ www/components/homepage/FormsSection.tsx | 3 +- www/components/homepage/HeadSection.tsx | 79 ++++++++++++++++++ www/components/homepage/RecipeDemo.tsx | 6 +- www/components/homepage/RenderingSection.tsx | 11 ++- www/components/homepage/SecuritySection.tsx | 71 ++++++++++++++++ www/components/homepage/Simple.tsx | 25 +++--- www/components/homepage/SocialProof.tsx | 16 ++-- .../homepage/TransitionsSection.tsx | 81 +++++++++++++++++++ www/components/homepage/WebSocketSection.tsx | 72 +++++++++++++++++ www/islands/FormSubmitDemo.tsx | 10 +-- www/routes/index.tsx | 22 ++++- 14 files changed, 441 insertions(+), 36 deletions(-) rename docs/latest/{examples => advanced}/websockets.md (100%) create mode 100644 www/components/homepage/APIRoutesSection.tsx create mode 100644 www/components/homepage/HeadSection.tsx create mode 100644 www/components/homepage/SecuritySection.tsx create mode 100644 www/components/homepage/TransitionsSection.tsx create mode 100644 www/components/homepage/WebSocketSection.tsx diff --git a/docs/latest/examples/websockets.md b/docs/latest/advanced/websockets.md similarity index 100% rename from docs/latest/examples/websockets.md rename to docs/latest/advanced/websockets.md diff --git a/docs/toc.ts b/docs/toc.ts index 15c3937c13e..4d3c5d7c451 100644 --- a/docs/toc.ts +++ b/docs/toc.ts @@ -61,6 +61,7 @@ const toc: RawTableOfContents = { ["environment-variables", "Environment Variables", "link:latest"], ["head", " element", "link:latest"], ["vite", "Vite Plugin Options", "link:latest"], + ["websockets", "WebSockets", "link:latest"], ["opentelemetry", "OpenTelemetry", "link:latest"], ["api-reference", "API Reference", "link:latest"], ["troubleshooting", "Troubleshooting", "link:latest"], @@ -107,7 +108,6 @@ const toc: RawTableOfContents = { ], ["active-links", "Active links", "link:latest"], ["session-management", "Session management", "link:latest"], - ["websockets", "WebSockets", "link:latest"], ["common-patterns", "Common Patterns", "link:latest"], ], }, diff --git a/www/components/homepage/APIRoutesSection.tsx b/www/components/homepage/APIRoutesSection.tsx new file mode 100644 index 00000000000..d6f43dd057c --- /dev/null +++ b/www/components/homepage/APIRoutesSection.tsx @@ -0,0 +1,79 @@ +import { CodeBlock } from "../../components/CodeBlock.tsx"; +import { CodeWindow } from "../../components/CodeWindow.tsx"; +import { PageSection } from "../../components/PageSection.tsx"; +import { SideBySide } from "../../components/SideBySide.tsx"; +import { SectionHeading } from "../../components/homepage/SectionHeading.tsx"; +import { FancyLink } from "../../components/FancyLink.tsx"; + +const apiCode = `import { createDefine } from "fresh"; +const define = createDefine(); + +export const handlers = define.handlers({ + GET(ctx) { + const user = db.getUser(ctx.params.id); + return Response.json(user); + }, + POST(ctx) { + const body = await ctx.req.json(); + const user = db.updateUser(ctx.params.id, body); + return Response.json(user); + }, + DELETE(ctx) { + db.deleteUser(ctx.params.id); + return new Response(null, { status: 204 }); + }, +});`; + +export function APIRoutesSection() { + return ( + + +
+ + Handlers for every method +

+ Define{" "} + + GET + ,{" "} + + POST + ,{" "} + + DELETE + {" "} + — any HTTP method as a named handler on your route. Fresh maps + requests to the right function automatically, with full type safety. +

+ + Learn about handlers + +
+
+ + + +
+
+
+ ); +} diff --git a/www/components/homepage/FormsSection.tsx b/www/components/homepage/FormsSection.tsx index fa6305b3c0a..539338ed651 100644 --- a/www/components/homepage/FormsSection.tsx +++ b/www/components/homepage/FormsSection.tsx @@ -8,7 +8,8 @@ import { ExampleArrow } from "../homepage/ExampleArrow.tsx"; import { FancyLink } from "../FancyLink.tsx"; import { FormSubmitDemo } from "../../islands/FormSubmitDemo.tsx"; -const routingCode = `import { define } from "../utils.ts"; +const routingCode = `import { createDefine } from "fresh"; +const define = createDefine(); export const handler = define.handlers({ async POST(ctx) { diff --git a/www/components/homepage/HeadSection.tsx b/www/components/homepage/HeadSection.tsx new file mode 100644 index 00000000000..3cd03158520 --- /dev/null +++ b/www/components/homepage/HeadSection.tsx @@ -0,0 +1,79 @@ +import { CodeBlock } from "../../components/CodeBlock.tsx"; +import { CodeWindow } from "../../components/CodeWindow.tsx"; +import { PageSection } from "../../components/PageSection.tsx"; +import { SideBySide } from "../../components/SideBySide.tsx"; +import { SectionHeading } from "../../components/homepage/SectionHeading.tsx"; +import { FancyLink } from "../../components/FancyLink.tsx"; + +const headCode = `import { Head } from "fresh/runtime"; + +export default function MyPage() { + return ( + <> + + My Page + + + +

Hello, world!

+ + ); +}`; + +export function HeadSection() { + return ( + + +
+ + + Full control of{" "} + + +

+ Use the{" "} + {" "} + component from any page or island to set titles, meta tags, + stylesheets, and scripts — no hoisting hacks or side channels + needed. +

+ + + +
+
+ + + +
+
+
+ ); +} diff --git a/www/components/homepage/RecipeDemo.tsx b/www/components/homepage/RecipeDemo.tsx index 9e96433c43f..b3703595fe4 100644 --- a/www/components/homepage/RecipeDemo.tsx +++ b/www/components/homepage/RecipeDemo.tsx @@ -6,13 +6,13 @@ export const RecipeDemo = () => ( class="w-full grid grid-cols-1 md:grid-cols-[auto_1fr] text-left gap-6 items-stretch min-h-64" > diff --git a/www/components/homepage/RenderingSection.tsx b/www/components/homepage/RenderingSection.tsx index 4c836cad7a3..1f379cd2063 100644 --- a/www/components/homepage/RenderingSection.tsx +++ b/www/components/homepage/RenderingSection.tsx @@ -7,9 +7,10 @@ import { DemoBox } from "../../components/homepage/DemoBox.tsx"; import { ExampleArrow } from "../../components/homepage/ExampleArrow.tsx"; const serverCode = `export default function HomePage() { - const time = new Date().toLocaleString(); + const now = Temporal.Now.plainDateTimeISO() + .toLocaleString("en-US"); return ( -

Freshly server-rendered {time}

+

Freshly server-rendered {now}

); }`; @@ -56,10 +57,8 @@ export function RenderingSection() { {" "}

- Freshly server-rendered {new Date().toLocaleString("default", { - dateStyle: "medium", - timeStyle: "medium", - })} UTC + Freshly server-rendered{" "} + {Temporal.Now.plainDateTimeISO().toLocaleString("en-US")}

diff --git a/www/components/homepage/SecuritySection.tsx b/www/components/homepage/SecuritySection.tsx new file mode 100644 index 00000000000..bd15f675b79 --- /dev/null +++ b/www/components/homepage/SecuritySection.tsx @@ -0,0 +1,71 @@ +import { CodeBlock } from "../../components/CodeBlock.tsx"; +import { CodeWindow } from "../../components/CodeWindow.tsx"; +import { PageSection } from "../../components/PageSection.tsx"; +import { SideBySide } from "../../components/SideBySide.tsx"; +import { SectionHeading } from "../../components/homepage/SectionHeading.tsx"; +import { FancyLink } from "../../components/FancyLink.tsx"; + +const otelCode = `import { App } from "fresh"; + +const app = new App({ + // When OpenTelemetry is active, Fresh + // auto-injects a + // tag — connecting browser traces to + // server spans, zero config required + otel: true, +}); + +app.listen();`; + +export function SecuritySection() { + return ( + + +
+ + OpenTelemetry, built in +

+ When OpenTelemetry is active, Fresh automatically injects a{" "} + + traceparent + {" "} + meta tag into every page — connecting your browser traces to server + spans end-to-end, zero config required. +

+

+ Get full-stack observability across your entire request lifecycle + with a single flag. +

+ + Learn about OpenTelemetry + +
+
+ + + +
+
+
+ ); +} diff --git a/www/components/homepage/Simple.tsx b/www/components/homepage/Simple.tsx index b161d102a3b..3509d3e09e3 100644 --- a/www/components/homepage/Simple.tsx +++ b/www/components/homepage/Simple.tsx @@ -1,26 +1,33 @@ import { PageSection } from "../../components/PageSection.tsx"; +import { CodeBlock } from "../../components/CodeBlock.tsx"; const ONE_FILE_EXAMPLE = `import { App } from "fresh"; const app = new App() - .get("/", () => new Response("Hello, World!")); + .get("/", () => + new Response("Hello!") + ); app.listen();`; const ADD_JSX_EXAMPLE = `import { App } from "fresh"; const app = new App() - .get("/", (ctx) => ctx.render(

Hello!

)); + .get("/", (ctx) => + ctx.render(

Hello!

) + ); app.listen();`; -const ADD_ISLAND_EXAMPLE = `// islands/Counter.tsx -import { useSignal } from "@preact/signals"; +const ADD_ISLAND_EXAMPLE = `import { useSignal } from + "@preact/signals"; export function Counter() { const count = useSignal(0); return ( - ); @@ -40,7 +47,7 @@ export function Simple() {

-
+
{props.title}

{props.description}

-
-        {props.code}
-      
+
+ +
); } diff --git a/www/components/homepage/SocialProof.tsx b/www/components/homepage/SocialProof.tsx index 4b7229ad3ec..fde4871ad2c 100644 --- a/www/components/homepage/SocialProof.tsx +++ b/www/components/homepage/SocialProof.tsx @@ -16,13 +16,15 @@ export function SocialProof() { eCommerce platform

- - Deco CX + +
+ + deco.cx + +
diff --git a/www/components/homepage/TransitionsSection.tsx b/www/components/homepage/TransitionsSection.tsx new file mode 100644 index 00000000000..f3e8ba03b24 --- /dev/null +++ b/www/components/homepage/TransitionsSection.tsx @@ -0,0 +1,81 @@ +import { CodeBlock } from "../../components/CodeBlock.tsx"; +import { CodeWindow } from "../../components/CodeWindow.tsx"; +import { PageSection } from "../../components/PageSection.tsx"; +import { SideBySide } from "../../components/SideBySide.tsx"; +import { SectionHeading } from "../../components/homepage/SectionHeading.tsx"; +import { FancyLink } from "../../components/FancyLink.tsx"; + +const transitionCode = `import { App } from "fresh"; + +const app = new App({ + // Enable the View Transitions API + // for smooth client-side navigation + viewTransition: true, +}); + +app.listen();`; + +const cssCode = `/* Style your transitions with CSS */ +::view-transition-old(root) { + animation: fade-out 0.2s ease-in; +} +::view-transition-new(root) { + animation: fade-in 0.2s ease-out; +}`; + +export function TransitionsSection() { + return ( + + +
+ + Smooth View Transitions +

+ Fresh supports the{" "} + + View Transitions API + {" "} + out of the box, giving your app native-feeling page transitions with + a single config flag. +

+

+ Pages crossfade automatically. Customize animations per-element with + plain CSS — no JavaScript animation libraries needed. +

+ + Learn about View Transitions + +
+
+ + + + + + +
+
+
+ ); +} diff --git a/www/components/homepage/WebSocketSection.tsx b/www/components/homepage/WebSocketSection.tsx new file mode 100644 index 00000000000..baad1197e91 --- /dev/null +++ b/www/components/homepage/WebSocketSection.tsx @@ -0,0 +1,72 @@ +import { CodeBlock } from "../../components/CodeBlock.tsx"; +import { CodeWindow } from "../../components/CodeWindow.tsx"; +import { PageSection } from "../../components/PageSection.tsx"; +import { SideBySide } from "../../components/SideBySide.tsx"; +import { SectionHeading } from "../../components/homepage/SectionHeading.tsx"; +import { FancyLink } from "../../components/FancyLink.tsx"; + +const wsCode = `import { App } from "fresh"; + +const app = new App(); + +app.ws("/chat", { + open(socket) { + console.log("Client connected"); + }, + message(socket, event) { + // Echo the message back + socket.send(\`You said: \${event.data}\`); + }, + close(socket) { + console.log("Client disconnected"); + }, +}); + +app.listen();`; + +export function WebSocketSection() { + return ( + + +
+ + First-class WebSockets +

+ Add real-time endpoints with{" "} + + app.ws() + {" "} + — define open, message, and close handlers in a single object. Build + chat, live dashboards, or collaborative editing without leaving + Fresh. +

+ + Learn about WebSockets + +
+
+ + + +
+
+
+ ); +} diff --git a/www/islands/FormSubmitDemo.tsx b/www/islands/FormSubmitDemo.tsx index 249b9bd2e05..6df2a352654 100644 --- a/www/islands/FormSubmitDemo.tsx +++ b/www/islands/FormSubmitDemo.tsx @@ -4,7 +4,7 @@ export function FormSubmitDemo() {
What's your favorite treat? -