Let's face it, forms are everywhere across the web, and they often take significant time to build depending on the requirements.
In this tutorial we will dynamically build pages with forms using Next.js, and GraphQL.
Chapters:
- Define a solid content model
- Create the content model in Hygraph
- Create an example Page and Form with Fields as a content editor
- Reordering form fields
- Query our Page, Form and Fields with GraphQL
- Configure public API access
- Setup Next.js project with dependencies
- Build Pages programatically with Next.js
- Build our Form Field components
- Render our Form to our individual Pages
- Managing form state and submissions
- Submitting our Form to Hygraph with GraphQL Mutations
- Deploy to Vercel
Get the source code for this tutorial
TLDR;
#1. Define a solid content model
Before we dive into creating our schema, let's first think about what we're going to need to enable our marketing team to spin up landing page forms from just using the CMS.
It all starts with a Page. Pages must have a slug
field so we can easily look up content from the params of any request.
Next, for simplicity, each page will have an associated Form
model. For the sake of this tutorial, we'll pick 4 form field types;
- Input
- Textarea
- Select
- Checkbox
Form Fields
If we think of a traditional form, let's try and replace all of the data points we need to recreate a simple contact form like the following:
<form><div><label for="name">Name</label><input type="text" id="name" placeholder="Your name" required /></div><div><label for="email">Email</label><input type="email" id="email" placeholder="Your email" required /></div><div><label for="tel">Tel</label><input type="tel" id="tel" placeholder="Your contact no." /></div><div><label for="favFramework">What's your favorite framework?</label><select id="favFramework"><option value="react">React</option><option value="vue">Vue</option><option value="angular">Angular</option><option value="svelte">Svelte</option></select></div><div><label for="message">Message</label><textarea id="message" placeholder="Leave a message" /></div><div><label for="terms"><input id="terms" type="checkbox" />I agree to the terms and privacy policy.</label></div><div><button type="submit">Submit</button></div></form>
In the above form, we have some <input />
's that are required, some which are of type email
, tel
and text
, while the <select />
has no placeholder or is required.
Hygraph has support for GraphQL Union Types. This means we can define models for each of our form field types, and associate them to our Form
model as one "has many" field.
Our schema will end up looking a little something like the following...
Models
Page
- Title, String, Single line text, Required, and used as a Title
- Slug, String, Slug, Required - Specify
{title}
as the template - Form, Reference to
Form
You can use the Slug field type that automatically generates a slug based on your title. You'll need to set the template. to{title}
to make this work. You can always override slugs.
Form
- Page, Reference, Accepts multiple
Page
values - Fields, Reference, Accepts multiple
FormInput
,FormTextarea
,FormSelect
andFormCheckbox
values
FormInput
- Name, String, Single line text, and used as a Title
- Type, Enum,
FormInputType
dropdown - Label, String, Single line text
- Placeholder, Single line text
- Required, Boolean
- Form, Reference to
Form
FormTextarea
- Name, String, Single line text, and used as a Title
- Label, String Single line text
- Placeholder, String, Single line text
- Required, Boolean
- Form, Reference to
Form
FormSelect
- Name, String, Single line text, and used as a Title
- Label, String, Single line text
- Required, Boolean
- Choices, Reference, Accepts multiple
FormOption
values - Form, Reference to
Form
FormOption
- Value, String, Single line text, Required, and used as a Title
- Option, String, Single line text
- FormSelect, Reference, Belongs to
FormSelect
FormCheckbox
Name, String, Single line text, and used as a Title
- Label, String, Single line text, Required
- Required, Boolean
- Form, Reference to
Form
Enumerations
FormInputType
values
EMAIL
TEXT
TEL
🖐 You could add more, but it's not required for this tutorial.
#2. Create the models in Hygraph
Now we have an idea of how our content model looks like. Let's create the models and their associations with eachother inside Hygraph.
You'll need an account to continue. Sign up or head to the Dashboard.
- Once logged in, head to the Schema editor by selecting Schema from the side.
- Click
- Add
Asset
model. - Go ahead and create the 7 models above. Don't worry about creating relations just yet, you can do them all at once after creating the other fields.
#3. Create an example Page and Form with Fields as a content editor
So that we are able to query, and build our forms, we're going to need some content inside our models.
- Inside the Dashboard, head to the Content editor by selecting Content from the side.
- Select the Page model and click
- Create New
- Give your page a
title
andslug
. I'll call useContact Us
, andcontact
, respectively. - Now underneath
Form
, click Create and add a new form. - Inside the inline
Form
content editor, click on Create and add a new document. - From the dropdown, select FormInput.
- Inside the inline
FormInput
content editor, enter aname
,type
,label
andplaceholder
for your form field. I'll add the valuesName
,TEXT
,Your name
,Name
and set required totrue
. - Now click Save and publish.
Repeat steps 5-8 to add additional fields.
🖐 To follow along with the rest of this tutorial, I will be using the following values for my fields...
3 x FormInput
's
Name
- Name:
name
- Type:
TEXT
- Label:
Name
- Placeholder:
Your name
- Required:
true
- Name:
- Email
- Name:
email
- Type:
EMAIL
- Label:
Email
- Placeholder:
Your email
- Required:
true
- Name:
- Tel
- Name:
tel
- Type:
TEL
- Label:
Tel
- Placeholder:
Your contact no.
- Required:
false
- Name:
1 x FormTextarea
- Message
- Name:
message
- Label:
Message
- Placeholder:
Leave a message
- Required:
true
- Name:
1 x FormCheckbox
- Terms
- Name:
terms
- Label:
I agree to the terms and privacy policy.
- Required:
true
- Name:
1 x FormSelect
The FormSelect
is a little special because it also references another model FormSelect
.
First, create your FormSelect
document as usual, entering the following.
- Favourite Framework
- Name:
favFramework
- Label:
What's your favorite frontend framework?
- Required:
false
- Name:
- Next below Options, click on Create and add a new formOption.
Now for each of our choices below, repeat the steps to "Create and add a new formOption", and provide the value
/option
for each:
react
/React
vue
/Vue
angular
/Angular
svelte
/Svelte
Finally, click Save and publish on this and close each of the inline editors, making sure to publish any unsaved changes along the way.
#4. Reordering form fields
Now we have created our fields, we can now reorder them using the content editor. This may be useful if you decide to add or remove some fields later, you can order the fields exactly the way you want them to appear.
✨ Simply drag each of the Field rows into the order you want. ✨
#5. Query our Page, Form and Fields with GraphQL
We have two pages, with two separate forms:
- Contact Form
- Request a Demo
Let's start by querying for all pages and their forms using the API Playground available from the sidebar within your project Dashboard.
Query pages, form and field __typename
{pages {titleslugform {idfields {__typename}}}}
Union Type Query
As we're using Union Types for our form fields
, we must use the ... on TypeName
notation to query each of our models.
Let's go ahead and query on
all of our models we created earlier.
{pages {titleslugform {idfields {__typename... on FormInput {nametypeinputLabel: labelplaceholderrequired}... on FormTextarea {nametextareaLabel: labelplaceholderrequired}... on FormCheckbox {namecheckboxLabel: labelrequired}... on FormSelect {nameselectLabel: labeloptions {valueoption}required}}}}}
The response should look a little something like the following:
{"data": {"pages": [{"title": "Contact us","slug": "contact","form": {"id": "ckb9j9y3k004i0149ypzxop4r","fields": [{"__typename": "FormInput","name": "Name","type": "TEXT","inputLabel": "Name","placeholder": "Your name","required": true},{"__typename": "FormInput","name": "Email","type": "EMAIL","inputLabel": "Email address","placeholder": "you@example.com","required": true},{"__typename": "FormInput","name": "Tel","type": "TEL","inputLabel": "Phone no.","placeholder": "Your phone number","required": false},{"__typename": "FormSelect","name": "favFramework","selectLabel": "What's your favorite frontend framework?","options": [{"value": "React","option": "React"},{"value": "Vue","option": "Vue"},{"value": "Angular","option": "Angular"},{"value": "Svelte","option": "Svelte"}],"required": false},{"__typename": "FormTextarea","name": "Message","textareaLabel": "Message","placeholder": "How can we help?","required": true},{"__typename": "FormCheckbox","name": "Terms","checkboxLabel": "I agree to the terms and privacy policy.","required": true}]}}]}}
#6. Configure public API access
Hygraph has a flexible permissions system, which includes enabling certain user groups to do actions, and most importantly restrict who can query what data.
For the purposes of querying data to build our pages and forms, we'll enable public API queries.
To do this, go to your project Settings.
- Open the API Access page
- Enable Content from stage Published under Public API permissions
- Save ✨
That's it! You can test this works using the API Playground and selecting Environment: master Public
from the dropdown in the section above your query/result.
🖐 Make sure to copy your API Endpoint to the clipboard. We'll need it in step 8.
#7. Setup Next.js project with dependencies
Now we have our schema, and content, let's begin creating a new Next.js project with all of the dependencies we'll need to build our pages and forms.
Inside the Terminal, run the following to create a new Next.js project.
npm init next-app dynamic-hygraph-forms
When prompted, select Default starter app
from the template choices.
cd dynamic-hygraph-forms
This template will scaffold a rough folder structure following Next.js best practices.
Next, we'll install graphql-request
for making GraphQL queries via fetch.
yarn add -E graphql-request # or npm install ...
Now, if you run the project, you should see the default Next.js welcome page at http://localhost:3000
.
yarn dev # or npm run dev
#8. Build Pages programatically with Next.js
This comes in two significant parts. First we create the routes (or "paths") and then query for the data for each page with those path params.
8.1 Create programmatic page routes
First up is to add some code to our Next.js application that will automatically generate pages for us. For this we will be exporting the getStaticPaths
function from a new file called [slug].js
in our pages
directory.
touch pages/[slug].js
Having a filename with square brackets may look like a typo, but rest assured this is a Next.js convention.
Inside pages/[slug].js
add the following code to get going:
export default function Index(props) {return (<pre>{JSON.stringify(props, null, 2)}</pre>)}
If you're familiar with React already, you'll notice we are destructuring props
from the Index
function. We'll be updating this later to destructure our individual page data, but for now, we'll show the props
data on each of our pages.
Inside pages/[slug].js
, let's import graphql-request
and initialize a new GraphQLClient
client.
🖐 You'll need your API Endpoint from Step 6 to continue.
import { GraphQLClient } from "graphql-request";const hygraph = new GraphQLClient("YOUR_HYGRAPH_ENDOINT_FROM_STEP_6");
Now the hygraph
instance, we can use the request
function to send queries (with variables) to Hygraph.
Let's start by querying for all pages, and get their slugs, inside a new exported function called getStaticPaths
.
export async function getStaticPaths() {const { pages } = await hygraph.request(`{pages {slug}}`)return {paths: pages.map(({ slug }) => ({ params: { slug } })),fallback: false}}
There's quite a bit going on above, so let's break it down...
const { pages } = await hygraph.request(`{pages {slug}}`)
Here we are making a query and destructuring the response pages
from the request. This will be similar to the results we got back in step 5.
return {paths: pages.map(({ slug }) => ({ params: { slug } })),fallback: false}
Finally inside getStaticPaths
we are returning paths
for our pages, and a fallback
. These build the dynamic paths inside the root pages
directory, and each of the slugs will become pages/[slug].js
.
The fallback
is false
in this example, but you can read more about using that here.
🖐 getStaticPaths
alone does nothing, we need to next query data for each of the pages.
8.2 Query page data
Now we have programmatic paths being generated for our pages, it's now time to query the same data we did in step 5, but this time, send that data to our page.
Inside pages/[slug].js
, export the following function:
export async function getStaticProps({ params: variables }) {const { page } = await hygraph.request(`query page($slug: String!) {page(where: {slug: $slug}) {titleslugform {fields {__typename... on FormInput {nametypeinputLabel: labelplaceholderrequired}... on FormTextarea {nametextareaLabel: labelplaceholderrequired}... on FormCheckbox {namecheckboxLabel: labelrequired}... on FormSelect {nameselectLabel: labeloptions {valueoption}required}}}}}`,variables);return {props: {page,},};}
Now just like before, there's a lot going on, so let's break it down...
export async function getStaticProps({ params: variables }) {// ...}
Here we are destructuring the params
object from the request sent to our page. The params here will be what we sent in getStaticPaths
, so we'd expect to see slug
here.
🖐 As well as destructuring, we are also renaming (or reassigning) the variable params
to variables
.
const { page } = await hygraph.request(`...`, variables);return {props: {page,},};
Next we're sending the same query we did in step 5, but this time we've given the query a name page
which expects the String
variable slug
.
Once we send on our renamed params
as variables
, we return an object with our page
inside of props
.
Now all that's left to do is run our Next development server and see our response JSON on the page!
yarn dev # or npm run dev
Now you should see at http://localhost:3000/contact
the data from Hygraph for our Page.
#9. Build our Form Field components
We are now ready to dynamically build our form using the data from Hygraph.
The __typename
value will come in handy when rendering our form, as this will decide which component gets renderered.
Inside a new directory components
, add a Form.js
file.
mkdir componentstouch components/Form.js
In this this file, we will create the structure of our basic form, and map
through each of our fields
to return the appropreciate field.
Add the following code to components/Form.js
import * as Fields from "./FormFields";export default function Form({ fields }) {if (!fields) return null;return (<form>{fields.map(({ __typename, ...field }, index) => {const Field = Fields[__typename];if (!Field) return null;return <Field key={index} {...field} />;})}<button type="submit">Submit</button></form>);}
Once you have this component setup, now create the file components/FormFields/index.js
and add the following:
export { default as FormCheckbox } from "./FormCheckbox";export { default as FormInput } from "./FormInput";export { default as FormSelect } from "./FormSelect";export { default as FormTextarea } from "./FormTextarea";
All we're doing in this file is importing each of our different form fields and exporting them.
The reason we do this is that when we import using import * as Fields
, we can grab any of the named exports by doing Fields['FormCheckbox']
, or Fields['FormInput']
like you see in components/Form.js
.
Now that that we are importing these new fields, we next need to create each of them!
For each of the imports above, create new files inside components/FormFields
for:
FormCheckbox.js
FormInput.js
FormSelect.js
FormTextarea.js
Once these are created, let's export each of the components as default, and write a minimum amount of code to make them work.
The code in the below files isn't too important. What's key about this tutorial is how we can very easily construct forms, and in fact any component or layout, using just data from the CMS. Magic! ✨
FormCheckbox.js
export default function FormCheckbox({ checkboxLabel, ...rest }) {const { name } = rest;return (<div><label htmlFor={name}><input id={name} type="checkbox" {...rest} />{checkboxLabel || name}</label></div>);}
FormInput.js
Since this component acts as a generic <input />
, we will need to lowercase the type
enumeration to pass to the input.
export default function FormInput({ inputLabel, type: enumType, ...rest }) {const { name } = rest;const type = enumType.toLowerCase();return (<div>{inputLabel && <label htmlFor={name}>{inputLabel || name}</label>}<input id={name} type={type} {...rest} /></div>);}
FormSelect.js
export default function FormSelect({ selectLabel, options, ...rest }) {const { name } = rest;if (!options) return null;return (<div><label htmlFor={name}>{selectLabel || name}</label><select id={name} {...rest}>{options.map(({ option, ...opt }, index) => (<option key={index} {...opt}>{option}</option>))}</select></div>);}
FormTextarea.js
export default function FormTextarea({ textareaLabel, ...rest }) {const { name } = rest;return (<div><label htmlFor={name}>{textareaLabel || name}</label><textarea id={name} {...rest} /></div>);}
We're done on the form components, for now...!
#10. Render our Form to our individual Pages
Let's recap...
- We have our form model and content coming from Hygraph
- We have our form fields created
- We have our form pages automatically created
Let's now render the form we created in step 9 to our page.
Inside pages/[slug].js
, we'll need to import our Form component and return that inside of the default export.
Below your current import (graphql-request
), import our Form component:
import Form from "../components/Form";
Lastly, update the default export to return the <Form />
.
export default function Index({ page }) {const { form } = page;return <Form {...form} />;}
Next run the Next.js development server:
yarn dev # or npm run dev
Once the server has started, head to http://localhost:3000/contact
(or a slug
you defined in the CMS) to see your form!
I'll leave the design and UI aesthetics up to you!
As far as creating dynamic forms with React, Next.js and GraphQL goes, this is it! Next we'll move onto enhancing the form to be accept submissions.
#11. Managing form state and submissions
In this step we will install a library to handle our form state, and submissions, as well as create an onSubmit
that'll we'll use in Step 12 to forward onto Hygraph.
Inside the terminal, let's install a new dependency:
yarn add -E react-hook-form # or npm install ...
Now it's not essential we use react-hook-form
for managing our form, I wanted to provide a little closer to real world scenario than your typical setState
example that are used in tutorials.
After we complete this tutorial, you should be in a position to return to each of your form fields, add some CSS, error handling, and more, made easy with react-hook-form
!
Inside components/Form.js
, add the following import to the top of the file:
import { useForm, FormContext } from "react-hook-form";
Then inside your Form
function after you return null
if there are no fields
, add the following:
const { handleSubmit, ...methods } = useForm();const onSubmit = (values) => console.log(values);
Finally, you'll need to wrap the current <form>
with <FormContext {...methods}>
, and add a onSubmit
prop to the <form>
that is onSubmit={handleSubmit(onSubmit)}
.
Your final components/Form.js
should look like this:
import { useForm, FormContext } from "react-hook-form";import * as Fields from "./FormFields";export default function Form({ fields }) {if (!fields) return null;const { handleSubmit, ...methods } = useForm();const onSubmit = (values) => console.log(values);return (<FormContext {...methods}><form onSubmit={handleSubmit(onSubmit)}>{fields.map(({ __typename, ...field }, index) => {const Field = Fields[__typename];if (!Field) return null;return <Field key={index} {...field} />;})}<button type="submit">Submit</button></form></FormContext>);}
Now all that's happening here is we're initializing a new react-hook-form
instance, and adding a FormContext
provider around our form + fields.
Next we'll need to update each of our FormFields/*.js
and register
them with the react-hook-form
context.
First update components/FormFields/FormInput.js
to include the hook useFormContext
from react-hook-form
.
At the top of the file add the following import:
import { useFormContext } from 'react-hook-form'
Then inside the FormInput
function, add the following before the return
:
const { register } = useFormContext();
Now all that's left to do add register
as a ref
to our <input />
and pass in the required
value.
<inputref={register({ required: rest.required })}id={name}type={type}{...rest}/>
The final FormInput
should look like:
import { useFormContext } from "react-hook-form";export default function FormInput({ inputLabel, type: enumType, ...rest }) {const { register } = useFormContext();const { name } = rest;const type = enumType.toLowerCase();return (<div>{inputLabel && <label htmlFor={name}>{inputLabel || name}</label>}<inputref={register({ required: rest.required })}id={name}type={type}{...rest}/></div>);}
Great! Now let's do the same for the other 3 field components:
FormCheckbox.js
import { useFormContext } from "react-hook-form";export default function FormCheckbox({ checkboxLabel, ...rest }) {const { register } = useFormContext();const { name } = rest;return (<div><label htmlFor={name}><inputref={register({ required: rest.required })}id={name}type="checkbox"{...rest}/>{checkboxLabel || name}</label></div>);}
FormSelect.js
import { useFormContext } from "react-hook-form";export default function FormSelect({ selectLabel, options, ...rest }) {if (!options) return null;const { register } = useFormContext();const { name } = rest;return (<div><label htmlFor={name}>{selectLabel || name}</label><select ref={register({ required: rest.required })} id={name} {...rest}>{options.map(({ option, ...opt }, index) => (<option key={index} {...opt}>{option}</option>))}</select></div>);}
FormTextarea.js
import { useFormContext } from "react-hook-form";export default function FormTextarea({ textareaLabel, ...rest }) {const { register } = useFormContext();const { name } = rest;return (<div><label>{textareaLabel || name}</label><textarearef={register({ required: rest.required })}htmlFor={name}id={name}{...rest}/></div>);}
🖐 Let's start the Next.js development server, and view the console when we submit the form!
yarn dev # or npm run dev
Once the server has started, head to http://localhost:3000/contact
(or a slug
you defined in the CMS) to see your form!
Open the browser developer tools console, and then fill out the form and click submit!
You should now see the form values submitted!
#12. Submitting our Form to Hygraph with GraphQL Mutations
It's now time to take our form to the next level. We are going to update our Hygraph schema with a new Submission
model that will be used to store submissions.
Inside the Hygraph Schema Editor, click + Add to create a new model.
- Give the model a name of
Submission
, - Add a new JSON Editor field with the Display Name
Form Data
, and, API ID asformData
, - Add a new Reference field with the Display Name/API ID
Form
/form
, and selectForm
as the Model that can be referenced, - Configure the reverse field to Allow multiple values and set the default Display Name/API ID to (
Submissions
/submissions
) respectively.
Things should look a little something like the following:
And the Form
model should now have a new field submisson
:
Since we want full control via the CMS what appears on our form, we'll just save all of that data inside formData
JSON field.
🖐 Using something like webhooks would enable you to forward formData
onto a service like Zapier, and do what you need to with the data, all without writing a single line of code! ✨
In order to use the Mutations API, we'll need to configure our API access to permit mutations and create a dedicated Permanent Auth Token. Don't enable Mutations for the Public API, as anybody will be able to query/mutate your data!
Head to Settings > API Access > Permanent Auth Tokens
and create a token with the following setup:
Next, Copy
the token to the clipboard once created.
Inside of the root of your Next.js project, create the file .env
and, add the following, replacing YOUR_TOKEN_HERE
with your token:
HYGRAPH_MUTATION_TOKEN=YOUR_TOKEN_HERE
With this token added, let's also do some housekeeping. Replace the API Endpoint you created in/pages/[slug].js
with a the .env
variable HYGRAPH_ENDPOINT
and assign the value inside .env
:
// pages/[slug].js// ...const hygraph = new GraphQLClient(process.env.HYGRAPH_ENDPOINT);// ...
Now before we can use the HYGRAPH_MUTATION_TOKEN
, we'll need to update our components/Form/index.js
to POST
the values to a Next.js API route.
Inside the form, let's do a few things:
- import
useState
from React, - Invoke
useState
inside yourForm
function, - Replace the
onSubmit
function, - Render
error
after the submit<button />
import { useState } from 'react'// ...export default function Form({ fields }) {if (!fields) return null;const [success, setSuccess] = useState(null);const [error, setError] = useState(null);// ...const onSubmit = async (values) => {try {const response = await fetch("/api/submit", {method: "POST",body: JSON.stringify(values),});if (!response.ok)throw new Error(`Something went wrong submitting the form.`);setSuccess(true);} catch (err) {setError(err.message);}};if (success) return <p>Form submitted. We'll be in touch!</p>;return (// ...<button type="submit">Submit</button>{error && <span>{error}</span>}})}
Finally we'll create the API route /api/submit
that forwards requests to Hygraph securely. We need to do this to prevent exposing our Mutation Token to the public.
One of the best ways to scaffold your mutation is to use the API Playground inside your Hygraph project. It contains all of the documentation and types associated with your project/models.
If you've followed along so far, the following mutation is all we need to create + connect form submissions.
mutation createSubmission($formData: Json!, $formId: ID!) {createSubmission(data: {formData: $formData, form: {connect: {id: $formId}}}) {id}}
The createSubmission
mutation takes in 2 arguments; formData
and formId
.
In the onSubmit
function above, we're passing along values
which will be our formData
. All we need to do now is pass along the form ID!
We are already querying for the form id
inside pages/[slug].js
, so we can use this id
passed down to the Form
component.
Inside components/Form.js
, destructure id
when declaring the function:
export default function Form({ id, fields }) {// ...}
.... and then pass that id
into the onSubmit
body
:
const response = await fetch("/api/submit", {method: "POST",body: JSON.stringify({ id, ...values }),});
Then, inside the pages
directory, create the directory/file api/submit.js
, and add the following code:
import { GraphQLClient } from "graphql-request";export default async ({ body }, res) => {const { id, ...data } = JSON.parse(body);const hygraph = new GraphQLClient(process.env.HYGRAPH_ENDPOINT, {headers: {authorization: `Bearer ${process.env.HYGRAPH_MUTATION_TOKEN}`,},});try {const { createSubmission } = await hygraph.request(`mutation createSubmission($data: Json!, $id: ID!) {createSubmission(data: {formData: $data, form: {connect: {id: $id}}}) {id}}`,{data,id,});res.status(201).json(createSubmission);} catch ({ message }) {res.status(400).json({ message });}};
That's it! ✨
Now go ahead and submit the form, open the content editor and navigate to the Submission
content.
You should see your new entry!
You could use Hygraph webhooks to listen for new submissions, and using another API route forward that onto a service of your choice, such as email, Slack or Zapier.
#13. Deploy to Vercel
Now all that's left to do is deploy our Next.js site to Vercel. Next.js is buil, and managed by the Vercel team and the community.
To deploy to Vercel, you'll need to install the CLI.
npm i -g vercel # or yarn global add vercel
Once installed, all it takes to deploy is one command!
vercel # or vc
You'll next be asked to confirm whether you wish to deploy the current directory, and what the project is named, etc. The defaults should be enough to get you going! 😅
Once deployed, you'll get a URL to your site. Open the deployment URL and append /contact
to see your form!
Blog Author
Jamie Barton
Jamie is a software engineer turned developer advocate. Born and bred in North East England, he loves learning and teaching others through video and written tutorials. Jamie currently publishes Weekly GraphQL Screencasts.