The author selected the Diversity in Tech Fund to receive a donation as part of the Write for DOnations program.
Landing pages are web pages that promote a product or service, providing a place for customers to land when arriving at a site. For businesses, they often are the destination of links in online advertisments and marketing emails. A commercial landing page’s primary goal is to turn visitors into potential clients or customers. Because of this, building a landing page is a valuable skill for a web developer.
In this tutorial, you will build a landing page with the following two technologies:
Gatsby, a React-based frontend framework designed to generate static websites. Gatsby allows you to generate landing pages quickly, which can be useful when creating many landing pages for different projects.
TypeScript is a superset of JavaScript that introduces static types and type-checking at build-time. TypeScript has become one of the most widely used alternatives to JavaScript because of its strong typing system, which alerts developers to problems in code before the code gets into proudction. For the landing page, TypeScript will help guard against invalidly typed data for dynamic values, such as the sales pitch text or the signup form input.
The example landing page you will build in this tutorial will promote a bookstore, and will include the following common components of a landing page:
The sample project will look like the following image by the end of the tutorial:
You will need Node and npm installed on your machine to run a development server and work with packages. This tutorial has been tested with Node.js version 14.17.2 and npm 6.14.13. To install on macOS or Ubuntu 20.04, follow the steps in How to Install Node.js and Create a Local Development Environment on macOS or the Installing Using a PPA section of How To Install Node.js on Ubuntu 20.04.
You will need the Gatsby CLI tool installed and a new Gatsby site made from the gatsby-starter-default
template. Follow Step 1 in How to Set Up Your First Gatsby Website to start a new project. This tutorial will refer to this project as bookstore-landing-page
.
You’ll need to set up your Gatsby project to work with TypeScript. Follow Step 1 through Step 4 in How To Set Up a Gatsby Project with TypeScript to install appropriate dependencies, create a new tsconfig.json
file, and refactor the seo.tsx
file in TypeScript.
You will need to be familiar with React components and JSX, since Gatsby is a React-based framework. This tutorial will also include using event handlers in forms. You can learn these concepts and more in our How To Code in React.js series.
Header
and Layout
ComponentsIn this step, you will begin by refactoring the existing header.tsx
and layout.tsx
components of the bookstore-landing-page
project that you created in the prerequisite tutorial. This will include replacing default type-definitions with custom-type interfaces and revising some GraphQL queries. Once you have completed this step, you will have populated the header of your landing page with the page’s title and description and created a layout component to implement future components.
Header
The Header
component will display the title and description of your page at the top of the browser window. But before refactoring this component, you will open the gatsby-config.js
file in the project’s root directory to update the site’s metadata. Later, you will query gatsby-config.js
from the Layout
component to retrieve this data.
Open gatsby-config.js
in your text editor of choice. Under siteMetaData
in the exported module, change the value of title
and description
to the name of the bookstore and a business slogan, as shown in the following highlighted code:
module.exports = {
siteMetadata: {
title: `The Page Turner`,
description: `Explore the world through the written word!`,
author: `@gatsbyjs`,
},
plugins: [
...
After making these changes, save and close the gatsby-config.js
file.
Next, inside the bookstore-landing-page/src/components
directory, open the header.tsx
file. From here you will refactor the <Header />
component to use TypeScript typing instead of the default PropTypes
. Make the following changes to your code:
import * as React from "react"
import { Link } from "gatsby"
interface HeaderProps {
siteTitle: string,
description: string
}
const Header = ({ siteTitle, description }: HeaderProps) => (
...
)
export default Header
You deleted the Header.PropTypes
and Header.defaultProps
objects after the Header
declaration and replaced them with a custom-type interface HeaderProps
, using the siteTitle
and description
properties. Then, you added description
to the list of arguments passed to the functional component and assigned them to the HeaderProps
type. The newly defined HeaderProps
interface will act as a custom type for the arguments passed to the <Header/>
component from the GraphQL query in the <Layout/>
component.
Next, in the JSX of the <Header />
component, change the styling in the opening header
tag so the background color is blue and the text is center-aligned. Keep siteTitle
in the embedded <Link/>
component, but add description
to a separate <h3/>
tag and give it a font color of white:
...
const Header = ({ siteTitle, description }: HeaderProps) => (
<header
style={{
background: `#0069ff`,
textAlign: `center`,
}}
>
<div
style={{
margin: `0 auto`,
maxWidth: 960,
padding: `1.45rem 1.0875rem`,
}}
>
<h1 style={{ margin: 0 }}>
<Link
to="/"
style={{
color: `white`,
textDecoration: `none`,
}}
>
{siteTitle}
</Link>
</h1>
<h3 style={{
color: 'white'
}}>
{description}
</h3>
</div>
</header>
)
export default Header
Now you will have inline styling when data is passed to this component.
Save the changes in the header.tsx
file, then run gatsby develop
and go to localhost:8000
on your browser. The page will look like the following:
Notice the description has not yet been rendered. In the next step, you will add this to the GraphQL query in layout.tsx
to ensure that it is displayed.
With the <Header/>
component ready, you can now refactor the default <Layout/>
component for the landing page.
Layout
The <Layout />
component will wrap your landing page, and can help share styles and formatting for future pages on your site.
To start editing this component, open layout.tsx
in your text editor. Delete the default type definitions at the end of the file and define a new interface named LayoutProps
after the import
statements. Then, assign the interface type to the arguments passed to <Layout/>
:
/**
* Layout component that queries for data
* with Gatsby's useStaticQuery component
*
* See: https://www.gatsbyjs.com/docs/use-static-query/
*/
import * as React from "react"
import { useStaticQuery, graphql } from "gatsby"
import Header from "./header"
import "./layout.css"
interface LayoutProps {
children: ReactNode
}
const Layout = ({ children }: LayoutProps) => {
...
}
default export Layout
The interface uses the ReactNode
type, which you imported with the React library. This type definition applies to most React child components, which is what <Layout/>
renders by default. This will enable you to define a custom-type interface for <Layout/>
.
Next, revise the default GraphQL query located inside the <Layout/>
component. Inside of the siteMetaData
object, add the description
that was set in gatsby-config.js
. Then, like with siteTitle
, store the fetched value in a new description
variable:
...
const Layout = ({ children }: LayoutProps) => {
const data = useStaticQuery(graphql`
query SiteTitleQuery {
site {
siteMetadata {
title
description
}
}
}
`)
const siteTitle = data.site.siteMetadata?.title || `Title`
const description = data.site.siteMetadata?.description || 'Description'
...
Now you can pass description
as a prop to the <Header/>
component in the layout’s returned JSX. This is important because description
was defined as a required property in the HeaderProps
interface:
...
return (
<>
<Header siteTitle={siteTitle} description={description}/>
...
</>
)
export default Layout
Save and exit from the layout.tsx
file.
As a final change to your layout, go into layouts.css
to make a styling change by centering all text in the body
of the page:
...
/* Custom Styles */
body {
margin: 0;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: hsla(0, 0%, 0%, 0.8);
font-family: georgia, serif;
font-weight: normal;
word-wrap: break-word;
font-kerning: normal;
-moz-font-feature-settings: "kern", "liga", "clig", "calt";
-ms-font-feature-settings: "kern", "liga", "clig", "calt";
-webkit-font-feature-settings: "kern", "liga", "clig", "calt";
font-feature-settings: "kern", "liga", "clig", "calt";
text-align: center;
}
...
Save and close the layout.css
file, then start the development server and render your site in the browser. You will now find the description
value rendered in the header:
Now that you have refactored the base files for your Gatsby site, you can add a hero image to your page to make it more visually appealing to customers.
A hero image is a visual that lends support to the product or service in the landing page. In this step, you will download an image for your bookstore landing page and render it on the site using the <StaticImage />
component of the gatsby-plugin-image
plugin.
Note: This project is using Gatsby version 3.9.0, so you won’t be able to use the deprecated gatsby-image
package. This package was replaced with gatsby-plugin-image
. This new plugin, along with help from gatsby-plugin-sharp
, will render responsive images with processing functionality.
First, download the bookshelf image from Unsplash, a site that provides images that you can use freely:
- curl https://images.unsplash.com/photo-1507842217343-583bb7270b66 -o src/images/bookshelf.png
This command uses curl
to download the image. The -o
flag designates the output, which you have set to be a file named bookshelf.png
in the images
directory.
Now open the src/pages/index.tsx
file. The Gatsby default starter template already has a <StaticImage/>
component, so replace the attributes to point to your newly downloaded image:
import * as React from "react"
import { StaticImage } from "gatsby-plugin-image"
import Layout from "../components/layout"
import Seo from "../components/seo"
const IndexPage = () => (
<Layout>
<Seo title="Home" />
<StaticImage
src="../images/bookshelf.png"
alt="Bookshelf hero image"
/>
</Layout>
)
export default IndexPage
You added a src
attribute to direct Gatsby to the correct image in your images
directory, then added the alt
attribute to provide alternative text for the image.
Save and close the file, then restart the development server. Your page will now have the downloaded bookshelf image rendered at its center:
With your image now rendered on your site, you can move on to adding some content to the page.
For the next part of the landing page, you are going to build a new component that holds the sales pitch for your bookstore. This will explain why your customers should come to your store.
Inside of bookstore-landing-page/src/components
, go ahead and create a new file titled salesPitchAndFeatures.tsx
. Inside the new file, import React
, create a new functional component called SalesPitchAndFeatures
, and export it:
import * as React from "react"
const SalesPitchAndFeatures = () => {
<>
</>
}
export default SalesPitchAndFeatures
The interface for this component will include an optional salesPitch
property of type string
. It will also have a list of features
of type Array<string>
, which is required:
import * as React from "react"
interface SalesPitchAndFeaturesProps {
salesPitch?: string
features: Array<string>
}
...
The data for the salesPitch
and features
will be hard-coded within salesPitchAndFeatures.tsx
, but you could also store it in another place (like gatsby-config.js
) and query the needed data with GraphQL. The content
object will be of type SalesPitchAndFeaturesProps
:
...
interface salesPitchAndFeaturesProps {
salesPitch?: string
features: Array<string>
}
const content: SalesPitchAndFeaturesProps = {
salesPitch: "Come and expand your world at our bookstore! We are always getting new titles for you to see. Everything you need is here at an unbeatable price!",
features: [
"Tens of thousands of books to browse through",
"Engage with the community at a book club meeting",
"From the classics to the newest publications, there's something for everybody!"
]}
const SalesPitchAndFeatures = () => {
return (
<>
...
Notice that the salesPitch
prop is a string and the features
prop is an array of strings, just as you set them in your interface.
You’ll also need a function that will display the list of features. Create a showFeatures(f)
function.
...
const showFeatures: any = (f: string[]) => {
return f.map(feature => <li>{feature}</li>)
}
const SalesPitchAndFeatures = () => {
return (
<>
...
The argument f
passed into showFeatures
is of type Array<string>
to be consistent with the array of features of type string
. To return the list tranformed into rendered JSX, you use the .map()
array method.
Populate the return
statement with your content, wrapped in divs
with assigned class names for styling:
...
const SalesPitchAndFeatures = () => {
return (
<div className='features-container'>
<p className='features-info'>
{content.salesPitch}
</p>
<ul className='features-list'>
{showFeatures(content.features)}
</ul>
</div>
)
}
export default SalesPitchAndFeatures
Save and close salesPitchAndFeatures.tsx
.
Next, open layout.css
to add styling to the class names added in the <SalesPitchAndFeatures/>
component:
...
.features-container {
border: 1px solid indigo;
border-radius: 0.25em;
padding: 2em;
margin: 1em auto;
}
.features-list {
text-align: left;
margin: auto;
}
This adds a border around the sales pitch and features list, then adds spacing between the elements to increase readability.
Save and close layout.css
.
Lastly, you will render this component on the landing page. Open index.tsx
in the src/pages/
directory. Add the <SalesPitchAndFeatures/>
component to the rendered layout children:
import * as React from "react"
import { StaticImage } from "gatsby-plugin-image"
import Layout from "../components/layout"
import SalesPitchAndFeatures from "../components/salesPitchAndFeatures"
import SEO from "../components/seo"
const IndexPage = () => (
<Layout>
<SEO title="Home" />
<div style={{ maxWidth: `450px`, margin: ' 1em auto'}}>
<StaticImage
src="../images/bookshelf.png"
alt="Bookshelf hero image"
/>
<SalesPitchAndFeatures/>
</div>
</Layout>
)
export default IndexPage
You also added in a div
to apply some styling to both the image and the sales pitch.
Save and exit from the file. Restart your development server and you will find your sales pitch and features list rendered below your image:
You now have a sales pitch on your landing page, which will help communicate to potential customers why they should go to your business. Next, you will build the final component for the landing page: an email signup form.
An email signup button is a common landing page component that lets the user enter their email address and sign up for more news and information about the product or business. Adding this to your landing page will give the user an actionable step they can take to become your customer.
To start, create a new file in bookstore-landing-page/src/components
called signupForm.tsx
. This component won’t have any custom types but will have an event handler, which has its own special React-based type.
First, build the <SignUpForm/>
component and its return
statement, with a header inside:
[label bookstore-landing-page/src/components/signupForm.tsx]
import * as React from "react"
const SignUpForm = () => {
return (
<h3>Sign up for our newsletter!</h3>
)
}
export default SignupForm
Next, add some markup to create a form
element with an onSubmit
attribute, initialized to null
for now. Soon this will contain the event handler, but for now, finish writing the form with label
, input
, and button
tags:
import * as React from "react"
const SignUpForm = () => {
return (
<React.Fragment>
<h3>Sign up for our newsletter!</h3>
<form onSubmit={null}>
<div style={{display: 'flex'}}>
<input type='email' placeholder='email@here'/>
<button type='submit'>Submit</button>
</div>
</form>
<React.Fragment>
)
}
export default SignupForm
If you type your email address in the text field and click Sign Up on the rendered landing page right now, nothing will happen. This is because you still need to write an event handler that will trigger whenever the form is submitted. A best practice is to write a separate function outside the return
statement for the event handler. This function usually has a special e
object that represents the triggered event.
Before writing the separate function, you will write the function in-line to figure out what the static type of the event object is, so that you can use that type later:
...
return (
<React.Fragment>
<h3>Sign up for our newsletter!</h3>
<form onSubmit={(e) => null}>
<div style={{display: 'flex'}}>
<input type='email' placeholder='email@here' style={{width: '100%'}}/>
<button type="submit">Submit</button>
</div>
</form>
</React.Fragment>
)
...
}
export default SignupForm
If you are using a text editor like Visual Studio Code, hovering your cursor over the e
parameter will use TypeScript’s IntelliSense to show you its expected type, which in this case is React.FormEvent<HTMLFormElement>
.
Now that you know what the expected type of your separate function will be, go ahead and use this to write a new, separate function called handleSubmit
:
import * as React from "react"
const SignupForm = () => {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
alert(alert('The submit button was clicked! You\'re signed up!'))
}
return (
<React.Fragment>
<h3>Sign up for our newsletter!</h3>
<form onSubmit={handleSubmit}>
<div style={{display: 'flex'}}>
<input type='email' placeholder='email@here'/>
<button type='submit'>Submit</button>
</div>
</form>
</React.Fragment>
)
}
export default SignupForm
The handleSubmit
function will now trigger a browser alert when the form is submitted. Note that this is a temporary placeholder; to actually add the user to an email list, you would have to connect this to a back-end database, which is beyond the scope of this tutorial.
Save and close the signupForm.tsx
file.
Now, open the index.tsx
file and add the new <SignupForm/>
component:
import * as React from "react"
import { StaticImage } from "gatsby-plugin-image"
import Layout from "../components/layout"
import SalesPitchAndFeatures from "../components/salesPitchAndFeatures"
import SignupForm from "../components/signupForm"
import Seo from "../components/seo"
const IndexPage = () => (
<Layout>
<Seo title="Home" />
<div style={{ maxWidth: `450px`, margin: ' 1em auto'}}>
<HeroImage />
<SalesPitchAndFeatures />
<SignupForm />
</div>
</Layout>
)
export default IndexPage
Save and exit the file.
Restart your development server, and you will find your completed landing page rendered in your browser, along with the email signup button:
You have now finished building all the core components of your bookstore landing page.
Because Gatsby creates fast static websites and TypeScript allows for data to be statically typed, building a landing page makes for an excellent use case. You can shape the types of its common elements (header, hero image, email signup, etc.) so that incorrect forms of data will trigger errors before they go into production. Gatsby provides the bulk of the structure and styling of the page, allowing you to build on top of it. You can use this knowledge to build other landing pages to promote other products and services quicker and more efficiently.
If you’d like to learn more about TypeScript, check out our How To Code in TypeScript series, or try our How To Create Static Web Sites with Gatsby.js series to learn more about Gatsby.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Hi Brandon.
Saw your landing page. Wanted to talk with you about using this, and to see if you would be willing to be a guru/tutor on the changes.
Of course there would be cash to you or the target group in your name.
I’ve got a basic idea of what I want in terms of functions/sections/etc. Working with someone like you would save a good deal of time.
thanks -bruce badouglas@gmail.com