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:
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' }) } }
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> ` }
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> ` }
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> ` }
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.
Top comments (0)