DEV Community

loading...

Serverless side rendering in Deno and Begin

pchinjr profile image Paul Chin Jr. ・4 min read

Lambda functions are really good at returning a string of text. In this article, we will review how to create a cool personal website with nothing but functions. We won't have to worry about configuring a web server, and our content will be server-side rendered, but serverlessly.

To get started with the completed app click the button below to deploy it in 15 seconds:

Deploy to Begin

Our entire page will be rendered from a single lambda function. Take a look at src/http/get-index/mod.ts

import Main from './views/main.js'

export async function handler (/*req: object*/) {
  return {
    statusCode: 200,
    headers: {
      'content-type': 'text/html; charset=utf8',
      'cache-control': 'no-cache, no-store, must-revalidate, max-age=0, s-maxage=0'
    },
    body: Main({
      /**
       * Basic bio
       */
      fullname: 'Your Name', // ←  Start by adding your name!
      title: 'My personal site!',
      occupation: 'Artist & Photographer',
      location: 'West Glacier, MT',
      bio: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Quis ipsum suspendisse ultrices gravida. Risus commodo viverra maecenas accumsan lacus vel facilisis.',

      /**
       * Contact / social
       * - Comment out any item below to remove it from your page
       */
      email: 'your@email.com',
      twitter: 'yourTwitter',
      linkedin: 'your-linkedin-name',
      instagram: 'yourInsta',
      facebook: 'your-facebook-name',

      /**
       * Layout
       */
      photographer: 'Ivana Cajina',
      service: 'Unsplash',
      credit: 'https://unsplash.com/@von_co',
      image: '_static/background.jpg'
    })
  }
}
Enter fullscreen mode Exit fullscreen mode

This single Lambda function takes in the information we will need to fill in the site. Notice that we can use ESM to import the Main function, which will return an HTML string to the browser.

Let's take a look at the Main function that we import.

import Styles  from './styles.js'
import Symbols from './symbols.js'
import Splash  from './splash.js'
import Content from './content.js'

export default function Main(props = {}) {
  let title = props.title || 'Personal Website'
  return `
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1">
  <title>${title}</title>
  ${Styles(props)}
  <!-- Replace this with your own custom font link and edit Styles font-family -->
  <link href="https://fonts.googleapis.com/css?family=Roboto:300,400" rel="stylesheet">
  <!-- End custom font -->
  <link href="data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=" rel="icon" type="image/x-icon">
</head>
<body
  class="
    min-width-20
    display-flex-large
    height-100vh
    overflow-hidden-large
  "
>
 ${Symbols}
 ${Splash(props)}
 ${Content(props)}
</body>
</html>
  `
}
Enter fullscreen mode Exit fullscreen mode

From here the site is broken up into different modules for each component. Each component is a function that accepts props and returns an HTML string. The modules we import at the top are going to be all the styles, svg symbols, a component for the background image and accreditation, and finally the content.

The pattern found here can be traced all the way down through content.js, which contains more submodules like heading-large.js, which is the component for showing a name. Let's take a look at content.js and heading-large.js.

import LargeHeading from './heading-large.js'
import MediumHeading from './heading-medium.js'
import LocationLink from './link-location.js'
import MailLink from './link-mail.js'
import SocialMedia from './social-media.js'
import Icon from './icon.js'

export default function Content(props = {}) {
  let fullname = props.fullname || ''
  let occupation = props.occupation || ''
  let location = props.location || ''
  let bio = props.bio || ''
  let email = props.email || ''
  let twitter = props.twitter || ''
  let linkedin = props.linkedin || ''
  let instagram = props.instagram || ''
  let facebook = props.facebook || ''

  return `
<section
  class="
    display-flex
    flex-direction-column
    height-content
    height-auto-large
    overflow-auto-large
  "
>
  <div
    class="
      display-flex
      align-items-center-large
      justify-content-center-large
      flex-grow-1
      flex-grow-2-large
      max-width-35
      margin-right-auto
      margin-left-auto
      padding-48
      padding-5-large
    "
  >
    <div
      class="
        margin-right-auto
        margin-left-auto
      "
    >
      ${LargeHeading({
    children: fullname
  })}
      ${MediumHeading({
    children: occupation
  })}
      ${LocationLink({
    location
  })}
      <p
        class="
          margin-bottom-42
          font-size-16
          color-383D3B
        "
      >
        ${bio}
      </p>
      <div
        class="
          display-flex
          flex-wrap-wrap
          align-items-center
          justify-content-space-between
          margin-bottom-16
        "
      >
        ${MailLink({
    email
  })}
        ${SocialMedia({
    twitter,
    linkedin,
    instagram,
    facebook
  })}
      </div>
    </div>
  </div>
  <div
    class="
      display-flex
      align-items-center
      justify-content-space-between
      padding-top-16
      padding-right-32
      padding-left-32
      padding-right-48-large
      padding-bottom-16
      padding-left-48-large
      color-5A5C5B
      background-color-F2F0F3
    "
  >
    <span
      class="
        display-flex
        align-item-center
      "
    >
      <span
        class="
          margin-right-8
          color-979797
        "
      >
        Built with
      </span>
      <a
        class="
          fill-979797
          fill-hover-FD6D6D
          transition-fill
        "
        href="https://begin.com"
        target="_blank"
        rel="noopener"
      >
        ${Icon({
    class: 'fill-inherit',
    href: 'begin',
    style: 'width:4rem;height:1.2725rem;'
  })}
      </a>
    </span>
    <a
      class="
        display-block
        padding-top-8
        padding-right-16
        padding-bottom-8
        padding-left-16
        font-size-12
        font-weight-300
        text-decoration-none
        color-FFFFFF
        border-radius-pill
        background-color-979797
        background-color-hover-058AEA
        transition-background-color
        text-transform-uppercase
      "
      href="https://begin.com"
      rel="noopener"
      target="_blank"
    >
      Build yours
    </a>
  </div>
</section>
  `
}
Enter fullscreen mode Exit fullscreen mode

The Content() function calls LargeHeading() and passes in the name to be rendered. Let's look at heading-large.js.

export default function LargeHeading(props = {}) {
  let children = props.children || ''
  return `
<h1
  class="
    margin-bottom-24
    font-size-42
    font-weight-300
    color-5A5C5B
  "
>
  ${children}
</h1>
  `
}
Enter fullscreen mode Exit fullscreen mode

We can follow this same pattern for the rest of the submodules in content.js where each function is returning a string template literal of HTML.

We can now get clean HTML to the front end with dynamic information constructed from a single Lambda function.

Discussion (0)

pic
Editor guide