diff --git a/compendium-frontend/src/survey/ProgressBar.tsx b/compendium-frontend/src/survey/ProgressBar.tsx
index 0232a12c0da82bff755a15cfcbb0decae598c997..aa2d1cc843092f44beba88f3f453560fb7f79899 100644
--- a/compendium-frontend/src/survey/ProgressBar.tsx
+++ b/compendium-frontend/src/survey/ProgressBar.tsx
@@ -8,7 +8,7 @@ interface Progress {
     pageTitle: string;
 }
 
-function ProgressBar({ surveyModel, pageNoSetter }) {
+function ProgressBar({ surveyModel, pageNoSetter, pageNo}) {
     const [progress, setProgress] = useState<Progress[]>([]);
 
     const filterCallback = (question) => {
@@ -78,7 +78,7 @@ function ProgressBar({ surveyModel, pageNoSetter }) {
                             }}>{index + 1}</span>
                             <span style={{
                                 whiteSpace: "nowrap",
-                                ...(surveyModel.currentPageNo == index) && {
+                                ...(pageNo == index) && {
                                     fontWeight: "bold",
                                 },
                             }}>{sectionProgress.pageTitle}</span>
diff --git a/compendium-frontend/src/survey/Prompt.tsx b/compendium-frontend/src/survey/Prompt.tsx
index f2ce6b0d8cefef8d303fbdc1afb2dd414154d78d..713b2171429786401adf07812acd096c7bfa7ffe 100644
--- a/compendium-frontend/src/survey/Prompt.tsx
+++ b/compendium-frontend/src/survey/Prompt.tsx
@@ -1,4 +1,3 @@
-import React from "react";
 import { useBlocker } from "react-router-dom";
 
 // adapted from https://stackoverflow.com/a/75920683
diff --git a/compendium-frontend/src/survey/SurveyComponent.tsx b/compendium-frontend/src/survey/SurveyComponent.tsx
index 051f45648accabd0056ef10242c894e4ed5e8bd3..6e48c3100c84dbd8b4e63ce33e1335f57b693e25 100644
--- a/compendium-frontend/src/survey/SurveyComponent.tsx
+++ b/compendium-frontend/src/survey/SurveyComponent.tsx
@@ -1,4 +1,4 @@
-import { useCallback } from "react";
+import { useEffect } from "react";
 import { Question } from "survey-core";
 import { Survey } from "survey-react-ui";
 import { VerificationStatus } from './Schema';
@@ -144,50 +144,52 @@ function setVerifyButton(question: Question, state: VerificationStatus, surveyMo
 }
 
 function SurveyComponent({ surveyModel }) {
-
-    const alwaysSetVerify = useCallback((_, options) => {
-        const status = surveyModel.verificationStatus.get(options.question.name);
-        const readonly = options.question?.readOnly;
-
-        if (status && !readonly) {
-            setVerifyButton(options.question, status, surveyModel);
+    
+    useEffect(() => {
+        const updateFromUnverified = (_, options) => {
+            const currentStatus = surveyModel.verificationStatus.get(options.question.name);
+            if (currentStatus == VerificationStatus.Unverified) {
+                setVerifyButton(options.question, VerificationStatus.Edited, surveyModel);
+            }
         }
-        else if (readonly) {
-            strikeThroughOrHide(options.question);
+        
+        const alwaysSetVerify = (_, options) => {
+            const status = surveyModel.verificationStatus.get(options.question.name);
+            const readonly = options.question?.readOnly;
+    
+            if (status && !readonly) {
+                setVerifyButton(options.question, status, surveyModel);
+            }
+            else if (readonly) {
+                strikeThroughOrHide(options.question);
+            }
         }
-    }, [surveyModel])
 
-    const updateFromUnverified = useCallback((_, options) => {
-        const currentStatus = surveyModel.verificationStatus.get(options.question.name);
-        if (currentStatus == VerificationStatus.Unverified) {
-            setVerifyButton(options.question, VerificationStatus.Edited, surveyModel);
+        if (!surveyModel.onAfterRenderQuestion.hasFunc(alwaysSetVerify)) {
+            surveyModel.onAfterRenderQuestion.add(alwaysSetVerify);
+            surveyModel.onAfterRenderQuestion.add(fixTitleCss);
         }
-    }, [surveyModel])
-
-    if (!surveyModel.onAfterRenderQuestion.hasFunc(alwaysSetVerify)) {
-        surveyModel.onAfterRenderQuestion.add(alwaysSetVerify);
-        surveyModel.onAfterRenderQuestion.add(fixTitleCss);
-    }
 
-    if (!surveyModel.onValueChanged.hasFunc(updateFromUnverified)) {
-        surveyModel.onValueChanged.add(updateFromUnverified);
-    }
+        if (!surveyModel.onValueChanged.hasFunc(updateFromUnverified)) {
+            surveyModel.onValueChanged.add(updateFromUnverified);
+        }
 
-    if (!surveyModel.onUpdateQuestionCssClasses.hasFunc(hideCheckboxLabels)) {
-        surveyModel.onUpdateQuestionCssClasses.add(hideCheckboxLabels);
-    }
+        if (!surveyModel.onUpdateQuestionCssClasses.hasFunc(hideCheckboxLabels)) {
+            surveyModel.onUpdateQuestionCssClasses.add(hideCheckboxLabels);
+        }
 
-    if (!surveyModel.onMatrixAfterCellRender.hasFunc(customDescriptionCallback)) {
-        // NB I would have preferred using onAfterRenderQuestion, but unfortunately that is
-        // not always triggered on re-renders (specifically when extra columns become visble or invisible)
-        surveyModel.onMatrixAfterCellRender.add(customDescriptionCallback);
-    }
+        if (!surveyModel.onMatrixAfterCellRender.hasFunc(customDescriptionCallback)) {
+            // NB I would have preferred using onAfterRenderQuestion, but unfortunately that is
+            // not always triggered on re-renders (specifically when extra columns become visble or invisible)
+            surveyModel.onMatrixAfterCellRender.add(customDescriptionCallback);
+        }
 
-    if (!surveyModel.onTextMarkdown.hasFunc(setDescriptionHTML)) {
-        // WARNING: THIS ASSUMES THAT THE QUESTION DESCRIPTION IS NOT USER-PROVIDED.
-        // SETTING THE HTML HERE IS A SECURITY RISK IF NOT PROPERLY SANITISED
-        surveyModel.onTextMarkdown.add(setDescriptionHTML);
-    }
+        if (!surveyModel.onTextMarkdown.hasFunc(setDescriptionHTML)) {
+            // WARNING: THIS ASSUMES THAT THE QUESTION DESCRIPTION IS NOT USER-PROVIDED.
+            // SETTING THE HTML HERE IS A SECURITY RISK IF NOT PROPERLY SANITISED
+            surveyModel.onTextMarkdown.add(setDescriptionHTML);
+        }
+    }, [surveyModel]);
 
     return <Survey model={surveyModel} />
 }
diff --git a/compendium-frontend/src/survey/SurveyContainerComponent.tsx b/compendium-frontend/src/survey/SurveyContainerComponent.tsx
index dbdf1c1dc83915aeb8520bb1bce66de987c62ee3..5661a7c00b17cd2fa76d243dc9c9c9b4f8132d08 100644
--- a/compendium-frontend/src/survey/SurveyContainerComponent.tsx
+++ b/compendium-frontend/src/survey/SurveyContainerComponent.tsx
@@ -11,76 +11,10 @@ import "survey-core/modern.min.css";
 import './survey.scss';
 import useMatomo from "../matomo/UseMatomo";
 import { FunctionFactory } from "survey-core";
-import { validationFunctions } from "./validation/validation";
+import { validateQuestion, oldValidateWebsiteUrl } from "./validation/validation";
 import SurveySidebar from "./management/SurveySidebar";
 import { userContext } from "../providers/UserProvider";
 
-interface ValidationQuestion {
-    name?: string;
-    value?: string | number | null;
-    data?: ValidationQuestion;
-}
-
-// Overrides for questions that need to be validated differently from the default expression in their group
-const questionOverrides = {
-    data_protection_contact: (..._args) => true, // don't validate the contact field, anything goes..
-}
-
-function oldValidateWebsiteUrl(params) {
-    let value = params[0];
-    if ((value == undefined || value == null || value == '')) {
-        return true;
-    }
-    try {
-        value = value.trim();
-        if (value.includes(" ")) {
-            return false;
-        }
-        // if there's not a protocol, add one for the test
-        if (!value.includes(":/")) {
-            value = "https://" + value;
-        }
-
-        const url = new URL(value);
-        return !!url
-    } catch {
-        return false;
-    }
-}
-
-function validateQuestion(this: { question: ValidationQuestion, row?}, params) {
-    try {
-        const question = this.question;
-        const validator = params[0] || undefined;
-
-        const matrix = question.data && 'name' in question.data;
-
-        let questionName;
-
-        if (matrix) {
-            questionName = question.data!.name;
-        } else {
-            questionName = question.name;
-        }
-        const value = question.value
-
-        const hasOverride = questionOverrides[questionName];
-        if (hasOverride) {
-            return hasOverride(value, ...params.slice(1));
-        }
-
-        const validationFunction = validationFunctions[validator];
-        if (!validationFunction) {
-            throw new Error(`Validation function ${validator} not found for question ${questionName}`);
-        }
-
-        return validationFunction(value, ...params.slice(1));
-    } catch (e) {
-        console.error(e);
-        return false;
-    }
-}
-
 Serializer.addProperty("itemvalue", "customDescription:text");
 Serializer.addProperty("question", "hideCheckboxLabels:boolean");
 
@@ -147,6 +81,7 @@ function SurveyContainerComponent({ loadFrom }) {
             survey.data = json['data'];
             survey.clearIncorrectValues(true);
             survey.currentPageNo = json['page'];
+            // setPageNo(json['page']);
             survey.mode = json['mode'];
 
             survey['lockedBy'] = json['locked_by'];
@@ -308,7 +243,9 @@ function SurveyContainerComponent({ loadFrom }) {
     }
 
     const onPageChange = (page) => {
+        if (!surveyModel) return;
         surveyModel.currentPageNo = page;
+        setSurveyModel(Object.create(surveyModel));
     }
 
     return (
@@ -316,8 +253,9 @@ function SurveyContainerComponent({ loadFrom }) {
             {isAdmin ? <SurveySidebar /> : null}
             <Container className="survey-container">
                 <Toaster />
-                <Prompt message="Are you sure you want to leave this page? Information you've entered may not be saved." when={() => { return surveyModel.mode == 'edit' && !!nren; }} onPageExit={onPageExitThroughRouter} />
-                <SurveyNavigationComponent onPageChange={onPageChange} surveyModel={surveyModel} surveyActions={surveyActions} year={year} nren={nren}>
+                <Prompt message="Are you sure you want to leave this page? Information you've entered may not be saved."
+                    when={() => { return surveyModel.mode == 'edit' && !!nren; }} onPageExit={onPageExitThroughRouter} />
+                <SurveyNavigationComponent surveyModel={surveyModel} surveyActions={surveyActions} year={year} nren={nren} onPageChange={onPageChange}>
                     <SurveyComponent surveyModel={surveyModel} />
                 </SurveyNavigationComponent>
             </Container>
diff --git a/compendium-frontend/src/survey/SurveyNavigationComponent.tsx b/compendium-frontend/src/survey/SurveyNavigationComponent.tsx
index b76dabcdf7aa49a20aa39c4c518d46c6f4afd837..10fd4991bea9ed77aca40107ed62ffb7523dc92b 100644
--- a/compendium-frontend/src/survey/SurveyNavigationComponent.tsx
+++ b/compendium-frontend/src/survey/SurveyNavigationComponent.tsx
@@ -1,46 +1,24 @@
-import { useContext, useEffect, useState, useCallback } from "react";
+import { useContext } from "react";
 import ProgressBar from './ProgressBar';
 import { Container, Row } from "react-bootstrap";
 import { userContext } from "../providers/UserProvider";
 
-function SurveyNavigationComponent({ surveyModel, surveyActions, year, nren, children, onPageChange }) {
-    // We use some state variables that are directly derived from the surveyModel to
-    // ensure React rerenders properly. It would honestly be just as easy to remove the state
-    // and force the rerender ourselves, because we know exactly when the rerender is necessary.
-    const [pageNo, setPageNo] = useState(0);
-    const [editing, setEditing] = useState(false);
-    const [lockedBy, setLockedBy] = useState("");
-    const [responseStatus, setResponseStatus] = useState("");
+function SurveyNavigationComponent({ surveyModel, surveyActions, year, nren, children, onPageChange
+ }) {   
     const { user: loggedInUser } = useContext(userContext);
+    const pageNo = surveyModel?.currentPageNo ?? 0;
+    const editing = surveyModel?.mode === 'edit';
+    const lockedBy = surveyModel?.["lockedBy"] ?? "";
+    const responseStatus = surveyModel?.["status"] ?? "";
 
-    // useCallback to keep the linter happy with the useEffect dependencies below
-    const copySurveyState = useCallback(() => {
-        setEditing(surveyModel.mode == 'edit');
-        setLockedBy(surveyModel.lockedBy);
-        setPageNo(surveyModel.currentPageNo);
-        setResponseStatus(surveyModel.status);
-    }, [surveyModel]);
-
-    useEffect(() => {
-        copySurveyState();
-    }, [copySurveyState]);
-
-    const pageNoSetter = (page) => {
-        setPageNo(page);
-        onPageChange(page);
-    }
-    // const decrementPageNo = () => { pageNoSetter(surveyModel.currentPageNo - 1); };
-    const incrementPageNo = () => { pageNoSetter(surveyModel.currentPageNo + 1); };
+    const incrementPageNo = () => { onPageChange(surveyModel.currentPageNo + 1); };
 
     const doSurveyAction = async (action) => {
         await surveyActions[action]();
-        copySurveyState();
+        // onPageChange triggers a re-render, just keep the same page number
+        onPageChange(surveyModel.currentPageNo);
     }
 
-    const renderActionButton = (text, action) => {
-        return renderButton(text, () => doSurveyAction(action));
-    };
-
     const renderButton = (text, action) => {
         return (
             <button className="sv-btn sv-btn--navigation" onClick={action}>
@@ -49,6 +27,10 @@ function SurveyNavigationComponent({ surveyModel, surveyActions, year, nren, chi
         );
     };
 
+    const renderActionButton = (text, action) => {
+        return renderButton(text, () => doSurveyAction(action));
+    };
+
     const saveAndStopEdit = 'Save and stop editing';
     const save = 'Save progress';
     const startEditing = 'Start editing';
@@ -63,8 +45,6 @@ function SurveyNavigationComponent({ surveyModel, surveyActions, year, nren, chi
                 {editing && renderActionButton(saveAndStopEdit, 'saveAndStopEdit')}
                 {editing && renderActionButton(completeSurvey, 'complete')}
                 {(pageNo !== surveyModel.visiblePages.length - 1) && renderButton('Next Section', incrementPageNo)}
-                {/* {renderActionButton('Validate Page', 'validatePage')} */}
-                {/* {pageNo !== 0 && renderButton('Previous Page', decrementPageNo)} */}
             </div>
         );
     };
@@ -103,7 +83,7 @@ function SurveyNavigationComponent({ surveyModel, surveyActions, year, nren, chi
                 )}
             </Row>
             <Row>
-                <ProgressBar surveyModel={surveyModel} pageNoSetter={pageNoSetter} />
+                <ProgressBar surveyModel={surveyModel} pageNoSetter={onPageChange} pageNo={pageNo} />
                 {children}
             </Row>
             <Row>