Versioning in the GOV.UK Prototype Kit
Versioning allows you to maintain multiple iterations of your prototype without affecting previous versions.
This is particularly useful for:
- user research — keep an older version live while testing a new one
- stakeholder feedback — compare multiple versions side-by-side to collaborate more effectively
- incremental changes — modify and improve designs easily without breaking existing functionality
It’s important to keep track of what changes you make and why. Consider using an index page to note these down or a separate changelog.
Key features
- version-agnostic forms — HTML forms do not require hardcoded
actionattributes - automated routing and redirects — a function automatically prefixes all redirects
- scalable and maintainable — adding a new version is straightforward
Folder structure
/project-root
├── app/
│ ├── routes.js
│ └── views/
│ ├── v1/
│ │ ├── routing.js
│ │ ├── question-1.html
│ │ ├── question-2.html
│ │ └── nested/
│ │ ├── question-1.html
│ │ └── question-2.html
│ └── v2/
│ ├── routing.js
│ ├── question-1.html
│ └── question-2.html
routes.js— this is the central routing file, it mounts each version’s router under its respective path (such as,/v1or/v2)routing.js— these files define the routes and ensure that all navigation stays within the correct version
Example question page
<form method="post" novalidate>
<h1 class="govuk-heading-xl">Question 1</h1>
<button type="submit" class="govuk-button" data-module="govuk-button">
Continue
</button>
</form>
The <form> element should not have an action attribute.
Automatic redirects
This ensures that redirects always include the correct version prefix. If you call res.redirect('/question-2'), it automatically updates to /v1/question-2 or /v2/question-2, depending on the version.
This code should go in the routing.js file for each version.
module.exports = () => {
const govukPrototypeKit = require('govuk-prototype-kit');
const subRouter = govukPrototypeKit.requests.setupRouter();
subRouter.use((req, res, next) => {
const originalRedirect = res.redirect;
res.redirect = function(url) {
if (url.startsWith('/') && !url.startsWith(req.baseUrl)) {
url = req.baseUrl + url;
}
return originalRedirect.call(this, url);
};
next();
});
subRouter.post('/question-1', (req, res) => {
res.redirect('/question-2');
});
subRouter.post('/question-2', (req, res) => {
res.redirect('/question-1');
});
return subRouter;
};
You must type out the full path for the question page and the redirect page (for example, /question-1 or /question-2). You do not need to include v1 or v2—this is handled automatically. However, if your version folder contains subdirectories, you should include the full nested path (such as, /nested/question-1).
The subRouter function should be used instead of the main router function.
Check out the GitHub repository for a working example.
Mounting version-specific routers
const govukPrototypeKit = require('govuk-prototype-kit');
const router = govukPrototypeKit.requests.setupRouter();
router.use('/v1', require('./views/v1/routing')());
router.use('/v2', require('./views/v2/routing')());
module.exports = router;
This is the routes.js file. It mounts the routing.js files from each version.
Creating a new version
- Duplicate the
v2folder and rename it tov3. This will create a copy of all pages and routes, so you can update them without affecting previous versions. - Open the
routes.jsfile and add the following line to mount your new version:This ensures that requests torouter.use('/v3', require('./views/v3/routing')());/v3are handled by thev3routing file. - Edit the files inside the
v3folder to make your changes.