azurenuxt.jsazure-web-app-serviceazure-appservicetrailing-slash

Nuxt app front end on Azure app service server issue with trailing slash. When refreshing or redirecting, adds slash and routes to old version of page


So we have this strange issue on our Nuxt 2 front end which is running on a server in Azure app service. The back end is Java spring also running on Azure app service. So what happens is that anytime the app redirects to another link, or you refresh the page, or you open link in a new tab, it adds a trailing slash to the link. For most pages, this doesn't seem to be an issue. However, we made an update to a page, to remove an old embedded form and instead have a link to a new page with the form on it. The issue becomes apparent when you try to login on the updated page. It logs you in, then redirects to the last page you were on. But it adds a trailing slash. So /SomePage becomes /SomePage/. When you refresh the page, the same thing happens. Also should note we wanted a similar url structure for the new page, so we made a dynamic route page out of the old page. So made a directory called SomePage, then added the page as _SomePage.vue and the new form page as Form.vue. So the link to the new form page would be /SomePage/Form.

It seems like when this trailing slash appears, then the page does not function as expected. When on this trailing slash version of the page, the page loads, but it's like it's loading an older version of the page. Like for the page I mentioned above, it should no longer have an embedded form, but when I'm on /SomePage/ the embedded form still appears. Tried messing with the routing and setting up the pages to have their own unique routes. So instead of /SomePage/Form, it would be /Form. But the same issue still happened. So it brought it back to the old setup of /SomePage/Form. After doing this, /SomePage now tries to link to that /Form page but it now looks like /Form/. So I removed that page, and it should no longer exist under that route, but it's like it's looking at older version of the pages or something. The trailing slash only seems to appear on the Azure app service server. When running locally, it does not appear.

I tried adding into my Nuxt.config.js

trailingSlash: false

This didn't help and just caused more issues. Now the trailing slash still appears but causes error on any of those pages. Tried adding a redirect, this works for now and redirects away from /SomePage/ to /SomePage/Form but I know on our test server it got messed up after too many updates and stopped working. Tried adding a web.config file to add rules to remove slash but this didn't seem to work. Not sure what to try next, and not really sure what the cause of the issue could be. Is the issue with Nuxt, or is it with the Azure app service?

So it seems the fix would be either to remove this trailing slash so it doesn't appear at the end of links anymore. Or to remove these older cached versions of pages that seem to appear only when you are on one of those trailing Slash pages. I'll add any code or configs if that would help figure this issue out. Just need to know what to look at.

Here is the SomePage.vue

export default {
  name: 'SomePage',
  layout: 'about',
  mixins: [global],
  //middleware: 'trailingslash',
  components: {

  },
  data() {
    return {
      metaDescription: null,
      metaKeywords: null,
      backupDescription: 'backup',
      backupKeywords: 'Backup',
      toggle: false,
      showInfo: true,
      loading: false,
      userId: '',
      productId: '',
      productName: '',
      existingSubscriptionId: '',
      startDate: '',
      endDate: '',
      active: false,
      institution: '',
      firstName: '',
      lastName: '',
      canRenew: false,
      subscriptionCost: 0.0,
      renewal: false
    };
  },
  head() {
    return {
      title: 'SomePage',
      meta: [
        {
          hid: 'somePage',
          name: 'somePage',
          content: 'somePage'
        },
        {
          name: 'description',
          hid: 'description-advantage',
          content: (this.metaDescription != null) ? this.metaDescription : this.backupDescription,
        },
        {
          name: 'keywords',
          hid: 'keywords-advantage',
          content: (this.metaKeywords != null) ? this.metaKeywords : this.backupKeywords,
        }
      ]
    }
  },
  mounted() {
    if (process.browser) {
      this.$gtag('config', process.env.GOOGLE_STREAM_ID, {
        page_title: this.$metaInfo.title,
        page_path: this.$route.fullPath,
      })
    }
    this.getSubscribeToFormPage();

  },
  created () {
    this.getMetaKeywordsAndDescription();
    this.loading = true;

    if (this.$route.params.subscribe) {
      this.toggle = true;
    }

  },
  methods: {
    getMetaKeywordsAndDescription() {
      return new Promise((resolve, reject) => {
        ConnectionContentService.getSectionKeywordsAndDescriptionByContentType(23, 24)
            .then((response) => {
              if (response) {
                this.metaKeywords = response.keywords;
                this.metaDescription = response.description;
              }
            })
            .catch((error) => {
              console.log(error);
            });
      });
    },
    viewSampleDocs() {
      this.$store.commit('data/setSampledocsCountryFilter', null);
      this.$store.commit('data/setSampledocsCategoryFilter', null);
      this.$store.commit('data/setSampledocsKeywordFilter', null);
      this.$store.commit('data/setSampledocsFalsifiedFilter', null);
      this.$store.commit('data/setSampledocsIssuedFilter', null);
      this.$store.commit('data/setSampledocsCompletedFilter', null);
      this.$store.commit('data/setSampledocsSortFilter', null);
      this.$store.commit('data/setCurrentPageSampledocs', null);
      this.$router.push({ path: '/SampleDocumentsViewAll'});
    },
    goToSubscribePage(){
      this.$router.push({ path: '/Advantage/Subscribe'});
    },
    paymentFocus(){
      this.toggle = !this.toggle;
      this.showInfo = !this.showInfo;
    },
    goToUserProfile(){
      this.$router.push({ path: '/UserDashboard'});
    },
    showLoginModal() {
      //
      this.$bvModal.show("bv-modal-login");
    },
    openChartKeys(){
      this.$store.commit('data/setResourcesCategoryFilter',{text:"Charts & Keys", value: process.env.NUXT_RESOURCE_CATEGORY_CHARTS});
      this.$store.commit('data/setResourcesCountryFilter', null);
      this.$store.commit('data/setCurrentPageResources', null);
      this.$store.commit('data/setResourcesSortFilter', null);
      this.$router.push({ path: '/ResourceBoardViewAll'});
    },
    openPublications() {
      this.$store.commit('data/setResourcesCategoryFilter',{text:"Research & Analysis", value: 18});
      this.$store.commit('data/setResourcesCountryFilter', null);
      this.$store.commit('data/setCurrentPageResources', null);
      this.$store.commit('data/setResourcesSortFilter', null);
      this.$router.push({ path: '/ResourceBoardViewAll'});
    },
    getSubscribeToFormPage() {

      return new Promise((resolve, reject) => {
        SubscriptionService.subscribeToFormPage()
            .then((response) => {
              if (response) {
                this.subscriptionCost = response.productPrice;
                this.institutionName = response.institution;
                this.existingSubscriptionId = response.existingSubscriptionId;
                this.productId = response.productId;
                this.productName = response.productName;
                this.userId = response.userId;
                this.startDate = response.existingSubscriptionStartDate;
                this.endDate = response.existingSubscriptionEndDate;
                this.active = response.existingSubscriptionIsActive;
                this.canRenew = response.canRenew;

                if (response.firstName)  this.firstName = response.firstName;
                if (response.lastName)  this.lastName = response.lastName;

                if (this.existingSubscriptionId) this.renewal = true;
                this.loading = false;
              }
            })
            .catch((error) => {
              let message = '';
              try {
                if (error.response.data) {
                  message = error.response.data.status + ' : ' + error.response.data.error;
                  this.$toast.error(message, {
                    position: "top-right",
                    timeout: 5000,
                    closeOnClick: true,
                    pauseOnFocusLoss: true,
                    pauseOnHover: true,
                    draggable: true,
                    draggablePercent: 0.6,
                    showCloseButtonOnHover: false,
                    hideProgressBar: true,
                    closeButton: "button",
                    icon: true,
                    rtl: false
                  });
                }
                else if (error.response && error.response.status === 403) {
                  message = '403: Insufficient Privileges';
                  this.$toast.error(message, {
                    position: "top-right",
                    timeout: 5000,
                    closeOnClick: true,
                    pauseOnFocusLoss: true,
                    pauseOnHover: true,
                    draggable: true,
                    draggablePercent: 0.6,
                    showCloseButtonOnHover: false,
                    hideProgressBar: true,
                    closeButton: "button",
                    icon: true,
                    rtl: false
                  });
                }
                // else if (error.response.status === 401) {
                //   this.$nuxt.error({ statusCode: 401, message: 'User is not authenticated' });
                // }
                this.loading = false;
              } catch (e) {
                this.loading = false;
                this.$nuxt.error(error);
              }
            });
      });
    }
  }


Solution

  • Nuxt app front end on Azure app service server issue with trailing slash. When refreshing or redirecting, adds slash and routes to old version of page

    On Azure App Service, create or modify a web.config file to normalize URLs and prevent trailing slashes from redirecting to a different cached route:

    <configuration>
      <system.webServer>
        <rewrite>
          <rules>
            <rule name="RemoveTrailingSlash" stopProcessing="true">
              <match url="(.*)/$" />
              <conditions>
                <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
                <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
              </conditions>
              <action type="Redirect" url="/{R:1}" redirectType="Permanent" />
            </rule>
          </rules>
        </rewrite>
      </system.webServer>
    </configuration>
    

    You can refer to the following Microsoft Q&A documentation for Add RemoveTrailingSlashRule

    Deploy this file to the root of the Nuxt app’s dist/ or public folder, depending on the deployment type.

    Sometimes App Service serves old .nuxt/dist content from earlier deployments.