Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

nicolassc's avatar

Inertia redirects to / after failed validation

Hi!

I have this weird issue: Inertia constantly redirects to / after a failed validation.

If a form validation fails, Inertia returns a response with the parameter url as "/".

However, there is one component where it doesn't happen. "Pages/Dashboard"

Any other component has that issue.

See a comparison here (first is ok, second redirects to /):

<script setup>
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout.vue';
import {Head, useForm} from '@inertiajs/vue3';
import PageHeader from "@/Components/PageHeader.vue";
import PrimaryButton from "@/Components/PrimaryButton.vue";
import { ref } from 'vue';
import SecondaryButton from "@/Components/SecondaryButton.vue";
import Modal from "@/Components/Modal.vue";
import TextInput from "@/Components/TextInput.vue";
import InputLabel from "@/Components/InputLabel.vue";
import InputError from "@/Components/InputError.vue";
import {Link} from "@inertiajs/vue3";
import LinkPrimaryButton from "@/Components/LinkPrimaryButton.vue";

const creatingNewSite = ref(null);
const nameInput = ref(null);

const form = useForm({
    name: '',
});

const createNewSiteForm = () => {
    creatingNewSite.value = true;
}

const createSite = () => {
    form.post(route('websites.store'), {
        onError: () => nameInput.value.focus(),
    });
};

const closeModal = () => {
    creatingNewSite.value = false;

    form.reset();
};


</script>

<template>
    <Head title="Tableau de bord" />

    <AuthenticatedLayout>
        <template #header>
            <PageHeader>Tableau de bord</PageHeader>
            <PrimaryButton @click="createNewSiteForm" v-if="$page.props.websites.length != 0">Créer un nouveau site</PrimaryButton>
        </template>

      <div class="space-y-6">
        <article v-for="website in $page.props.websites" class="bg-white flex flex-col lg:flex-row items-center lg:items-start justify-between rounded-lg shadow-md p-4 lg:p-6">
          <div class="w-full lg:w-1/2 flex justify-center lg:justify-start">
            <div class="relative">
              <img src="https://images.unsplash.com/photo-1496128858413-b36217c2ce36?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=3603&q=80" alt="" class="aspect-[16/9] w-full rounded-2xl bg-gray-100 object-cover sm:aspect-[2/1] lg:aspect-[3/2]">
              <div class="absolute inset-0 rounded-2xl ring-1 ring-inset ring-gray-900/10"></div>
            </div>
          </div>
          <div class="w-full lg:w-1/2 max-w-xl mt-4 lg:mt-0 lg:pl-6 flex flex-col justify-center lg:justify-between">
            <div class="flex flex-col justify-center h-full">
              <div class="flex items-center gap-x-4 text-xs">
                <time datetime="2020-03-16" class="text-gray-500">Dernière modification le 16 mars 2023</time>
              </div>


              <div class="group relative mt-2">
                <h3 class="text-lg font-semibold leading-6 text-gray-900 group-hover:text-gray-600">
                  <Link :href="route('websites.show', website.slug)">
                    <span class="absolute inset-0"></span>
                    {{ website.name }}
                  </Link>
                </h3>
              </div>
              <div class="mt-4 lg:mt-0 lg:pt-2 flex justify-center lg:justify-start">
                <LinkPrimaryButton class="btn-small" :href="route('websites.show', website.slug)">Modifier le site</LinkPrimaryButton>
              </div>
            </div>
          </div>
        </article>
        <div class="text-center" v-if="$page.props.websites.length == 0">
          <svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
              <path stroke-linecap="round" stroke-linejoin="round" d="M12 21a9.004 9.004 0 008.716-6.747M12 21a9.004 9.004 0 01-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 017.843 4.582M12 3a8.997 8.997 0 00-7.843 4.582m15.686 0A11.953 11.953 0 0112 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0121 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0112 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 013 12c0-1.605.42-3.113 1.157-4.418" />
          </svg>
          <h3 class="mt-2 text-sm font-semibold text-gray-900">Aucun site web</h3>
          <p class="mt-1 text-sm text-gray-500">Créez un nouveau site web dès maintenant!</p>
          <div class="mt-4">
            <PrimaryButton class="btn-small" @click="createNewSiteForm">
              <svg class="-ml-0.5 mr-1.5 h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
                <path d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z" />
              </svg>
              Créer un nouveau site web
            </PrimaryButton>
          </div>
        </div>

      </div>

        <Modal :show="creatingNewSite" @close="closeModal">
            <div class="p-6">
                <h2 class="text-lg font-medium text-gray-900">
                    Create site
                </h2>

                <div class="mt-6">
                    <InputLabel for="name" value="Site name" />

                    <TextInput
                        id="name"
                        ref="nameInput"
                        v-model="form.name"
                        type="name"
                        class="mt-1 block w-3/4"
                        @keyup.enter="createSite"
                    />

                    <InputError :message="form.errors.name" class="mt-2" />
                </div>

                <div class="mt-6 flex justify-end">
                    <SecondaryButton @click="closeModal"> Cancel </SecondaryButton>

                    <PrimaryButton
                        class="ml-3"
                        :class="{ 'opacity-25': form.processing }"
                        :disabled="form.processing"
                        @click="createSite"
                    >
                        Create site
                    </PrimaryButton>
                </div>
            </div>
        </Modal>

    </AuthenticatedLayout>
</template>
    public function store(Request $request) {
        $data = $request->validate([
            'name' => 'required|string|min:1|max:255'
        ]);

        $website = $request->user()->websites()->create($data);

        $page = $website->pages()->create([
            'name' => 'Accueil'
        ]);

        $website->homepage_id = $page->id;
        $website->save();

        return to_route('websites.show', $website);
    }
<script setup>
import PrimaryButton from "@/Components/PrimaryButton.vue";
import Modal from "@/Components/Modal.vue";
import {useForm, usePage} from "@inertiajs/vue3";
import {ref} from "vue";
import SecondaryButton from "@/Components/SecondaryButton.vue";
import InputError from "@/Components/InputError.vue";
import InputLabel from "@/Components/InputLabel.vue";
import TextInput from "@/Components/TextInput.vue";
import ColorThief from "colorthief";

const {model} = defineProps(['model']);
const creatingEvent = ref(false);
const form = useForm({
  name: '',
  description: '',
  image: '',
  place: '',
  starts_at: '',
  ends_at: '',
  inscription_link: ''
})

const fileInput = ref(null);
const currentImage = ref(null);

function onSelectImage($event) {

  let file = $event.target.files[0];
  form.image = file;

  if (!file) {
    form.image = '';
    return;
  }

  const reader = new FileReader();
  reader.onload = (e) => {
    currentImage.value = e.target.result;
  };

  reader.readAsDataURL(file);
}


function closeModal() {
  form.reset();
  if(fileInput.value != null) {
    fileInput.value.value = null;
  }

  currentImage.value = null;

  creatingEvent.value = false;
}

const store = () => {
  form.post(route('events.store', usePage().props.website.slug), {
    onError: () => {},
    onSuccess: () => {
      closeModal();
    }
  })
}

</script>

<template>
  <PrimaryButton @click="creatingEvent = true;">Créer un évènement</PrimaryButton>
  <Modal :show="creatingEvent" @close="closeModal">
    <div class="p-6">
      <h2 class="text-lg font-medium text-gray-900">
        Créer un évènement
      </h2>

      <form @submit.prevent="store">
        <img ref="image" :src="currentImage" v-if="currentImage != null" class="max-h-32 mt-6"/>

        <div class="mt-6" key="logo">
          <InputLabel for="image" value="Image"/>
          <input ref="fileInput" type="file" @input="onSelectImage" />
          <InputError :message="form.errors.image" class="mt-2" />
        </div>


        <div class="mt-6">
          <InputLabel for="name" value="Nom de l'évènement" />

          <TextInput
              id="name"
              v-model="form.name"
              type="text"
              class="mt-1 block w-3/4"
              placeholder="Nom de l'évènement"
          />

          <InputError :message="form.errors.name" class="mt-2" />
        </div>

        <div class="mt-6">
          <InputLabel for="description" value="Description" />

          <textarea
              id="description"
              v-model="form.description"
              type="text"
              class="textarea mt-1 block w-3/4"
              placeholder="Description"
          />

          <InputError :message="form.errors.description" class="mt-2" />
        </div>

        <div class="mt-6">
          <InputLabel for="place" value="Lieu" />

          <TextInput
              id="place"
              v-model="form.place"
              type="text"
              class="textarea mt-1 block w-3/4"
              placeholder="Lieu"
          />

          <InputError :message="form.errors.place" class="mt-2" />
        </div>

        <div class="mt-6">
          <InputLabel for="starts_at" value="Débute le" />

          <input
              id="starts_at"
              v-model="form.starts_at"
              type="datetime-local"
              class="input mt-1 block w-3/4"
              placeholder="Débute le "
          />

          <InputError :message="form.errors.starts_at" class="mt-2" />
        </div>

        <div class="mt-6">
          <InputLabel for="ends_at" value="Termine le" />

          <input
              id="ends_at"
              v-model="form.ends_at"
              type="datetime-local"
              class="input mt-1 block w-3/4"
              placeholder="Termine le "
          />

          <InputError :message="form.errors.ends_at" class="mt-2" />
        </div>

        <div class="mt-6">
          <InputLabel for="inscription_link" value="Lien d'inscription" />

          <TextInput
              id="inscription_link"
              v-model="form.inscription_link"
              class="input mt-1 block w-3/4"
              placeholder="Lien d'inscription"
          />

          <InputError :message="form.errors.inscription_link" class="mt-2" />
        </div>





        <div class="mt-6 flex justify-end">
          <SecondaryButton @click="closeModal"> Annuler </SecondaryButton>

          <PrimaryButton
              class="ml-3"
              :class="{ 'opacity-25': form.processing }"
              :disabled="form.processing"
              @click="store"
          >
            Créer un évènement
          </PrimaryButton>
        </div>

      </form>
    </div>
  </Modal>

</template>
    public function store(Website $website, Request $request) {
        $data = $request->validate([
            'name' => 'required|string|min:1|max:255',
            'description' => 'required|string|min:1|max:10000',
            'place' => 'required|string|min:1|max:255',
            'starts_at' => 'required|date|before:ends_at',
            'ends_at' => 'required|date|after:starts_at',
            'inscription_link' => 'required|url|max:255',
            'image' => 'nullable|image|max:5120'
        ]);

        $event = $website->events()->create(Arr::except($data, 'image'));

        $event->uploadImage('image', $request->file('image'), 600);

        $event->save();

        return to_route('events.index', $website);
    }
0 likes
6 replies
wafto's avatar

I suggest trying redirecting back instead of fixed routes.

return redirect()->back();
nicolassc's avatar

@secondman @wafto yes, but validation redirection happens in the ->validate() call, not at the end of the function?

If I put a dd() after the ->validate() call, it isn't reached. The issue is that for some reason, the validation errors do no return back, but returns to the / route. It is because url()->previous() (which is used in the case of a validation error) returns to "/" by default.

Whether or not I put the redirect at the ends changes nothing if there is a validation error.

I made a custom validator that redirects back manually to fix that error while searching for a permanent fix.

rtyshyk's avatar

I believe you have an issue with referer policy. origin policy send only root page "/" as a refer for any page. Make sure you have 'strict-origin-when-cross-origin' origin policy set or empty, as it is default.

Please or to participate in this conversation.