Skip to content

Boilerplate for a single page application (SPA) based on Nuxt.js

Notifications You must be signed in to change notification settings

dennisfrijlink/nuxt-spa-boilerplate

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

79 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

logo of Nuxt Single Page Application Repository

Single Page Application Boilerplate - Nuxt.js

A boilerplate for single page applications based on the Vue.js Framework, Nuxt.js

🧐 What's inside

✨ Quick start

  1. Clone this repository.

    git clone https://github.com/dennisfrijlink/nuxt-spa-boilerplate.git
  2. Start developing.

    Navigate into your new site’s directory and start it up.

    cd nuxt-spa-boilerplate/
    npm install
    npm run dev
  3. Running!

    Your site is now running at http://localhost:3000!

  4. Generate for deploy

    Generate a static project that will be located in the dist folder:

    $ npm run generate

βš™οΈ What is a Single Page Application

A single-page application (SPA) is a web application or website that interacts with the user by dynamically rewriting the current web page with new data from the web server, instead of the default method of the browser loading entire new pages.

In a SPA, all necessary HTML, JavaScript, and CSS code is either retrieved by the browser with a single page load, or the appropriate resources are dynamically loaded and added to the page as necessary, usually in response to user actions. The page does not reload at any point in the process, nor does it transfer control to another page, although the location hash or the HTML5 History API can be used to provide the perception and navigability of separate logical pages in the application.

Lifecycle of Single Page Application

πŸ—ΊοΈ Nuxt Router

Nuxt.js automatically generates the vue-router configuration for you, based on your provided Vue files inside the pages directory. That means you never have to write a router config again! Nuxt.js also gives you automatic code-splitting for all your routes.

To navigate between pages of your app, you should use the NuxtLink component. For all links to pages within your site, use <NuxtLink>. If you have links to other websites you should use the <a> tag. See below for an example:

<template>
  <main>
    <h1>Home page</h1>
    <NuxtLink to="/about">
      About (internal link that belongs to the Nuxt App)
    </NuxtLink>
    <a href="https://nuxtjs.org">External Link to another page</a>
  </main>
</template>

There a three router modes "hash" | "history" | "abstract":

  • hash: uses the URL hash for routing. Works in all Vue-supported browsers, including those that do not support HTML5 History API.

  • history: requires HTML5 History API and server config. See HTML5 History Mode.

  • abstract: works in all JavaScript environments, e.g. server-side with Node.js. The router will automatically be forced into this mode if no browser API is present.

For example:

// nuxt.config.js

export default {
  router: {
    mode: 'hash'
  }
}

πŸ” Data Fetching

Nuxt.js supports traditional Vue patterns for loading data in your client-side app, such as fetching data in a component's mounted() hook. Nuxt has two hooks for asynchronous data loading:

  • The fetch hook (Nuxt 2.12+). This hook can be placed on any component, and provides shortcuts for rendering loading states (during client-side rendering) and errors.
  • The asyncData hook. This hook can only be placed on page components. Unlike fetch, this hook does not display a loading placeholder during client-side rendering: instead, this hook blocks route navigation until it is resolved, displaying a page error if it fails.

For example:

<template>
  <p v-if="$fetchState.pending">Fetching mountains...</p>
  <p v-else-if="$fetchState.error">An error occurred :(</p>
  <div v-else>
    <h1>Nuxt Mountains</h1>
    <ul>
      <li v-for="mountain of mountains">{{ mountain.title }}</li>
    </ul>
    <button @click="$fetch">Refresh</button>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        mountains: []
      }
    },
    async fetch() {
      this.mountains = await this.$axios.$get(
        'https://api.nuxtjs.dev/mountains'
      )
    }
  }
</script>

When using the nuxt/axios library you can define the baseURL in the nuxt.config.js:

// nuxt.config.js

export default {
  modules: [
    '@nuxtjs/axios'
  ],

  axios: {
    browserBaseURL: 'https://api.nuxtjs.dev/'
  },
}

Now you can use the url of the API in all your pages and components without repeating the base URL:

<!-- pages/index.vue -->

<template>
  <div>
    <h1>{{ mountain.slug }}</h1>
    <img :src="mountain.image" :alt="mountain.slug">
  </div>
</template>

<script>
  export default {
    name: 'index',
    data() {
      return {
        mountain: null
      }
    }, 
    async fetch() {
      const mountain = await this.$axios.$get('/mountains/aconcagua') // https://api.nuxtjs.dev/mountains/aconcagua
      this.mountain = mountain;
    }
  }
</script>

You can use keep-alive directive in <nuxt/> and <nuxt-child/> component to save fetch calls on pages you already visited:

<!-- layouts/default.vue -->

<template>
  <nuxt keep-alive />
</template>

πŸ“± Breakpoints mobile first

The scss folder located in ./assets/scss/ contains two files to make it easier for web developers to prototype, build, scale, and maintain CSS for responsive websites:

SCSS Files

assets
β”‚
└─── scss
    β”‚
    └─── main.scss
    β”‚
    └─── mixins.scss
    β”‚
    └─── breakpoints.scss

Building responsive websites is a must-have skill for front-end developers today, so we've made the breakpoints mobile first. They are all defined with a @media (min-width: so that the main css you write is based on mobile screens.

// breakpoints.scss


/* Small (sm) */
$screen-sm-min: 640px;

/* Medium (md) */
$screen-md-min: 768px;

/* Large (lg) */
$screen-lg-min: 1024px;

/* Extra Large (xl) */
$screen-xl-min: 1280px;

/* 2 Extra Large (xxl) */
$screen-xxl-min: 1536px;

Now it’s time to create the most important element – mixins:

// mixins.scss


// Small devices
@mixin  sm {
  @media (min-width: #{$screen-sm-min}) {
    @content;
  }	
}

// Medium devices
@mixin  md {
  @media (min-width: #{$screen-md-min}) {
    @content;
  }	
}

// Large devices
@mixin  lg {
  @media (min-width: #{$screen-lg-min}) {
    @content;
  }	
}

// Extra large devices
@mixin  xl {
  @media (min-width: #{$screen-xl-min}) {
    @content;
  }	
}

// XXL devices
@mixin  xxl{
  @media (min-width: #{$screen-xxl-min}) {
    @content;
  }	
}

I always build my websites in a mobile-first approach, so I don’t need to define the smallest screen size (xs – extra small) and I write my SCSS code first for the smallest devices and next for the largest. Sometimes we also need to define some styles beyond the rigidly defined breakpoints. Let’s add one more mixin – I called it β€œrwd”:

// mixins.scss


// Custom devices
@mixin rwd($screen) {
  @media (min-width: $screen+'px') {
    @content;
  }
}

As a parameter $screen we can put any screen size.

For Example

.container {
    padding: 0 15px;
    
	/* 576px window width and more */
    @include sm {
        padding: 0 20px;
    }
    
	/* 992px window width and more */
    @include lg {
        margin-left: auto;
        margin-right: auto;
        max-width: 1100px;
    }
    
	/* 1400px window width and more */
    @include rwd(1400) {
        margin-bottom: 20px;
        margin-top: 20px;
    }
}

πŸ’¬ Nuxt-i18n

Nuxt-I18n is the Vue.js internationalization plugin optimized for using in Nuxt.js. The configuration of the languages is defined in the nuxt.config.js file:

// nuxt.config.js

{
  modules: [
    'nuxt-i18n'
  ],
  i18n: {
    locales: [
      {
        code: 'en',
        iso: 'en-US',
        name: 'English',
      },
      {
        code: 'nl',
        iso: 'nl-NL',
        name: 'Dutch',
      }
    ],
    defaultLocale: 'en',
    vueI18n: {
      fallbackLocale: 'en',
      messages: {
        en: require('./locales/en.json'),
        nl: require('./locales/nl.json')
      }
    }
  }
}

The locales are located in the ~/locales folder, for example:

locales
β”‚
└─── en.json
β”‚
└─── nl.json
// nl.json

{
  "welcome": "Een boilerplate voor single page application gebasserd op Nuxt.js"
}
// en.json

{
  "welcome": "A boilerplate for single page applications based on Nuxt.js"
}

When rendering internal links in your app using <nuxt-link>, you need to get proper URLs for the current locale. To do this, nuxt-i18n registers a global mixin that provides some helper functions:

  • localePath – Returns the localized URL for a given page. The first parameter can be either the path or name of the route or an object for more complex routes. A locale code can be passed as the second parameter to generate a link for a specific language:
<nuxt-link :to="localePath('/')">{{ $t('home') }}</nuxt-link>
<nuxt-link :to="localePath('index', 'en')">Homepage in English</nuxt-link>
<nuxt-link :to="localePath('/app/profile')">Route by path to: {{ $t('Profile') }}</nuxt-link>
<nuxt-link :to="localePath('app-profile')">Route by name to: {{ $t('Profile') }}</nuxt-link>
<nuxt-link
  :to="localePath({ name: 'category-slug', params: { slug: category.slug } })">
  {{ category.title }}
</nuxt-link>
<!-- It's also allowed to omit 'name' and 'path'. -->
<nuxt-link :to="localePath({ params: { slug: 'ball' } })">{{ category.title }}</nuxt-link>
  • switchLocalePath – Returns a link to the current page in another language:
<nuxt-link :to="switchLocalePath('en')">English</nuxt-link>
<nuxt-link :to="switchLocalePath('fr')">Français</nuxt-link>

Template:

<p>{{ $t('welcome') }}</p>

Output:

<p>A boilerplate for single page applications based on Nuxt.js</p>

πŸ—› Fonts

There are two standard declarations for the font types:

/* standard declrations */
h1,h2,h3,h4,h5,h6 {
  font-family: 'DM sans';
}
body {
  font-family: 'Arial';
}

These font-families are defined in the same file font.css:

@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('~assets/fonts/DM-Sans/DMSans-Regular.ttf') format('truetype');
}

If you wanna use a different font family in a specified component you can use another specified font in from the font.css inside the <style lang="scss" scoped></style> of the .vue component

πŸŒ— Dark & Light theme

πŸŒ‘ Dark and πŸŒ• Light mode with auto detection made easy with the plugin nuxt/color-mode.

Note

If you don't need a dark/sepia/light mode you can always disable it by commenting this line in nuxt.config.js:

modules: [
  '@nuxtjs/color-mode'
],

Theme file

The main theme file, located in css/theme.css contains all css rules specific for nuxtjs/color-mode. In the theme.css you declare all color variables per theme. So for example:

:root {
  --bg-color: #ffffff;
}

.dark-mode {
  --bg-color: #21252b;
}

body {
  background-color: var(--bg-color);
  transition: background-color .3s;
}

Now you can add the option to select a theme:

<template>
    <select v-model="$colorMode.preference">
      <option value="system">System</option>
      <option value="dark">Dark</option>
    </select>
</template>

Important

We use PurgeCSS for removing unused CSS selectors to optimize the performance of the web application. But PurgeCSS will delete all css rules of the theme(s) that are not selected. To resolve this issue you'll have to add the theme classes in to the white list of PurgeCSS. So for example:

//nuxt.config.js

purgeCSS: {
  whiteList: () =>['dark-mode']
},

Now PurgeCSS will ignore those classes by removing the unused CSS selectors

About

Boilerplate for a single page application (SPA) based on Nuxt.js

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages