How to Build Lightning Fast Surveys with Next.js and SurveyJS

Share this article

How to Build Lightning Fast Surveys with Next.js and SurveyJS

In this article, we’ll walk through how to build a website that you can use to create new surveys, share your surveys, and then analyze the results. Your website will be lightning fast and will be SEO friendly, relying on all the latest features in Next.js. It will also be flexible and easy to build thanks to SurveyJS, which makes working with surveys effortless.

This article will assume you understand the basics of React and Next.js, but it will walk you through how to build every component and page of the website. You can follow along with the article for all the code, or you can jump to the end and use the example repository here. You can also take a look at the final version of the website that I’ve deployed for you here.

Next.js is a React-based framework that helps you build full-stack websites entirely in React. Next.js handles all the bundling and gives you powerful APIs to decide how to render each page so that it can be lightning fast. In this article, we’ll make sure that all our pages can be rendered at build time. This means that we can easily expose a sitemap that Google can use to index your website, which is vital for making sure your SEO performance is great.

SurveyJS is an open-source form management tool that gives you the ability to create, share and analyze your surveys and forms. They provide a React API that we’ll use to create a survey management system with Next.js.

Setting Up Next.js

First, let’s setup our Next.js application. It’s quick and easy to get started with Next.js, as they provide a CLI tool that lets you create a basic app based on the preferences you give.

To use the tool you need to make sure you have npx installed and then run the following command:

npx create-next-app@latest

Once you run the create-next-app command it will ask you a series of questions about the project you want to create. Most of the questions are entirely based on personal preference, so you can answer them however you like. For this article, we’ll be using pure JavaScript (rather than Typescript) and we’ll also be using the new app router in Next.js rather than the old file router.

Now that you have your Next.js app set up, you can run it with:

yarn run dev

This will leave you with a dev server running that will update any time you make changes to your files. For now, let’s keep this running so we can add pages without having to rebuild every time.

Setting Up SurveyJS

To set up SurveyJS, we’re going to have to install all the different dependencies. We’re going to use all the different parts of SurveyJS including the form creator, the form display and the results package, so we need to make sure to install them all.

To install the packages, make sure to run the following install command:

yarn add survey-analytics survey-core survey-creator-core survey-creator-react survey-react-ui

Setting Up the Form Creator

First, let’s start of by adding the form creator page. I’m going to make mine available at /creator, so to do that I create a file at /creator/page.js.

The creator doesn’t need any server-side data to render, so that means that our page component is very simple; it just renders our Creator component, which I will outline later. It looks like this:

export const metadata = {
  title: "Survey Creator",
};

export default function Page() {
  return <Creator />;
}

In the code above, you can see that I export both the page and a metadata object. The metadata object will then be used for the SEO meta tags by Next.js. For this page, we always want to use the same string, so we just export an object.

The Creator component is where we actually use the SurveyJS API. Let’s take a look at the component:

"use client";

import { useEffect, useState } from "react";
import { SurveyCreatorComponent, SurveyCreator } from "survey-creator-react";

export default function Creator() {
  let [creator, setCreator] = useState();

  useEffect(() => {
    const newCreator = new SurveyCreator({
      showLogicTab: true,
      showTranslationTab: true,
    });
    setCreator(newCreator);
  }, []);

  return <div>{creator && <SurveyCreatorComponent creator={creator} />}</div>;
}

The first thing you’ll notice is we use the use client directive in this component. This is because the SurveyJS components aren’t designed to be run as server components. Not to worry, though; they’ll still be rendered on the server first before being sent to the client.

The next thing you’ll see is that we run a useEffect with an empty dependency array. This means that the function will run once and create the SurveyCreator. You can see at that point we can pass in any options into the creator depending on what features we want to enable.

All we need to do is render the SurveyCreatorComponent and pass it the creator object. We optionally render it so that it doesn’t break before the creator is set up.

Your dev server should have been reloading as you go, so if you now visit /creator, you’ll be able to access the creator and use all the features like you can see in the screenshot below.

A screenshot of the form creator

Create a Page to View the Form

Next we want to create a page to view the forms that we’ve built. Once you’ve created the form in the designer, the output will be a JSON object that will contain your questions and the preferences you setup as you build the survey, including any logic or styles.

For our form page, we want to use a dynamic setup so that we can render any number of form pages without having to create a new file for every new form. We do this by using Next.js dynamic routes. To create a dynamic route, we need to create a new file at /app/form/[slug]/page.js which will give all our forms a separate page at /form/form-slug.

In our new file, we have to create a few functions to support Next.js to create our pages. First, let’s start with generateStaticParams, which we can use to tell Next.js which pages we want to generate. Below you can see the contents of the function:

export async function generateStaticParams() {
  return surveys.map((x) => ({ slug: x.slug }));
}

For this project, we set up a file that exports a list of surveys (which contain a slug) and a survey (which is the object provided by the survey designer). If we want to add a new survey, we just need to add another entry to our surveys array. Our generateStaticParams function needs to export a list of slugs, which Next.js will then use to render our pages at build time. For us, this is really easy; we just need to map our survey array to fit the format:

export async function generateMetadata({ params }) {
  const survey = surveys.find((x) => x.slug === params.slug);

  return {
    title: survey.survey.title,
    description: survey.survey.description,
  };
}

The next function we will look at is generateMetadata. This takes in the parameters from the static params function we just defined, and then it returns our title and description, which are used for the metadata on our web page. As you can see above, our function finds the correct survey object based on the slug we’re given. Then we can use the same title and description that we wrote when we created our survey.

The last thing we need to define in our page.js file is the React page itself. The page component for our form page is also very simple. It finds the survey object again, then passes it through to the SurveyComponent:

export default function Page({ params: { slug } }) {
  const survey = surveys.find((x) => x.slug === slug);

  return (
    <div>
      <SurveyComponent surveyData={survey.survey} />
    </div>
  );
}

The SurveyComponent then has to be defined separately. Take a look at the component:

"use client";

import { useCallback } from "react";
import { Model } from "survey-core";
import { Survey } from "survey-react-ui";

export default function SurveyComponent({ surveyData }) {
  const model = new Model(surveyData);

  const alertResults = useCallback(async (sender) => {
    fetch("/api/submit", {
      method: "POST",
      headers: {
        "Content-Type": "application/json;charset=UTF-8",
      },
      body: JSON.stringify({ result: sender.data }),
    });
  }, []);

  model.onComplete.add(alertResults);

  return <Survey model={model} />;
}

Again, you’ll notice that we have the use client directive to make sure Next.js knows it’s not a server component. We then create a model with SurveyJS and pass it into the SurveyJS Survey component. Before we do that, you’ll notice that we set up an onComplete function. In our case, the function just sends the raw data to /api/submit, which can then be handled there.

You can use Next.js to create API endpoints. In our case, we can do it by creating a file at /api/submit/route.js and putting a POST function in it, like so:

export async function POST(request) {
  const res = await request.json();

  console.log(res);

  return Response.json({ message: "Done" });
}

In our case, the POST function is very simple: it grabs the object that’s sent and then logs it to the console and responds with a message. This is where you would want to save the result to your database if you have one. You might also choose to validate the result further and return a result to display on the frontend. At this point, it’s totally up to you what you do with the data.

Creating a Page to View the Results

Now that we have set up a way to create and display forms, we need to set up a way to look at the results we’ve collected from our forms. Obviously, one way to look at the results is just to look straight at the database, but that won’t give you any insights into trends that are appearing in your surveys. If we want to identify trends, we can use the surveyjs-analytics package.

For this project, I’ve created some fake result data so we can create a results dashboard. I’ve added a results array to each survey object that we used earlier. Each result looks something like this:

  {
    "nps-score": 9,
    "disappointing-experience": [
      "The service is great, i highly recommend you use it.",
    ],
    "improvements-required": [
      "The service is great, i highly recommend you use it.",
    ],
    "promoter-features": ["ui"],
    rebuy: [true, false],
  }

As you can see, each result is simply an object that has the question ID as a key and the answer as a value. This is exactly what we get from the onComplete function when the form is submitted.

First, we want to create a new dynamic page, as we’ll want to create a new web page for each different form so we can show the results for that form specifically. For this page, we want to create a new file at /results/[slug]/page.js.

Again, we want to define a generateMetadata and a generateStaticParams like we did to display the forms. In our generateMetadata function, we make a slight tweak to the title so it’s clear that we’re looking at the results rather than the form itself. The only difference this time is that, inside our generateStaticParams, we filter some of the forms that don’t have results so we don’t generate a page for forms without any results. Our generateStaticParams function ends up looking like this:

export async function generateStaticParams() {
  return surveys
    .filter((x) => x.results.length > 0)
    .map((x) => ({ slug: x.slug }));
}

Again, we want to also export a Page component. Our page component is identical to the page component from the previous section, except instead we render the component Results. But we still do a find to grab the right survey data and pass that through to the component.

Our Results component loads in all of the required packages and then renders them to the page. It requires a few useEffect hooks to set up, and the whole component looks like this:

"use client";

import { useEffect } from "react";
import { Model } from "survey-core";

export default function Results({ surveyData }) {
  useEffect(() => {
    (async () => {
      const survey = new Model(surveyData.survey);

      const { VisualizationPanel } = await import("survey-analytics");

      const currentPanel = new VisualizationPanel(
        survey.getAllQuestions(),
        surveyData.results,
        {
          allowHideQuestions: false,
        }
      );

      currentPanel.render("surveyVizPanel");

      return () => {
        const panelElement = document.getElementById("surveyVizPanel");

        if (panelElement) {
          panelElement.innerHTML = "";
        }
      };
    })();
  }, [surveyData]);

  return (
    <div>
      <div id="surveyVizPanel" />
    </div>
  );
}

As you can see, we again start with the use client directive for all the same reasons as before. The component starts with a useEffect that’s used to set up the panel that shows all the charts. It firstly uses the surveyData object, which defines the survey itself to create a Model. This lets the results package know which graphs to show, as it can understand each question.

The next thing the useEffect does is load the survey-analytics package. We do this via a dynamic import, so it isn’t loaded at build time. This approach prevents build-time errors caused by client-side specific code in the package.

After getting the required package, we set up the visualization object with all of the questions and then we can give it a list of all the submissions for it to go through and create graphs from. At this point you can configure your visualizations with the options provided. After that, all you have to do is let the panel object know which ID to use to render to in the DOM, which in our case is surveyVizPanel, which we render further down. Lastly, we have to make sure to provide a clean-up function to our hook so that it clears out the element when it’s done.

You’ll notice that we only pass in the surveyData to the dependency array so that we only re-render all the graphs if the input data changes, which might be the case if we ever link between different results pages.

Further Work

This article has given you enough of an idea to get started integrating SurveyJS into your Next.js application. To have a fully functioning system, you’ll want to look into adding some kind of authentication system so that you can make sure only verified users can access the different parts of the system.

You’ll also want to integrate with some kind of data source, both for the creator to create new forms and to collect the results from the end user. All of these additions are made very straightforward in Next.js and SurveyJS.

Conclusion

This guide has shown you how to build a comprehensive survey management system in Next.js with SurveyJS. You get so many benefits out of the box with Next.js, so although you might not have written that much code, you’ll find that what you have created will scale to as many forms as you want without any hassle.

Thanks for taking the time to read this guide. As I previously mentioned, you can check out the full repo here or you can play with the hosted version of the system here.

Gavin HendersonGavin Henderson
View Author

Gavin is a Software Engineer specializing in fast and scalable web experiences. With expertise in modern technologies, he’s built everything from headless WordPress sites with Next.js to complex React applications, like interactive family tree builders. Skilled in both frontend and backend development, Gavin delivers seamless, high-performance solutions tailored to user needs. Known for his problem-solving and collaborative approach, he’s committed to pushing the boundaries of web development. He also writes at DEV.

Next.jssurvey
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week