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)