├── .firebaserc ├── .eslintignore ├── .prettierignore ├── static ├── favicon.ico └── images │ └── logo.png ├── .env ├── firebase.json ├── .dependabot └── config.yml ├── .vscode ├── extensions.json └── settings.json ├── .prettierrc.json ├── plugins └── head.js ├── pages ├── 404.vue ├── index.vue └── about.vue ├── components └── navbar.vue ├── .gitignore ├── .eslintrc.json ├── layouts └── default.vue ├── package.json ├── LICENSE ├── .circleci └── config.yml ├── nuxt.config.js └── README.md /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "pre-vue" 4 | } 5 | } -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | node_modules 3 | /dist 4 | .nuxt 5 | .firebase -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | node_modules 3 | /dist 4 | .nuxt 5 | .firebase -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtlynch/pre-vue/HEAD/static/favicon.ico -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # Replace this with your Google Analytics ID. 2 | GOOGLE_ANALYTICS_ID='UA-000000000-1' -------------------------------------------------------------------------------- /static/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtlynch/pre-vue/HEAD/static/images/logo.png -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "dist", 4 | "ignore": ["firebase.json", "**/.*", "**/node_modules/**"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.dependabot/config.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | update_configs: 3 | - package_manager: "javascript" 4 | directory: "/" 5 | update_schedule: "monthly" 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "esbenp.prettier-vscode", 5 | "octref.vetur" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "esbenp.prettier-vscode", 3 | "editor.formatOnSave": true, 4 | "[markdown]": { 5 | "editor.formatOnSave": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": false, 3 | "printWidth": 80, 4 | "semi": true, 5 | "singleQuote": true, 6 | "tabWidth": 2, 7 | "trailingComma": "es5", 8 | "useTabs": false 9 | } 10 | -------------------------------------------------------------------------------- /plugins/head.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | Vue.mixin({ 4 | head() { 5 | return { 6 | title: this.title, 7 | meta: [{hid: 'og:title', property: 'og:title', content: this.title}], 8 | }; 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /pages/404.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 15 | -------------------------------------------------------------------------------- /components/navbar.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | .nuxt 5 | .firebase 6 | 7 | # local env files 8 | .env.local 9 | .env.*.local 10 | 11 | # Log files 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | firebase-debug.log* 16 | 17 | # Editor directories and files 18 | .idea 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw* -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "es6": true 5 | }, 6 | "parser": "vue-eslint-parser", 7 | "plugins": ["vue"], 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:vue/recommended", 11 | "plugin:prettier/recommended" 12 | ], 13 | "rules": { 14 | "vue/max-attributes-per-line": "off", 15 | "vue/html-closing-bracket-newline": "off" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /pages/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 25 | -------------------------------------------------------------------------------- /layouts/default.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 29 | 30 | 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pre-vue", 3 | "dependencies": { 4 | "nuxt": "^2.14.7" 5 | }, 6 | "scripts": { 7 | "serve": "nuxt --hostname 0.0.0.0 --port 3700", 8 | "build": "nuxt generate", 9 | "check-format": "prettier --check ./**/*.js ./**/*.vue ./**/*.json", 10 | "format": "prettier --write ./**/*.js ./**/*.vue ./**/*.json", 11 | "fix-lint": "eslint --fix './**/*.js' './**/*.vue'", 12 | "lint": "eslint --ext .js,.vue ." 13 | }, 14 | "devDependencies": { 15 | "@nuxt/typescript-build": "^2.0.3", 16 | "@nuxt/typescript-runtime": "^2.0.0", 17 | "@nuxtjs/dotenv": "^1.4.1", 18 | "@nuxtjs/google-analytics": "^2.4.0", 19 | "@nuxtjs/robots": "^2.4.2", 20 | "@nuxtjs/sitemap": "^2.4.0", 21 | "@typescript-eslint/eslint-plugin": "^4.8.2", 22 | "@typescript-eslint/parser": "^4.8.2", 23 | "babel-eslint": "10.1.0", 24 | "eslint": "7.14.0", 25 | "eslint-config-prettier": "6.15.0", 26 | "eslint-loader": "4.0.2", 27 | "eslint-plugin-prettier": "3.1.4", 28 | "eslint-plugin-vue": "7.1.0", 29 | "prettier": "2.2.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Michael Lynch 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. -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/node:14.15.1-stretch 6 | steps: 7 | - checkout 8 | - run: 9 | name: Download npm packages 10 | command: npm install 11 | - run: 12 | name: Check JavaScript formatting 13 | command: npm run check-format 14 | - run: 15 | name: Run linter 16 | command: npm run lint 17 | - run: 18 | name: Build static pages 19 | command: npm run build 20 | - persist_to_workspace: 21 | root: ./ 22 | paths: 23 | - ./dist 24 | - firebase.json 25 | - .firebaserc 26 | deploy: 27 | docker: 28 | - image: mtlynch/firebase-tools:8.1.1-node-14.0.0 29 | steps: 30 | - attach_workspace: 31 | at: ./ 32 | - run: 33 | name: Deploy to firebase 34 | command: firebase deploy --token="$FIREBASE_DEPLOY_TOKEN" 35 | workflows: 36 | version: 2 37 | test-deploy: 38 | jobs: 39 | - build 40 | - deploy: 41 | requires: 42 | - build 43 | filters: 44 | branches: 45 | only: master 46 | -------------------------------------------------------------------------------- /pages/about.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 45 | -------------------------------------------------------------------------------- /nuxt.config.js: -------------------------------------------------------------------------------- 1 | // Load dotenv module so that .env environment variables are available within 2 | // this file. 3 | require('dotenv').config(); 4 | 5 | export default { 6 | target: 'static', 7 | head: { 8 | htmlAttrs: { 9 | lang: 'en', 10 | }, 11 | titleTemplate: '%s | Pre-Vue', 12 | meta: [ 13 | {charset: 'utf-8'}, 14 | {name: 'viewport', content: 'width=device-width, initial-scale=1.0'}, 15 | {property: 'og:type', content: 'website'}, 16 | { 17 | property: 'og:description', 18 | content: 'Pre-Vue is a simple example of a pre-rendered Vue website', 19 | }, 20 | { 21 | property: 'og:image', 22 | content: 'https://pre-vue.web.app/images/logo.png', 23 | }, 24 | ], 25 | link: [ 26 | { 27 | rel: 'icon', 28 | type: 'image/x-icon', 29 | href: '/favicon.ico', 30 | }, 31 | ], 32 | }, 33 | buildModules: [ 34 | '@nuxtjs/dotenv', 35 | [ 36 | '@nuxtjs/google-analytics', 37 | { 38 | id: process.env.GOOGLE_ANALYTICS_ID, 39 | }, 40 | ], 41 | ], 42 | modules: ['@nuxtjs/sitemap', '@nuxtjs/robots'], 43 | plugins: ['~/plugins/head'], 44 | sitemap: { 45 | hostname: 'https://pre-vue.web.app/', 46 | gzip: true, 47 | exclude: ['/404'], 48 | }, 49 | robots: { 50 | sitemap: 'https://pre-vue.web.app/sitemap.xml', 51 | }, 52 | }; 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pre-vue 2 | 3 | [![CircleCI](https://circleci.com/gh/mtlynch/pre-vue.svg?style=svg)](https://circleci.com/gh/mtlynch/pre-vue) [![License](http://img.shields.io/:license-mit-blue.svg?style=flat-square)](LICENSE) 4 | 5 | ## Overview 6 | 7 | This is an example of a [Vue](https://vuejs.org) + [Nuxt](https://nuxtjs.org) configuration that generates a pre-rendered, static site. 8 | 9 | Instead of generating a normal single-page app, it generates a static page for every route on your site, making it easier for you to configure search engine optimization and social sharing. 10 | 11 | ## Features 12 | 13 | The following features are built-in to this project template: 14 | 15 | - Generates a sitemap (for SEO) 16 | - Generates a robots.txt (for SEO) 17 | - Supports unique `` tags for each page (for SEO) 18 | - Adds unique opengraph tags to each page (for social sharing) 19 | - Adds Google Analytics support 20 | - Adds a favicon 21 | - Handles 404s 22 | 23 | ## Benefits of pre-rendering 24 | 25 | ### Search engine optimization 26 | 27 | The primary benefit of this configuration is that it makes it easier to improve your site's search engine optimization. With a traditional Vue SPA, you can't set per-page attributes like the `<title>` tag or the `<link rel="canonical">` tag, which search engines use to understand the structure of your website. 28 | 29 | With pre-rendering, you can configure all the per-page elements so that they're present in your site's HTML when search engines index it. 30 | 31 | ### Social sharing 32 | 33 | Another major drawback of SPAs is that they have poor support for social sharing. Sites like Twitter, Facebook, and Pinterest require your pages to have HTML `<meta>` tags present before any JavaScript loads. With a normal Vue SPA, this prevents you from having distinct social sharing cards for different pages. 34 | 35 | With pre-rendering, each page on your site can have its own set of `<meta>` tags, so you can customize the social sharing cards for all of your site's pages. 36 | 37 | ## Requirements 38 | 39 | - [Node.js](https://nodejs.org/en/download/) 14.x or higher 40 | 41 | ## Quick Start 42 | 43 | ```bash 44 | npm install 45 | npm run serve 46 | ``` 47 | 48 | Visit [http://localhost:3700](http://localhost:3700) to see the site running. 49 | 50 | ## Deploying to production 51 | 52 | To pre-render your website, run 53 | 54 | ```bash 55 | npm install 56 | npm run build 57 | ``` 58 | 59 | This will generate all the files for a static, pre-rendered version of your website under the `dist/` folder. You can upload these files to any service that suports static website hosting, such as: 60 | 61 | - [Amazon S3](https://docs.aws.amazon.com/AmazonS3/latest/dev/WebsiteHosting.html) 62 | - [Google Cloud Storage](https://cloud.google.com/storage/docs/hosting-static-website) 63 | - [Netlify](https://www.netlify.com/blog/2016/10/27/a-step-by-step-guide-deploying-a-static-site-or-single-page-app/) 64 | 65 | The [circleci-firebase](https://github.com/mtlynch/pre-vue/tree/circleci-firebase) branch of this repo includes an example configuration that automatically builds your site using Circle CI and deploys it to Firebase. 66 | 67 | ## Customization 68 | 69 | To customize this template for your project: 70 | 71 | 1. Find/replace "https://pre-vue.web.app" with your app's base URL. 72 | 1. Find/replace `pre-vue` with your repo name. 73 | 1. Update `.env` with your Google Analytics ID. 74 | 75 | ## Live Demo 76 | 77 | The live version of this project is at: 78 | 79 | - [https://pre-vue.web.app](https://pre-vue.web.app) 80 | --------------------------------------------------------------------------------