Build a Link Previewer with Puppeteer and Serverless Functions

    Miracle Onyenma
    Share

    In this tutorial, we’re going to see how we can deploy serverless functions alongside our front-end application and create an API that generates images and grabs metadata from links.

    With serverless functions, developers can create and implement modern features and functionalities in their applications without going through the pain of setting up and managing back-end servers. The functions are hosted and deployed by cloud computing companies.

    Netlify functions make creating and deploying serverless functions easy for applications hosted on Netlify.

    Prerequisites

    To follow along with this tutorial, you’ll need to be familiar with JavaScript, Vue.js, Git, GitHub, and Netlify. You should also have a text editor — such as VS Code) with Vetur installed (for IntelliSense) — and a recent version of Node installed on your machine. You can install Node here. You can check your version of Node by running the command node -vin your terminal.

    You should also have an account on Netlify. You can create one if you haven’t already.

    What We’re Building

    To show how we can easily set up serverless functions with our front-end application, we’ll be building an app with a custom link previewer component.

    This component sends a request with a URL to our serverless function. The function then uses Puppeteer to get metadata from the target site using the URL and to generate a screenshot of the site.

    The function sends the metadata and screenshots back to the component on our front-end to display it as a link preview in the application.

    Here’s the link to the example project deployed on Netlify. And here’s the GitHub Repo to follow along.

    Create and Set Up the Vue Application

    We’re going to create a Vue 3 application using Vue CLI. We’ll also install and set up Tailwind CSS, a utility-first CSS framework that provides classes we can use for our app without having to write a lot of custom CSS.

    Install and set up Vue

    To quickly scaffold a Vue application, we’ll use Vue CLI. To install Vue CLI, run:

    npm install -g @vue/cli
    

    Once the CLI has been installed, we can create a project by running:

    vue create link-previewer
    

    This will prompt us to pick a preset for our installation. We’ll select “Manually select features” so that we can pick the features we need. Here are the options I selected:

    Please pick a preset: Manually select features
    ? Check the features needed for your project: Choose Vue version, Babel, PWA, Router, Vuex, Linter
    ? Choose a version of Vue.js that you want to start the project with: 3.x
    ? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
    ? Pick a linter / formatter config: Basic
    ? Pick additional lint features: Lint on save
    ? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
    

    After selecting these options, we’ll be asked if we want to save the options as a preset for later use. Select Y (yes) or N (no) and continue with the installation.

    Linkpreviewer vue presets

    Run cd link-previewer to enter the newly created project.

    Install and set up Tailwind CSS

    To install Tailwind, we’ll use the PostCSS 7 compatibility build, since Tailwind depends on PostCSS 8 — which at the time of writing is not yet supported by Vue 3. Uninstall any previous Tailwind installation and re-install the compatibility build:

    npm uninstall tailwindcss postcss autoprefixer
    npm install -D tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9
    

    Create the Tailwind configuration files

    Next, generate tailwind.config.js and postcss.config.js files:

    npx tailwindcss init -p
    

    This will create a minimal tailwind.config.js file at the root of the project.

    Configure Tailwind to remove unused styles in production

    In the tailwind.config.js file, configure the purge option with the paths to all of the pages and components so Tailwind can tree-shake unused styles in production builds:

    // ./tailwind.config.js
    module.exports = {
      purge: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
      ...
    }
    

    Include Tailwind in the CSS file

    Create the ./src/assets/css/main.css file and use the @tailwind directive to include Tailwind’s base, components, and utilities styles:

    /* ./src/assets/css/main.css */
    @tailwind base;
    @tailwind components;
    @tailwind utilities;
    
    body{
      @apply bg-gray-50;
    }
    

    Tailwind will swap these directives out at build time with all of the styles it generates based on the configured design system.

    Finally, ensure the CSS file is being imported in the ./src/main.js file:

    // ./src/main.js
    import { createApp } from 'vue'
    import App from './App.vue'
    import './registerServiceWorker'
    import router from './router'
    import store from './store'
    import './assets/css/main.css'
    
    createApp(App).use(store).use(router).mount('#app')
    

    And that’s it, we can run our server:

    npm run serve
    

    Vue CLI: start the server after successfully creating an app

    Now that the app is running, if we go to the URL provided, we should see the default demo app for Vue and see that Tailwind’s preflight base styles have been applied.

    Vue app output after adding Tailwind CSS

    Install Tailwind CSS IntelliSense extension

    For a smoother development experience, install the Tailwind CSS Intellisense extension for VS Code.

    Install Tailwind CSS Intellisense Extension for VS Code

    Basic app structure

    Here’s an overview of what our project folder should look like:

    link-previewer/
    ├─ functions/
    │  ├─ generate-preview.js
    │  └─ hello.js
    ├─ public/
    │  ├─ favicon.ico
    │  ├─ img/
    │  │  └─ icons/
    │  ├─ index.html
    │  └─ robots.txt
    ├─ src/
    │  ├─ main.js
    │  ├─ App.vue
    │  ├─ registerServiceWorker.js
    │  ├─ assets/
    │  │  ├─ css/
    │  │  │  └─ main.css
    │  │  └─ logo.png
    │  ├─ components/
    │  │  └─ LinkPreviewer.vue
    │  ├─ router/
    │  │  └─ index.js
    │  ├─ store/
    │  │  └─ index.js
    │  └─ views/
    │     ├─ About.vue
    │     └─ Home.vue
    ├─ .git
    ├─ .gitignore
    ├─ .browserslistrc
    ├─ .eslintrc.js
    ├─ babel.config.js
    ├─ netlify.toml
    ├─ package-lock.json
    ├─ package.json
    ├─ postcss.config.js
    ├─ README.md
    └─ tailwind.config.js
    

    A Quick Introduction to Netlify Functions

    Netlify Functions is a Netlify product that simplifies the process of creating and deploying serverless functions. According to the product’s home page, it’s used to:

    Deploy server-side code that works as API endpoints, runs automatically in response to events, or processes more complex jobs in the background.

    A basic Netlify Function file exports a handler method with the following syntax:

    exports.handler = async function(event, context){
      return {
        statusCode: 200,
        body: JSON.stringify({message: "Hello World!"})
      }
    }
    

    Netlify provides the event and context parameters when the function is called/invoked. When a function’s endpoint is called, the handler receives an event object like this:

    {
      "path": "Path parameter (original URL encoding)",
      "httpMethod": "Incoming request’s method name",
      "headers": {Incoming request headers},
      "queryStringParameters": {Query string parameters},
      "body": "A JSON string of the request payload",
      "isBase64Encoded": "A boolean flag to indicate if the applicable request payload is Base64-encoded"
    }
    

    The context parameter, on the other hand, includes information about the context in which the function was called.

    Within the function, we’re returning an object with two important properties:

    • statusCode, which is 200 in this case
    • body, which is a stringified object.

    The function will be called from our site at /.netlify/functions/hello and on success, it would return the 200 status code and the message, “Hello, World!”.

    Now that we have an idea of how Netlify functions work, let’s see them in practice.

    Creating Our First Netlify Function

    To create our first Netlify function, we’ll create a new file functions/hello.js in the project directory and enter the following:

    // functions/hello.js
    exports.handler = async function(event, context){
      return {
        statusCode: 200,
        body: JSON.stringify({message: "Hello World!"})
      }
    }
    

    Once we’ve created the function file, we have to make some necessary configurations in order for us to run our function locally.

    Set up Netlify configuration

    We’ll create a netlify.toml file at the root of our project folder that will tell Netlify where to find our functions:

    # ./netlify.toml
    
    [functions]
      directory = "./functions"
    

    Netlify will now locate and deploy the functions in the functions folder at build time.

    Install Netlify CLI

    To run our functions locally without having to deploy to Netlify, we need to install Netlify CLI. The CLI allows us to deploy our projects with some great Netlify features locally.

    To install the CLI, make sure you have Node.js version 10 or later, then run:

    npm install netlify-cli -g
    

    This installs Netlify CLI globally, so we can run netlify commands from any directory. To get the version, usage, and so on, we can run:

    netlify 
    

    Run the App with Netlify Dev

    To run our project locally with Netlify CLI, stop the dev server (if it’s active), then run:

    netlify dev
    

    And here’s what we should see:

    Run Netlify dev with Netlify CLI

    If you look closely, you’ll see a few things going on there:

    • Netlify tries to inject environment variables from our .env files into the build process, which can then be accessed by our Netlify Functions. In this case, we have no .env file, so it loads the defaults defined in process.

    • Secondly, it loads or deploys our functions located in the functions directory. The Functions server is deployed on a different and random port — 36647.

    • Lastly, it automatically detects what framework the application is built with and runs the necessary build processes to deploy the application. In this case, you can see “Starting Netlify Dev with Vue.js”. It also supports React and other popular frameworks.

    Netlify then starts our development server on http://localhost:8888.

    Netlify dev Cli started

    Now that our server has started and our functions arevloaded, we can call/invoke it. By default, we can access our functions using this route: /.netlify/functions/<function name>.

    One important thing to note is that we don’t need to specify the port where our Functions server is running. We can use the default route above to communicate with our Functions server. Netlify automatically resolves the URL and port behind the scenes.

    if we send a GET request to http://localhost:8888/.netlify/functions/hello, we should get a response of {"message":"Hello, World!"}.

    hello.js Function response from browser

    Great! Our first serverless function works!

    Create the Preview Function API

    Now that our Netlify function works, we can begin building the preview API. Here’s a quick rundown of what our Functions API is going to do:

    • it receives the target URL that will be sent from the front end
    • it passes the data to Puppeteer
    • Puppeteer then launches a new headless browser instance
    • Puppeteer opens a new page in the browser and navigates to the target URL
    • Puppeteer then extracts the content of the <title> and <meta> tags for the description of the target page
    • it captures a screenshot of the page
    • it sends the screenshot data back to the front end

    Now that we have a basic idea of what our Functions API is going to do, we can start creating Functions. Let’s start by installing and setting up Puppeteer for Netlify Functions.

    Install and configure Puppeteer

    Puppeteer is a Node library that provides a high-level API to control headless Chrome or Chromium browsers. It can also be configured to use the full (non-headless) chrome or Chromium. You can do most things that you can do manually in the browser using Puppeteer. More about Puppeteer can be found in the Puppeteer documentation.

    To get started with Puppeteer, we’ll install it in our project.

    Puppeteer for local development and production

    Puppeteer downloads a recent version of Chromium (~170MB macOS, ~282MB Linux, ~280MB Windows) that’s guaranteed to work with the API.

    We can’t use the full puppeteer package for production. This is because Netlify Functions has a maximum size of 50MB, and the Chromium package is too large.

    Thanks to this very useful article by Ire Aderinokun, we can still work with Puppeteer both locally and in production. Here’s what we have to do:

    Install puppeteer as a development dependency* for local deployment:

    npm i puppeteer --save-dev
    

    For Puppeteer to work both locally and in production, we have to install puppeteer-core and chrome-aws-lambda as production dependencies.

    You can check out the difference between puppeteer and puppeteer-core here. The main difference, though, is that puppeteer-core doesn’t automatically download Chromium when installed.

    Since puppeteer-core doesn’t download a browser, we’ll install chrome-aws-lambda, a “Chromium Binary for AWS Lambda and Google Cloud Functions” which we can use in our Netlify Functions. These are the packages that will work in production:

    npm i puppeteer-core chrome-aws-lambda --save-prod
    

    Now that we’ve installed our packages, let’s create our function.

    Use already installed browser for Puppeteer

    If Puppeteer installing a full browser to work with locally is going to be an issue, that may be due to slow network or bandwidth concerns. There’s a workaround, which is to use our already installed Chrome or Chromium browser for Puppeteer.

    What we need is the path to the browser in our local machine. We’ll use this as our executablePath, which we’ll pass to the puppeteer.launch() method. This tells Puppeteer where to find the browser executable file.

    If you don’t know exactly where to find the executable path, open up your browser and go to chrome://version/ to display the version of chrome.

    Chrome Executable path from the chrome browser

    Copy the path and create a .env file in the root of the project.

    # ./.env
    EXCECUTABLE_PATH=<path to chrome>
    

    To get the content of the .env file, we’ll install another package — dotenv:

    npm install dotenv
    

    Now that we’ve successfully installed the package, let’s create the Netlify function.

    Create the generate-preview function

    Create a new file, ./functions/generate-preview.js:

    // ./functions/generate-preview.js
    
    const chromium = require('chrome-aws-lambda')
    const puppeteer = require('puppeteer-core')
    
    exports.handler = async function (event, context) {
      // parse body of POST request to valid object and
      // use object destructuring to obtain target url
      const { targetURL } = JSON.parse(event.body)
    
      // launch browser
      const browser = await puppeteer.launch({
        args: chromium.args,
        // get path to browser
        executablePath: process.env.EXCECUTABLE_PATH || await chromium.executablePath,
        headless: true
      })
    
      // open new page in browser
      const page = await browser.newPage()
    
      // set the viewport of the page
      await page.setViewport({
        width: 768,
        height: 425,
        deviceScaleFactor: 1
      })
    
      // set the prefers-color-scheme to dark
      await page.emulateMediaFeatures([
        {name: 'prefers-color-scheme', value:'dark'}
      ])
    
      // navigate to target URL and get page details and screenshot
      try{
        ...
      }
    }
    

    In the code above, we’re doing a number of things. First, we obtain the targetURL from the request payload in event.body. This would be sent with a POST request.

    Next, we launch the browser using the chrome-aws-lambda package. We do this using the puppeteer.launch() method. This method takes in an object as an argument with a few optional properties. An important property we pass to this method is the executablePath.

    We assign the executablePath to process.env.EXCECUTABLE_PATH || await chromium.executablePath enabling the package to locate the available headless browser to launch.

    Once the browser is launched, we open a new page in the browser using the browser.newPage() method. We also set our desired browser viewport for the page using the page.setViewport() method.

    Notice that we’re using the await keyword when running any function. This is because Puppeteer works asynchronously and some functions might take some time before they execute.

    We can also do things like define the media features of the page with Puppeteer using the page.emulateMediaFeatures() method, which takes an array of media feature objects. That’s how we set the prefers-color-scheme to dark.

    Get site meta data and screenshot

    Next, we’ll navigate to the target URL and get our title, description and screenshot:

    // ./functions/generate-preview.js
    
    ...
    // navigate to target URL and get page details and screenshot
    try {
      // navigate to the targetURL
      await page.goto(targetURL)
    
      // get the title from the newly loaded page
      const title = (await page.$eval(`head > title`, el => el.textContent) || null)
    
      // get the descriptions of the page using their CSS selectors
      const descriptions = await page.evaluate(() => {
        let descriptions = {}
    
        let desc = document.querySelector(`meta[name='description']`)
        let og = document.querySelector(`meta[property='og:description']`)
        let twitter = document.querySelector(`meta[property='twitter:description']`)
    
        desc ? descriptions.desc = desc.content : descriptions.desc = null
        og ? descriptions.og = og.content: descriptions.og = null
        twitter ? descriptions.twitter = twitter.content : descriptions.twitter = null
    
        return descriptions
      })
    
      // screenshot the page as a jpeg with a base64 encoding
      const screenshot = await page.screenshot({
        type: 'jpeg',
        encoding: 'base64'
      })
    
      // close the browser
      await browser.close()
    
      // send the page details 
      return {
        statusCode: 200,
        body: JSON.stringify({
          title,
          screenshot,
          descriptions
        })
      }
    
    } catch (error) {
    
      // if any error occurs, close the browser instance 
      // and send an error code
      await browser.close()
      return {
        statusCode: 400,
        body: JSON.stringify({
          error
        })
      }
    }
    

    In the code above, we’re using a trycatch block to wrap our code so that, if anything goes wrong, starting from await page.goto(targetURL), which navigates to the target URL, we can catch the error and send it to our front end. An error might occur through providing an invalid URL.

    If the URL was valid, we get the title using the page.$eval() method, which is similar to the usual document.querySelector method in JavaScript. We pass in the CSS selector — head > title — of the title tag as the first argument. We also pass a function el => el.textContent as the second argument, where el is a parameter we pass to the function and is the title element. We can now get the value using title.textContent.

    Notice that all this is wrapped in a parentheses (()) and we have a || null after page.$eval. This is so that title is assigned null if page.$eval() fails to get the title of the page.

    To get the descriptions of the page, we’ll use the page.evaluate() method, which allows us to run some client-side JavaScript and return a value to the assigned variable — descriptions.

    We pass a function as and argument to the page.evaluate() method. Within the function we use document.querySelector to get the various meta descriptions, such as <meta name="description" content="<site description>" /> for the default description, and <meta property="og:description" content="<site description>" /> for the OpenGraph description.

    After getting the elements, we use ternary operators to get the content and add it to the descriptions object if the elements exist, or null if the element doesn’t exist.

    Once we’ve gotten the descriptions, we take a screenshot of the page using the page.screenshot() method and close the browser with browser.close().

    Finally, we’re sending the page details in the body property a JSON object with a statusCode of 200. If an error occurs in any of the previous steps, it’s caught in the catch block and we send a statusCode of 400 and the error message instead.

    Test and deploy function

    Let’s test our function using an API tester. You can install Postman or Talend API tester in your browser or use the Thunder Client extension, an API tester for VS Code.

    You can also use cURL:

    curl -X POST -H "Content-Type: application/json" -d '{"paramName": "value"}' <URL>
    

    Run the function using the netlify dev command.

    Netlify CLI functions server after running Netlify Dev

    We can send a request using the port for the functions server or the default :8888 port for the Netlify dev server to send a request to our functions. I’ll be using http://localhost:8888/.netlify/functions/generate-preview to send a POST request with an object containing the targetURL in the body:

    {
      "targetURL" : "https://miracleio.me"
    }
    

    When we send the request, here’s the response we get.

    Thunder Client API request and response

    We get a JSON object containing our preview data:

    {
      "title": "Miracleio | PortfolioX",
      "screenshot": "/9j/4AAQSkZJRgABAQAAAQABAAD...",
      "descriptions": {
        "desc": "Designer & Frontend Developer portfolio site. Built by Miracleio with love ❤",
        "og": "Designer & Frontend Developer portfolio site. Built by Miracleio with love ❤",
        "twitter": null
      }
    }
    

    Now that our serverless function works, let’s see how we can use it in our front end.

    Building the Link Preview Functionality on the Client

    In order to interact with our generate-preview function, we’ll need to send POST requests containing our targetURL.

    We’ll create LinkPreview components that will display normal links. These components will be passed their target URLs as props. Before the component is mounted in the application, it’ll send a POST request with the targetURL to our serverless function, get the preview data, and display it once we hover on the link.

    Creating the link preview component

    First, let’s create our link preview component src/components/LinkPreviewer.vue.

    In our <script>, we’ll get the link preview data by sending a request to our serverless function and save the data in previewData object. We’ll use this later in our template to display the data:

    // ./src/components/LinkPreviewer.vue
    ...
    
    <script>
    import { computed, onBeforeMount, ref } from '@vue/runtime-core'
      export default {
        // define targetURL as a prop
        props: ['targetURL'],
        setup(props) {
          // create a reactive previewData object using ref
          const previewData = ref({})
    
          // function to send a POST request containing the targetURL 
          // to the serverless function
          const generatePreview = async () => {
            try {
              const res = await fetch('/.netlify/functions/generate-preview', {
                method: 'POST',
                body: JSON.stringify({
                  targetURL : props.targetURL
                })
              })
    
              const data = await res.json()
              return data
            } catch (err) {
              console.log(err)
              return null
            }
          }
    
          // run function before component is mounted
          onBeforeMount(async ()=>{
            // run generatePreview() to get the preview data and assign to previewData
            previewData.value = await generatePreview()
    
            // use object destructuring to get the different descriptions 
            // from the preview data
            const {desc, og, twitter} = previewData.value.descriptions
    
            // assign only one valid value to the description property 
            // in the previewData object
            previewData.value.description = computed(()=>(desc || og || twitter || ""))
          })
    
          // make the following entities available to the component
          return { generatePreview, previewData}
        }
      }
    </script>
    

    In the code above, we get the targetURL as a prop that will be passed into our component.

    In the setup(), we pass props as an argument in order for us to access component props like targetURL.

    Then, we create a reactive peviewData object using ref: const previewData = ref({}). In a new generatePreview() function, we’re using fetch to send a POST request containing the targetURL to our serverless function. This function returns the response or null if an error occurs.

    Next, to run the function before the component is mounted, we use the onBeforeMount() hook. We pass an async function as an argument. Within the function, we assign previewData.value to the generatePreview() function. The descriptions (desc, og, twitter) are then gotten from the descriptions property.

    To get the description that will be displayed in the preview, we’ll assign previewData.value.description to (desc || og || twitter || ""). This way, the first property with a value gets assigned to the description.

    Do this to display the preview data in our template:

    <!-- ./src/components/LinkPreviewer.vue -->
    
    <template>
      <div class="inline relative">
        <!-- display targetURL link -->
        <a class="link underline text-blue-600" 
           :href="targetURL" 
           :target="previewData ? previewData.title : '_blank'">
           {{targetURL}} 
        </a>
    
        <!-- display preview data if object exists -->
        <div v-if="previewData" class="result-preview absolute top-8 left-0 w-72 transform translate-y-4 opacity-0 invisible transition bg-white overflow-hidden rounded-md shadow-lg z-10">
    
          <!-- display image using the base64 screenshot data -->
          <img v-if="previewData.screenshot"
               :src="`data:image/jpeg;base64,${previewData.screenshot}`"
               :alt="previewData.description" />
    
          <!-- display title and description -->
          <div class="details p-4 text-left">
            <h1 class=" font-extrabold text-xl"> {{previewData.title}} </h1>
            <p> {{previewData.description}} </p>
          </div>
        </div>
      </div>
    </template>
    
    <script> ... </script>
    
    <style scoped>
      .link:hover ~ .result-preview{
        @apply visible opacity-100 translate-y-0;
      }
    </style>
    

    In the above code, in order to display our image — which is essentially a base64 string — we have to pass the string along with data like the image type and encoding into the src-"" attribute.

    That’s about it for our LinkPreviewer.vue component. Let’s see it in action. In ./src/views/Home.vue:

    <!-- ./src/views/Home.vue -->
    
    <template>
      <main class="home">
        <header>
          <h1>Welcome to the link previewer app!</h1>
          <p>Here are some links that you can preview by hovering on them</p>
        </header>
        <ul class=" mb-4">
          <!-- render LinkPreviewer component for each demolink -->
          <li v-for="link in demoLinks" :key="link">
            <link-previewer :targetURL="link" />
          </li>
        </ul>
        <!-- input field to add new links -->
        <input class=" p-2 ring ring-blue-600 rounded-lg shadow-md" type="url" @keyup.enter="addLink" required placeholder="enter valid url">
      </main>
    </template>
    
    <script>
    import { ref } from '@vue/reactivity'
    import LinkPreviewer from '../components/LinkPreviewer.vue'
    
    export default{
      components: { LinkPreviewer },
      setup(){
        // demo links
        const demoLinks = ref([
          'http://localhost:5000',
          'https://google.com',
          'https://miracleio.me',
          'https://miguelpiedrafita.com/'
        ])
    
        // function to add new links to the demoLinks array
        const addLink = ({target}) => {
          demoLinks.value.push(target.value)
          target.value = ""
        }
    
        return {demoLinks, addLink}
      }
    }
    </script>
    

    In our Home.vue file, we’re basically using a demoLinks array of links to render a list of LinkPreviewer components, which we pass to the targetURL props of the component.

    We also have an <input> element, which we use to dynamically add more LinkPreviewer components to the list.

    Here’s what our simple app looks like now.

    Link previewer output on user hover

    Sweet! Our app works. Since we’ve been running locally using Netlify CLI, let’s see how we can deploy to Netlify using the CLI.

    Deploying the App to Netlify

    Before we deploy our app to Netlify, we have to build our app for production:

    npm run build
    

    This will build our app and create a dist/ folder we can deploy to production.

    Next, we need to log in to our Netlify account:

    netlify deploy
    

    This will log you into your Netlify account in your browser.

    Netlify CLI successful login

    After authorizing the application, we can link our project to a new site. Netlify will ask us a bunch of questions:

    • What would you like to do? Choose “Create & configure a new site”.
    • Team? Choose <your team>.
    • Choose a unique site name? Choose <site name>.
    • Please provide a publish directory (such as “public” or “dist” or “.”). Enter dist.

    After this, Netlify will upload our files and deploy them to our new site.

    Deploy using GitHub

    Alternatively, we can decide to deploy our site from GitHub. All you have to do is to log in to GitHub, create a new repository, and copy the URL to our newly created repo.

    GitHub quick setup after creating a new repo to get repo URL

    We then run the following command in our project folder:

    git init
    git add .
    git commit -m "first commit"
    git branch -M main
    git remote add origin https://github.com/miracleonyenma/link-previewer.git
    git push -u origin main
    

    Note: you may not be able to push to your repo from your terminal because of authentication issues, and you might get a message from Git like this: “Support for password authentication was removed on August 13, 2021. Please use a personal access token instead.” This means that you have to create a personal access token (PAT) and use it to log in. To do that, go to GitHub token settings and generate a new token. Select all the permissions you want. Make sure you’re able to access repos. After generating your PAT, copy it and save it somewhere. Then try the git push -u origin main command again and paste in your PAT when asked for your password.

    Once we’ve pushed the project to GitHub, head over to Netlify to create a new site from GitHub.

    Follow the steps to choose a repository and enter the build settings for your project. For our Vue project, the build command is npm run build, and the deploy directory is dist.

    Netlify options for site deploy

    After that, click on Deploy site.

    Netlify will deploy the site, and we can preview our site by clicking on the deploy link provided. We can see our functions by going over to Functions from the top menu.

    Netlify Dashboard functions overview

    You can select a function to view more details and logs.

    Netlify functions details and logs

    Sweet!

    Here’s the link to the demo deployed on Netlify: https://lnkpreviewr.netlify.app

    Conclusion

    We’ve been able to create and deploy serverless functions with Netlify using Netlify functions. We’ve also seen how we can interact with the functions from our Vue front end. This time, we used to it screenshot and get data from other sites and built a link previewer component with it, but we can do so much more. With serverless functions, we can do more on the front end without having to bother on setting up a back-end server.

    Further Reading and Resources

    Here are some resources and content that I found useful and I think you will too: