Giter Club home page Giter Club logo

trailing-slash-guide's Introduction

Trailing Slash Guide

Have trailing slash problems after deploying a static website in production?

This repo explains factually the behavior of:

We also suggest some possible solutions

Intro

Let's get more familiar with trailing slash issues.

Common problems:

  • SEO/perf issues: when browsing /myPath, your host redirects to /myPath/
  • 404 issues: relative link such as <a href="otherPath"> are resolved differently (/otherPath or /myPath/otherPath depending on the presence/absence of a trailing slash
  • UX issues: your host adds a trailing slash, and later your single-page-application frontend router removes it, leading to a confusing experience and flickering url

Causes:

  • static site generators can emit different files for the same path /myPath: /myPath.html or /myPath/index.html (the later can lead to an additional trailing slash)
  • host providers all have a different behavior when serving static files, there is no standard

Summary

Considering this static site:

static
│
├── file.html
│
├── folder
│   └── index.html
│
├── both.html
└── both
    └── index.html

Behavior of various static hosting providers:

Host Settings Url /file /file/ /file.html /folder /folder/ /folder/index.html /both /both/ /both.html /both/index.html
GitHub Pages link 💢 404 ➡️ /folder/
Netlify Default: Pretty Urls on link ➡️ /file ➡️ /folder/ ➡️ /both
Netlify Pretty Urls off link
Vercel Default: cleanUrls=false trailingSlash=undefined link 💢 404 💢 404
Vercel cleanUrls=false trailingSlash=false link 💢 404 💢 404 ➡️ /folder ➡️ /both
Vercel cleanUrls=false trailingSlash=true link 💢 404 💢 404 ➡️ /folder/ ➡️ /both/
Vercel cleanUrls=true trailingSlash=undefined link ➡️ /file ➡️ /folder ➡️ /both ➡️ /both
Vercel cleanUrls=true trailingSlash=false link ➡️ /file ➡️ /file ➡️ /folder ➡️ /folder ➡️ /both ➡️ /both ➡️ /both
Vercel cleanUrls=true trailingSlash=true link ➡️ /file/ ➡️ /file/ ➡️ /folder/ ➡️ /folder/ ➡️ /both/ ➡️ /both/ ➡️ /both/
Cloudflare Pages link ➡️ /file ➡️ /file ➡️ /folder/ ➡️ /folder/ ➡️ /both ➡️ /both/
Render link
Azure Static Web Apps link ➡️ /file

Help Wanted

Let's keep this resource up-to-date, and make it exhaustive together.

trailing-slash-guide's People

Contributors

josh-cena avatar muescha avatar slorber avatar strift avatar styfle avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

trailing-slash-guide's Issues

Add tests for /index.html

Would be handy to test home pages (index.html at the root of the site).

I noticed Vercel throws a ERR_TOO_MANY_REDIRECTS error in this case when cleanUrls=true. I'll raise the issue with Vercel

Update: The Vercel issue appears to be a misconfiguration on our end with Cloudfront, but still think this would be useful :)

Feedback on trailingSlash option + GH Pages

@slorber

Issue / Bug?

I've set "trailingSlash": false on my project following suggestions by the CLI. I have a non-empty base-url, and here is what happens when fetching https://meliorence.github.io/react-native-render-html:

> http -h https://meliorence.github.io/react-native-render-html
HTTP/1.1 301 Moved Permanently
Location: https://meliorence.github.io/react-native-render-html/
...

OK, so GH pages redirects the root (a file named react-native-render-html.html in the gh-pages branch) to the slash-forwards path. That's not great for SEO (unless the sitemap reflects that), but at least if it works... Let's fetch the redirected-url:

> http -h https://meliorence.github.io/react-native-render-html/
HTTP/1.1 200 OK

Good.... However, if you try that out in a browser, you'll see a "Not Found" flash. My understanding of what's happening:

  1. The browser loads the static HTML asset, build the DOM and paints.
  2. The DOM is hydrated.
  3. Docusaurus router (if there is such thing) finds that this route is a mismatch because of the trailingSlash option. It thus flashes a "Not Found" route after which it manages to find the actual route.
404.mp4

Workaround

After a docusaurus build, the gh-pages branch folder structure looks like this:

├── api
├── assets
├── blog
├── docs
├── favicons
├── img
├── video
├── 404.html
├── api.html
├── blog.html
├── opensearch.xml
├── react-native-render-html.html
├── search.html
└── sitemap.xml

By copying react-native-render-html.html into index.html and pushing, the "Not Found" flash bug disappears.

Additional information

@docusaurus/*@2.0.0-beta.4d93c894f

Remarks

Ideally, the sitemap generated for gh-pages would add a trailing slash to the root URL to avoid redirects, or there could be an option to deal with the root.

404 error after adding `trailingSlash: false` in a docusaurus website

(* stackoverflow: https://stackoverflow.com/questions/75442550 *)

I have a website made by docusaurus 2.0.0-beta.18. I realize that, in production, when I click on items in the sidebar, it first goes to https://www.mywebsite.com/docs/a-page. If I reload the page, the url becomes https://www.mywebsite.com/docs/a-page/. This problem does not exist in localhost.

I think a url should be consistent before and after reloading. And like StackOverflow, a good style is not having / in the end. I searched issues of Docusaurus and it seems that trailingSlash would be the solution. So I added trailingSlash: false in docusaurus.config.js.

Then, I deployed this change to my production server with docker and nginx. But loading most of the pages of the website returned a 404 error, while static contents could be loaded. Reloading nginx or recreating docker containers did not help.

By contrast, setting trailingSlash: true returns well the website. And it works well: clicking on items in the sidebar goes to https://www.mywebsite.com/docs/a-page/ ended with /.

So does anyone know why trailingSlash: false makes the website return 404 error?

PS: docusaurus.config.js:

const path = require('path');
module.exports = {
  title: 'my website',
  tagline: 'The tagline of my site',
  onBrokenLinks: 'ignore',
  url: 'https://www.mywebsite.com',
  baseUrl: '/', // if we use GitHub Pages, we need to set this to '/docusaurus/', and also change routing links in some pages
  favicon: 'img/favicon.ico',
  organizationName: 'softtimur', // Usually your GitHub org/user name.
  projectName: 'docusaurus', // Usually your repo name.
  trailingSlash: false,
  plugins: [
    path.resolve(__dirname, './plugins/monaco-loader'),
  ],
  themeConfig: {
    navbar: {
      title: 'my website',
      logo: {
        alt: 'My Site Logo',
        src: 'img/logo.svg',
      },
      items: [
        {
          label: 'Documentation', position: 'left',
          items: [
            { label: 'Introduction', to: 'docs/introduction' },
            { label: 'Getting Started', to: 'docs/try-samples' }
          ]
        },
        { to: 'demo', label: 'Live Demo', position: 'left' }, // to src/pages/demo.js
        {
          label: 'Persons', position: 'left',
          items: [
            { label: 'Get my website', to: 'docs/consumer-buy' },
            { label: 'Follow Free Videos and Contents', to: 'docs/free-videos' },
            { label: 'Request a Help', to: 'docs/help-your-work' }
          ]
        },
        {
          label: 'Businesses', position: 'left',
          items: [
            { label: 'Get my website Together', to: 'docs/group-buy' },
            { label: 'Other Services', to: 'docs/web-development' }
          ]
        },
        {
          label: 'Language and Programming', position: 'left',
          items: [
            { label: 'Course in English', to: 'https://go.mywebsite.com/RJ2HPz' },
            { label: 'Course in Chinese', to: 'https://go.mywebsite.com/2KjQzL' },
          ]
        }
        ,
        {
          to: '#',
          position: 'right',
        },
        {
          to: '/logout',
          label: 'Sign Out',
          position: 'right',
        },
        {
          to: '/login',
          label: 'Sign In',
          position: 'right',
        },
        {
          type: 'localeDropdown',
          position: 'right',
        },

      ],
    },
    footer: {
      style: 'dark',
      links: [
        {
          title: 'Documentation',
          items: [
            {
              label: 'Try samples',
              to: 'docs/try-samples',
            },
            {
              label: 'Installation',
              to: 'docs/installation',
            }
          ],
        },
        {
          title: 'Philosophy and Research',
          items: [
            {
              label: 'Fundamentals',
              to: 'docs/fundamentals'
            },
          ],
        },
        {
          title: 'Community',
          items: [
            {
              label: 'LinkedIn',
              href: 'https://www.linkedin.com/in/softtimur/'
            }
          ],
        },
        {
          title: 'Company',
          items: [
            {
              label: 'About Us',
              to: 'docs/about-us'
            },
            {
              label: '❤ We are hiring!',
              to: 'docs/hiring'
            }
          ],
        }
      ],
    },
  },
  presets: [
    [
      '@docusaurus/preset-classic',
      {
        googleAnalytics: {
          trackingID: 'UA-68622074-6',
        },
        docs: {
          sidebarPath: require.resolve('./sidebars.js'),
        },
        theme: {
          customCss: require.resolve('./src/css/custom.css'),
        },
        sitemap: {
          changefreq: 'weekly',
          priority: 0.5,
        },
      },
    ],
  ],
  scripts : [
    '/js/patch.js'
  ],
  i18n: {
    defaultLocale: 'en',
    locales: ['en', 'zh-CN'],  // Add 'zh-CN' here
    localeConfigs: {
      en: {
        label: 'English',
        htmlLang: 'en-GB',
      },
      'zh-CN': {   // Add this section for Simplified Chinese
        label: '简体中文',
        htmlLang: 'zh-Hans',
      },
    },
  }
};

nginx configuration:

gzip on;
gzip_proxied any;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_min_length 256;
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/rss+xml text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/jpeg image/png image/svg+xml image/x-icon;


server {
  listen  3002;
  absolute_redirect off;
  root  /app;

  location = / {
    rewrite ^(.*)$ https://$http_host/docs/introduction redirect;
  }

  location = /docs {
    rewrite ^(.*)$ https://$http_host/docs/introduction redirect;
  }

  location / {
    try_files $uri $uri/ =404;
  }
}


upstream funfun {
   server 178.62.87.72:443;
}


server {
    listen              443 ssl;
    ssl_certificate     /etc/letsencrypt/live/www.mywebsite.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/www.mywebsite.com/privkey.pem;
    server_name www.mywebsite.com;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    ssl_session_timeout 1d;
    ssl_stapling on;
    ssl_stapling_verify on;
    add_header Strict-Transport-Security max-age=15768000;
    add_header X-Frame-Options "";

    proxy_ssl_name "www.funfun.io";
    proxy_ssl_server_name on;
   
    location ~ /auth/(.*) {                                                                                            
          proxy_pass  https://funfun/mywebsite/auth/$1?$query_string;
          proxy_set_header Host www.mywebsite.com;
    }

    location / {
        proxy_set_header    Host                $host;
        proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
        proxy_set_header    X-Forwarded-Proto   $scheme;
        proxy_set_header    Accept-Encoding     "";
        proxy_set_header    Proxy               "";
        proxy_pass          http://docusaurus:3002/;

        # These three lines added as per https://github.com/socketio/socket.io/issues/1942 to remove socketio error
        proxy_http_version 1.1;
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection "upgrade";
    }
}

server {
    listen 443 ssl;
    server_name mywebsite.com;
    ssl_certificate /etc/letsencrypt/live/mywebsite.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mywebsite.com/privkey.pem;

    # Additional SSL settings (same as your existing www server block)
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    ssl_session_timeout 1d;
    ssl_stapling on;
    ssl_stapling_verify on;

    return 301 https://www.mywebsite.com$request_uri;
}

server {
    listen 80;
    server_name mywebsite.com;

    return 301 https://www.mywebsite.com$request_uri;
}

Vercel has a `cleanUrls` option

Hey!

FYI, Vercel has a cleanUrls option for vercel.json.


When set to true, all HTML files and Serverless Functions will have their extension removed. When visiting a path that ends with the extension, a 308 response will redirect the client to the extensionless path.

For example, a static file named about.html will be served when visiting the /about path. Visiting /about.html will redirect to /about.

Similarly, a Serverless Function named api/user.go will be served when visiting /api/user. Visiting /api/user.go will redirect to /api/user.

{
  "cleanUrls": true
}

https://vercel.com/docs/configuration#project/clean-urls

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.