Preview URLs

By Jared Palmer
September 17, 2020

The Jamstack is taking over the web and Formium is purpose-built to support the modern needs of the sites we build today.

Today I’m excited to release support for Preview URLs and well as reference implementations for Next.js.

What are Preview URLs?

Traditional CMS’s (like Wordpress) are server rendered applications. When you make changes to a resource, you can preview it by simply clicking a button. This works by Wordpress storing a token to fetch the the new data in a cookie. When you view the preview, Wordpress authenticates you and then fetches the changed data. This is relatively simple because everything happens on the same domain and Wordpress is already doing dynamic server rendering on every page load.

Mimicking this experience with the Jamstack is more challenging. In the past, you would have to totally regenerate (either locally or in production) the entire site any time the data changes. This is perhaps tolerable for small sites with fast build times, but doesn’t scale. What you want is the ability to preview your changes without having to regenerate the entire site.

Thanks to Formium Preview URLs you can get this editorial workflow regardless of your stack.

Adding a Preview URL

Assuming you already have an application up and running, open any form on Formium.

  • Press on the right side of the Preview button in the Formium Editor
  • Press Configure an External Preview URL
An image from Notion
  • This will open a dialog where you can set a custom Preview URL for your form. This should be the URL of your server or a serverless function where you fetch your form's schema with the Formium API client (@formium/client).
An image from Notion

Once you do this, Formium will automagically save the current state of your form as a new revision whenever you press the Preview button in the editor. In addition, the Preview button will now open your Preview URL in a new browser window and append a query string with the current form’s slug, the current form’s id, and the unique identifier of the current revision (revisionId).

With this information, you can alter your request to the Formium API so you can then fetch the revised form data without needing to alter any other part of your application.

import { createClient } from '@formium/client';
const formium = createClient('your_project_id', {
apiToken: 'your_access_token',
});
export default async function handle(request, response) {
if (request.query.slug) {
// Pass the `revisionId` to the request
const form = await formium.getFormBySlug(slug, {
revisionId: request.query.revision,
});
}
// now the form you can see the revision without changing
// any code in the rest of your application
}

An example with Next.js on Vercel

Now that you get the gist, here's an example of how to setup preview mode inside of a Next.js deployed to Vercel. Why Vercel? Well, they invented Next.js and they support advanced Next.js features such as API routes, Preview Mode, and Incremental Static Regeneration. The combination of these three, to be honest, is actually why we built Preview URLs into Formium in the first place!

To get going, we updated all of our Next.js quickstarts to have this all setup out-of-the box. However, let's do a quick run through anyways.

Assuming you've followed our Next.js tutorial and already, let's just pick up where we left off. First, we'll make a Next.js API route at pages/api/preview.js that we'll end using for our Preview URL. Its job is to receive the HTTP callback from the Formium dashboard, possibly turn on Next.js Preview Mode, and ultimately redirect to the correct URL based on the form of interest.

// pages/api/preview.js
import { formium } from '../../lib/formium';
export default async (req, res) => {
// Check for the presence of the id in the query string
if (!req.query.id) {
return res.status(400).json({ message: 'Invalid request.' });
}
// Fetch the form to check if the provided `id` exists
const form = await formium.getFormById(req.query.id);
// If the it doesn't exist prevent preview mode from being enabled
if (!form) {
return res.status(404).json({ message: 'Form not found' });
}
// If the request has a revisionId, it means it's for a preview, so
// we set res.previewData as an object with the revisionId. This is a
// cookie that will be used by our Next.js page to fetch the correct data.
// The reason we don't fetch the form revision here is because we are
// limited by the size of the cookie we can set.
// @see https://nextjs.org/docs/advanced-features/preview-mode
if (req.query.revisionId) {
res.setPreviewData({
revisionId: req.query.revisionId,
});
}
// Redirect to the path from the fetched form
// We don't redirect to req.query.slug as that might lead to open redirect
// vulnerabilities
res.writeHead(307, { Location: '/forms/' + form.slug });
res.end();
};

Now that we have an API route dealing with the cookie and redirect. We can use a dynamic route to fetch the form by slug. This will let us have a route for each form in our project at /forms/:slug.

// pages/forms/[slug].js
import React from 'react';
import { FormiumForm } from '@formium/react';
import { formium } from '../../lib/formium';
export default function FormPage(props) {
const [success, setSuccess] = React.useState(false);
// We'll use state to store the data if we're in preview
// mode (instead of sending to Formium Storage
const [data, setData] = React.useState({});
// Show a success message
if (success) {
return (
<div>
<h1>Thank you! Your response has been recorded.</h1>
<br />
{/* Show a special success message in preview mode */}
{props.preview ? (
<>
<hr />
<p>
<small>
Note: This was a test submission. It would have produced this
data:
</small>
</p>
<pre style={{ fontSize: 11 }}>{JSON.stringify(data, null, 2)}</pre>
<button type="button" onClick={() => window.location.reload()}>
Reset Form
</button>
<hr />
</>
) : null}
</div>
);
}
return (
<>
<FormiumForm
data={props.form}
onSubmit={async (values) => {
if (props.preview) {
setData(values);
} else {
await formium.submitForm(form.slug, values);
}
setSuccess(true);
}}
/>
</>
);
}
export const getStaticProps = async ({
params,
preview = false,
previewData = {},
}) => {
// Fetch the form based on the `slug`. Potentially pass the
// revisionId out of the previewData cookie
const form = await formium.getFormBySlug(params.slug, {
revisionId: previewData.revisionId,
});
return {
props: {
preview,
form,
},
};
};
export const getStaticPaths = async () => {
// Fetch our project's forms
const { data: forms } = await formium.findForms();
// Create a static path for each form at /forms/:slug
return {
paths: forms.map((form) => ({
params: { slug: form.slug },
})),
fallback: false,
};
};

If you want to test your setup locally (without deploying to Vercel), you can do so by using a tunneling tool like NGrok and point it at the port you're running your Next.js app on. The other option is to just deploy your application to Vercel, just be sure to enter your Formium credentials as environment variables. Either way, once your app is on the public internet, open up the Formium form you want to develop against and set the Preview URL (like we described above) to be https://xxxx.com/api/preview where the xxx.com is either your ngrok URL or your Vercel deployment.

Once this is done, make a few changes to your form in the Formium editor. When you're ready, Press the Preview button again, and you should see the changes you've just made reflected on your deployment URL. Congrats! You've just setup your first Preview URL. You can and use the same Preview URL on multiple forms in your app as needed (as long as your API Route knows how to handle the request).

If you gotten this far, you're a badass. You're using Formium exactly we use it and for exactly what it was built for. We love Next.js and we think Vercel is the very best place to deploy it because of awesome features such as Preview Mode.

If you have any questions or need support, ping us at support@formium.io. And as always, check out Formium's GitHub repo for even more Formium and Next.js examples.

-J


Written by
Jared Palmer

Last Edited on September 17, 2020

Build forms, without the tears.