├── lib ├── templates │ └── plugin.js └── module.js ├── .gitignore ├── .editorconfig ├── .eslintrc.js ├── .circleci └── config.yml ├── package.json ├── LICENSE └── README.md /lib/templates/plugin.js: -------------------------------------------------------------------------------- 1 | export default async function ({ router, store }) { 2 | 3 | 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.iml 3 | .idea 4 | *.log* 5 | .nuxt 6 | .vscode 7 | .DS_STORE 8 | coverage 9 | dist -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_size = 2 6 | indent_style = space 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parserOptions: { 4 | sourceType: 'module' 5 | }, 6 | env: { 7 | browser: true, 8 | node: true, 9 | jest: true 10 | }, 11 | extends: 'airbnb', 12 | plugins: [ 13 | 'jest', 14 | 'vue' 15 | ], 16 | rules: { 17 | // Allow paren-less arrow functions 18 | 'arrow-parens': 0, 19 | // Allow async-await 20 | 'generator-star-spacing': 0, 21 | // Allow debugger during development 22 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 23 | // Do not allow console.logs etc... 24 | 'no-console': 2 25 | }, 26 | globals: { 27 | 'jest/globals': true, 28 | jasmine: true 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | working_directory: /usr/src/app 5 | docker: 6 | - image: banian/node 7 | steps: 8 | # Checkout repository 9 | - checkout 10 | 11 | # Restore cache 12 | - restore_cache: 13 | key: yarn-{{ checksum "yarn.lock" }} 14 | 15 | # Install dependencies 16 | - run: 17 | name: Install Dependencies 18 | command: NODE_ENV=dev yarn 19 | 20 | # Keep cache 21 | - save_cache: 22 | key: yarn-{{ checksum "yarn.lock" }} 23 | paths: 24 | - "node_modules" 25 | 26 | # Test 27 | - run: 28 | name: Tests 29 | command: yarn test && yarn codecov 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-trailingslash-module", 3 | "version": "1.2.0", 4 | "description": "NuxtJS module to remove trailing slashes from URLs", 5 | "license": "MIT", 6 | "contributors": [ 7 | { 8 | "name": "William DA SILVA " 9 | } 10 | ], 11 | "main": "lib/module.js", 12 | "repository": "https://github.com/WilliamDASILVA/nuxt-trailingslash-module", 13 | "publishConfig": { 14 | "access": "public" 15 | }, 16 | "scripts": { 17 | "lint": "eslint lib test" 18 | }, 19 | "eslintIgnore": [ 20 | "lib/templates/*.*" 21 | ], 22 | "files": [ 23 | "lib" 24 | ], 25 | "dependencies": {}, 26 | "devDependencies": { 27 | "eslint": "^4.9.0", 28 | "eslint-config-airbnb": "^16.1.0", 29 | "eslint-plugin-import": "^2.8.0", 30 | "eslint-plugin-jsx-a11y": "^6.0.2", 31 | "eslint-plugin-react": "^7.4.0", 32 | "nuxt-module-builder": "latest" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) William DA SILVA 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nuxt-trailingslash-module 2 | 3 | [![npm (scoped with tag)](https://img.shields.io/npm/v/nuxt-trailingslash-module/latest.svg?style=flat-square)](https://npmjs.com/package/nuxt-trailingslash-module) 4 | [![npm](https://img.shields.io/npm/dt/nuxt-trailingslash-module.svg?style=flat-square)](https://npmjs.com/package/nuxt-trailingslash-module) 5 | [![js-standard-style](https://img.shields.io/badge/code_style-standard-brightgreen.svg?style=flat-square)](http://standardjs.com) 6 | 7 | > A NuxtJS module that makes a 301 redirection to a non trailing slash URL 8 | 9 | ## Table of Contents ## 10 | 11 | * [Requirements](#requirements) 12 | * [Install](#install) 13 | * [Getting Started](#getting-started) 14 | 15 | ## Requirements 16 | 17 | * npm or yarn 18 | * NuxtJS 19 | * NodeJS 20 | 21 | ## Install 22 | 23 | ```bash 24 | $ npm install --save nuxt-trailingslash-module 25 | // or 26 | $ yarn add nuxt-trailingslash-module 27 | ``` 28 | 29 | ## Getting Started 30 | 31 | Add `nuxt-trailingslash-module` to `modules` section of `nuxt.config.js`. 32 | ```js 33 | { 34 | modules: [ 35 | // Simple usage 36 | 'nuxt-trailingslash-module', 37 | 38 | // With options 39 | ['nuxt-trailingslash-module', { 40 | /* module options */ 41 | methods: [ 42 | 'GET', 43 | 'HEAD', 44 | ], 45 | }], 46 | ] 47 | } 48 | ``` 49 | or even 50 | ```js 51 | { 52 | modules: [ 53 | 'nuxt-trailingslash-module', 54 | ], 55 | trailingslash: { 56 | methods: [ 57 | 'GET', 58 | 'HEAD', 59 | ], 60 | }, 61 | } 62 | ``` 63 | 64 | ## License 65 | 66 | [MIT License](./LICENSE) 67 | -------------------------------------------------------------------------------- /lib/module.js: -------------------------------------------------------------------------------- 1 | const defaults = { 2 | base: '', 3 | methods: [ 4 | 'GET', 'HEAD', 5 | ], 6 | }; 7 | 8 | module.exports = function module(moduleOptions) { 9 | const options = Object.assign({}, defaults, this.options.trailingslash, moduleOptions); 10 | 11 | if (!this.options.router) { 12 | this.options.router = {} 13 | } 14 | 15 | this.options.router.trailingSlash = true // https://nuxtjs.org/api/configuration-router/#trailingslash 16 | 17 | function checkMethods(method) { 18 | return options.methods.includes(method); 19 | } 20 | 21 | this.addServerMiddleware((req, res, next) => { 22 | if (checkMethods(req.method)) { 23 | const reQuery = /[?&]+/; 24 | const reDoubleSlashes = /(\/\/+)/; 25 | 26 | // Use originalUrl when defined ( for express compatibility); 27 | const url = (req.originalUrl || req.url).split(reQuery); 28 | const location = url[0]; 29 | let redirect = null; 30 | 31 | if (location[location.length - 1] === '/' && location !== '/') { 32 | // remove slashes 33 | redirect = location.slice(0, location.length - 1); 34 | } 35 | 36 | // complete the redirect url 37 | if (redirect) { 38 | if (url.length > 1) { 39 | redirect += `?${url.slice(1).join('&')}`; 40 | } 41 | 42 | redirect = options.base + redirect; // prepend the base path 43 | redirect = redirect.replace(reDoubleSlashes, '/'); 44 | 45 | if (redirect[0] !== '/') { 46 | redirect = `/${redirect}`; // guarantee absolute redirect 47 | } 48 | 49 | res.writeHead(301, { location: redirect }); 50 | res.end(); 51 | } else { 52 | next(); 53 | } 54 | } else { 55 | next(); 56 | } 57 | }); 58 | }; 59 | --------------------------------------------------------------------------------