examples / sites

Personal site

A one-page site rendered on the server, with its stylesheet served from public/.

denohtmlstatic

This service renders a single HTML page from one handler and serves its stylesheet as a static file. Anything under public/ is published by the platform at its own path, so public/style.css is available at /style.css without a line of code. Every other request runs the handler.

Because the page is built on the server, you can drop in live data: this example stamps the current year into the footer. Edit the profile object at the top of main.ts to make the site yours, then deploy. No build step, no framework.

code
main.ts
// A personal website rendered on the server. This one handler builds the whole
// page as HTML; the stylesheet lives in public/ and is served for you by the
// platform, so the service never touches static files itself.
//
// Anything under public/ is published at its own path (public/style.css is
// served at /style.css). Every other request runs this handler, which is why
// we can inject live data like the current year.

const PROFILE = {
  name: "Ada Lovelace",
  role: "Mathematician and writer",
  bio: "I work on analytical engines and believe a machine can do more than crunch numbers. This site is a single Frontback service: one file that renders itself.",
  links: [
    { label: "Email", href: "mailto:ada@example.com" },
    { label: "GitHub", href: "https://github.com/" },
    { label: "Notes", href: "https://example.com/notes" },
  ],
};

export default async (req: Request): Promise<Response> => {
  const { pathname } = new URL(req.url);

  // Only "/" is dynamic. /style.css and any other public asset are served
  // by the platform and never reach this handler.
  if (pathname !== "/") {
    return new Response("Not found\n", { status: 404 });
  }

  return new Response(renderPage(), {
    headers: { "content-type": "text/html; charset=utf-8" },
  });
};

function renderPage(): string {
  const year = new Date().getFullYear();
  const links = PROFILE.links
    .map((link) => `<a href="${link.href}">${link.label}</a>`)
    .join("\n        ");

  return `<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>${PROFILE.name}</title>
    <link rel="stylesheet" href="/style.css" />
  </head>
  <body>
    <main>
      <h1>${PROFILE.name}</h1>
      <p class="role">${PROFILE.role}</p>
      <p class="bio">${PROFILE.bio}</p>
      <nav>
        ${links}
      </nav>
    </main>
    <footer>&copy; ${year} ${PROFILE.name}</footer>
  </body>
</html>
`;
}
public/style.css
:root {
  color-scheme: light dark;
  --fg: #16181d;
  --muted: #5c636e;
  --bg: #ffffff;
  --line: #e6e8ec;
}

@media (prefers-color-scheme: dark) {
  :root {
    --fg: #e9ebf0;
    --muted: #9aa1ad;
    --bg: #101216;
    --line: #262a31;
  }
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2rem;
  padding: 4rem 1.5rem;
  background: var(--bg);
  color: var(--fg);
  font: 17px/1.6 system-ui, -apple-system, "Segoe UI", sans-serif;
}

main {
  max-width: 34rem;
  margin: 0 auto;
}

h1 {
  margin: 0 0 0.25rem;
  font-size: 2.25rem;
  letter-spacing: -0.02em;
}

.role {
  margin: 0 0 1.5rem;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: 0.8rem;
}

.bio {
  margin: 0 0 2rem;
}

nav {
  display: flex;
  flex-wrap: wrap;
  gap: 1.25rem;
}

nav a {
  color: var(--fg);
  text-decoration: none;
  border-bottom: 1px solid var(--line);
  padding-bottom: 2px;
}

nav a:hover {
  border-color: var(--fg);
}

footer {
  max-width: 34rem;
  margin: 0 auto;
  color: var(--muted);
  font-size: 0.85rem;
}

Go beyond what seems possible.