A sales funnel is an online marketing tool that is built and designed to capture leads from traffic and convert them into customers. They tend to convert 20% to 50% higher than ordinary web pages. A sales funnel typically consists of opt-in pages, order forms, shopping carts, checkout pages and email marketing software. Building such a system is not a walk in the park.
The common way of building a sales funnel today is by purchasing a monthly subscription plan from a sales funnel builder platform. The most popular provider currently charges about $100 to $300. There are other affordable options. However, you may encounter limitations or technical challenges with any provider you work with — some more severe than others.
If you don’t want to pay for a subscription plan, then you will have to build one yourself. Historically, coding your own funnel has been more expensive and time-consuming. However, we are living in 2019. The technology used by web developers today has improved immensely in the last 10 years.
It’s easier and faster to build and deploy a web application. We have tons of third-party providers that allow integrations to their platforms via remote APIs. This allows us to easily implement heavy-duty features without having to write the code ourselves.
The benefits of owning your own funnel code means your business will be more resilient. You can easily switch servers if something doesn’t work out with your provider. You can also easily scale up your online business without meeting major obstacles.
In this tutorial, I’ll show you how to code your own simple sales funnel with Vue that will help you promote a product or service that you are selling to consumers. We’ll build a simple squeeze page funnel for collecting leads for your email list.
This article assumes that you have at least a solid grasp in:
You’ll need to have a modern version of Node.js and the Vue CLI tool installed in your system. At the time of writing this article, Node v10.15.1 was the current LTS. The current Vue CLI version tool is v3.4.1. My personal recommendation is to use nvm to keep your Node.js environment up-to-date. To install the Vue.js CLI tool, execute the command:
npm install @vue/cli
You can access the full source for this project onGitHub. There is also a live demo of this project. We’ll be using Bootstrap-vue as our primary CSS framework. Please make sure to read the docs if you are new to this framework.
For this project, we are going to build a two page funnel consisting of an opt-in page — a.k.a squeeze page — and a thank you page. The setup will look something like this:
The opt-in page captures a visitor’s email address and saves it to an email list. At the same time, it moves the visitor to the next page. It is that simple. You can even do it using just plain HTML, CSS and JavaScript. Why are we even doing it in Vue.js?
The reason is because we could want to build different types of funnels promoting the same product, or different products. We wouldn’t want to repeat the same code we implemented earlier in another funnel.
Vue.js is the perfect solution that will allow us to build re-usable components that will be easy to maintain and update. In addition, we can package our components and publish it to a npm registry, making it available to all our future funnel projects.
To publish our funnel, we will have to do it in a framework like Nuxt.js. This is because Vue.js is mostly a view layer technology. We can install our sales funnel library in a Nuxt.js project. Nuxt.js is more of a full framework in that it allows code to run both on the server and the client side. It also supports a ton of useful features such as SEO.
Unfortunately, it won’t be possible to perform most of the steps mentioned above in a single tutorial. What we’ll do instead is build the two page funnel. I’ll show you how to design the components such that they are easy to re-use.
This way you can package the project later and install it in another funnel project. I’ll also provide additional links in regards to packaging a Vue.js project and deploying it to a private npm server.
With this plan in mind, let’s move on and start building our funnel library project!
Open a console terminal and create a new Vue project.
vue create vue-sales-funnel
Use the following settings:
ESlint
+ Prettier, Lint on Save, Lint and fix on commit (or choose your preferred options)Next, install Bootstrap-Vue
:
cd vue-sales-funnel
npm install vue bootstrap-vue bootstrap
Next open the project in your favorite editor. If you have Visual Studio Code, you can launch it like this:
code .
Update src\main.js
as follows:
import Vue from "vue";
import BootstrapVue from "bootstrap-vue";
import App from "./App.vue";
import router from "./router";
import "bootstrap/dist/css/bootstrap.css";
import "bootstrap-vue/dist/bootstrap-vue.css";
Vue.config.productionTip = false;
Vue.use(BootstrapVue);
new Vue({
router,
render: h => h(App)
}).$mount("#app");
This should set up the Bootstrap CSS framework in your project. Next, update src\App.js
as follows:
<template>
<div id="app">
<router-view />
</div>
</template>
Rename the following files as follows:
src/views/Home.vue
=> Optin.vue
src/views/About.vue
=> Thank-you.vue
Replace the existing code in src/views/Optin.vue
as follows:
<template>
<div class="optin">
<b-row>
<b-col>
<p>Squeeze Funnel Page</p>
</b-col>
</b-row>
</div>
</template>
<script>
export default {
name: "optin"
};
</script>
Update the code in src\router.js
as follows:
import Vue from "vue";
import Router from "vue-router";
import Optin from "./views/Optin.vue";
import ThankYou from "./views/Thank-you.vue";
Vue.use(Router);
export default new Router({
mode: "history",
base: process.env.BASE_URL,
routes: [
{
path: "/",
name: "optin",
component: Optin
},
{
path: "/thank-you",
name: "thank-you",
component: ThankYou
}
]
});
You can now fire up your project server using the command npm run serve
. Open the browser and check the links localhost:8080 and localhost:8080/thank-you are working as expected. If so, you can proceed to the next section. If not, check your work.
The first component that we will build is a Text component. We can easily use <h1>
and <p>
tags. However, we need preset elements that already have a style applied to them. We need to quickly build a page in 10 minutes or less without thinking about styling.
Delete the component src/components/HelloWorld.vue
and create TextComponent.vue
in its place. Copy the following code:
<template>
<div class="text-content" v-bind:style="{ marginTop: topMargin }">
<h1 class="header" v-if="variant === 'header'">{{ content }}</h1>
<h2 class="subheader" v-if="variant === 'subheader'">{{ content }}</h2>
<p class="paragraph" v-if="variant === 'paragraph'" v-html="content"></p>
</div>
</template>
<script>
export default {
name: "TextContent",
props: {
variant: String,
content: String,
topMargin: String
}
};
</script>
<style scoped>
.header {
color: rgb(50, 50, 50);
text-align: center;
}
.subheader {
font-size: 1.5rem;
color: rgb(100, 100, 100);
text-align: center;
}
p {
color: rgb(124, 124, 124);
}
</style>
Let’s test it out by making the following changes in src/views/Optin.vue
:
<template>
<div class="optin container">
<b-row>
<b-col>
<TextContent
variant="header"
content="Here’s Your Attention Getting Headline"
topMargin="50px"
/>
<TextContent
variant="subheader"
content="This is your sub-headline to increase credibility or curiosity"
/>
</b-col>
</b-row>
<b-row>
<b-col>
put image here
</b-col>
<b-col>
<TextContent
variant="paragraph"
content="Here’s where you write your compelling message, keep your sentences and paragraphs short."
/>
</b-col>
</b-row>
</div>
</template>
<script>
import TextContent from "../components/TextComponent";
export default {
name: "optin",
components: {
TextContent
}
};
</script>
Refresh your page. You should have something like this:
You may have noticed the paragraph
tag will render content differently. We are using the v-html
attribute to allow users to pass HTML styling such as <strong>
and <u>
. You can test it out by adding this snippet:
<TextContent
variant="paragraph"
content="Use <strong>*bold*</strong>, <u>underline</u> and <i>italics</i> to emphasize important points."
/>
We have built a simple Text Component that comes with pre-built styling. The prop variant
is used to determine the type of text tag that needs to be rendered. We also have the prop topMargin
which will allow us to easily space the text elements.
Assuming that test has worked out for you, let’s move on to the next challenge. Let’s assume we need to have different styles for the header and sub-header. We need a way to instruct the TextContent
component to change the style. Update the OptinForm.vue
code as follows:
<TextContent
variant="header"
content="Here’s Your Attention Getting Headline"
topMargin="50px"
theme="red"
/>
We have added a new prop called theme
. We need to declare this new prop in TextComponent.vue
. Update the code as follows:
<template>
<h1
class="header"
v-bind:style="getStyle()"
v-if="variant === 'header'"
>
{{ content }}
</h1>
</template>
<script>
export default {
...
props: {
...
theme: String
},
data: function() {
return {
red: { // style object
color: "red"
},
blue: { // style object
color: "blue"
}
};
},
methods: {
getStyle() {
switch (this.$props.theme) {
case "red":
return this.red;
case "blue":
return this.blue;
default:
break;
}
}
}
};
</script>
When you refresh your page, you should have something like this:
In this example, we have declared multiple style objects, red
and blue
. Users can specify which theme they want to use. Feel free to add more properties to the style objects and create more style objects.
If you want to go further, you can externalize the style objects so that it’s separate from the code. You can create something like a theme.css
file that will be easier to customize.
Let’s now look at the next component.
The opt-in form is where the lead capture action happens. We ask visitors to give us their email address in exchange for a valuable resource that will help them.
Create the file src/components/OptinForm.vue
and insert the following code:
<template>
<div class="optin-form">
<form @submit.prevent="onSubmit">
<b-form-group>
<b-form-input
type="email"
v-model="form.email"
size="lg"
required
placeholder="Enter email"
/>
</b-form-group>
<b-button type="submit" v-bind:variant="submitColor" size="lg" block>{{
submitText
}}</b-button>
</form>
</div>
</template>
<script>
export default {
name: "optin-form",
props: {
submitText: String,
submitColor: String
},
data() {
return {
form: {
email: ""
}
};
},
methods: {
onSubmit() {
this.$emit("submit", this.form);
}
}
};
</script>
<style scoped>
.btn {
font-weight: bold;
font-size: 1.5rem;
}
</style>
Go through the code, pay special attention to the props used. To demonstrate how this component is used, simply update src/views/Optin.vue
as follows:
<template>
<b-row style="marginTop:20px">
<b-col>
<b-img
src="https://images.unsplash.com/photo-1483032469466-b937c425697b?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=600&q=80"
fluid
/>
</b-col>
<b-col>
<TextContent
variant="paragraph"
content="Here’s where you write your compelling message, keep your sentences and paragraphs short."
/>
<OptinForm
submitText="Free Download!"
submitColor="warning"
@submit="handleSubmit"
/>
</b-col>
</b-row>
</template>
<script>
import TextContent from "../components/TextContent";
import OptinForm from "../components/OptinForm";
export default {
name: "optin",
components: {
TextContent,
OptinForm
},
methods: {
handleSubmit(form) {
console.log("Add New Subscriber", form.email);
this.$router.push({ path: '/thank-you'})
}
}
};
</script>
Do note we’ve replaced the “put image here” text with an actual image tag. We’ve also specified a @submit
event on the OptinForm
component which will be handled by the handleSubmit
function. If you look back at the OptinForm.vue
code, you’ll notice that an event is fired through this code: this.$emit("submit", this.form);
.
And that is how we’ve decoupled the OptinForm.vue
component. We can easily write custom code that sends an email address to any email marketing platform of your choice. My current favorite is MailerLite. Here is their API documentation on how to add a new subscriber.
You can use a library like Fetch
or Axios
to send the information via the REST API. If you are new to this, check out the tutorial Introducing Axios, a Popular, Promise-based HTTP Client.
Refresh your browser and confirm that the opt-in page is working:
At the moment, we are using HTML validation. Entering a valid email address should quickly navigate you to the thank-you
page. At the moment, it doesn’t look like one. Let’s fix that in the next section.
Before we populate the Thank-You.vue
page, we need to create the src/components/VideoContent.vue
component. Insert the following code:
<template>
<div class="video-content" v-bind:style="{ marginTop: topMargin }">
<b-embed type="iframe" aspect="16by9" :src="link" allowfullscreen />
</div>
</template>
<script>
export default {
name: "video-content",
props: {
link: String,
topMargin: String
}
};
</script>
The VideoContent
component will allow us to embed any video from sites such as YouTube and Vimeo. You’ll have to get an embed link for it to work. The embed URL for YouTube looks something like this:
https://www.youtube.com/embed/xxxxxxxxx
Once you have saved the component, we can now start working on src/views/Thank-you.vue
. Replace all existing code with this:
<template>
<div class="thank-you container">
<b-row>
<b-col>
<TextContent
variant="header"
content="Here’s Your Thank You Headline"
topMargin="50px"
theme="red"
/>
<TextContent
variant="subheader"
content="This is your sub-headline to increase credibility or curiosity"
/>
</b-col>
</b-row>
<b-row>
<b-col>
<VideoContent
link="https://www.youtube.com/embed/m9q58hSppds"
topMargin="30px"
/>
</b-col>
</b-row>
</div>
</template>
<script>
import TextContent from "../components/TextContent.vue";
import VideoContent from "../components/VideoContent";
export default {
name: "thank-you",
components: {
TextContent,
VideoContent
}
};
</script>
If you refresh the http://localhost:8080/thank-you page, you should have the following view:
Since we are done with that, let’s create one more component that is a bit complicated. This one will go to our Optin
page.
A countdown timer is a popular marketing tool used to create a sense of urgency. It encourages the visitor to take action now before the opportunity expires. There are mainly two types of countdown timers:
In this case, we’ll focus on building the first use case. We are not going to actually code this timer itself, but grab one from the npm registry. You’ll need to exit the Vue.js server first. Install it as follows:
npm install vuejs-countdown
Next create the file src/components/Countdowntimer.vue
and insert the following code:
<template>
<div class="countdown-timer">
<Countdown :deadline="endDate"></Countdown>
</div>
</template>
<script>
import Countdown from "vuejs-countdown";
export default {
name: "countdown-timer",
components: { Countdown },
props: {
endDate: String
}
};
</script>
<style>
.countdown-timer {
padding: 15px;
text-align: center;
}
.countdown-section {
display: inline-block;
margin-right: 25px;
}
.vuejs-countdown .digit {
display: block;
border: 4px solid orange;
color: darkorange;
padding: 10px;
border-radius: 100px;
width: 72px;
margin-bottom: 10px;
}
.text {
font-size: 0.7rem;
font-weight: bold;
color: gray;
}
</style>
Next, add the CountdownTimer
component to the src/views/Optin.vue
page:
<template>
...
<b-row>
<b-col>
<CountdownTimer endDate="March 3, 2020e" />
<TextContent
variant="subheader"
content="This is offer will soon expire"
/>
</b-col>
</b-row>
...
</template>
<script>
import CountdownTimer from "../components/CountdownTimer";
...
components: {
...
CountdownTimer
},
</script>
After making the changes, you can now start the server. Your page should look like this:
In case yours is not counting down, change the date and make sure it’s set in the future. Let’s now finalize the tutorial.
So far we’ve made a good base for a sales funnel library. You can keep on adding more components, each with more customizable options. Now is a good time to talk about packaging and launching a production-ready funnel. While it’s possible to run the funnel we just created from our Vue project, it’s best we launch it in a Nuxt.js project.
I hope you have learned something useful from this tutorial. As a developer, you have an advantage over non-technical people. You can build and launch your funnels at a fraction of the cost most marketers pay for the entire lifetime when using a funnel building service. This will result in thousands of dollars in savings every year.
In addition, you will be able to sell more of your software products and services as opposed to using a standard website. Let us know via social media if you plan to create an open-source version of a sales funnel builder that will benefit the community.
#vuejs #vue #javascript