Skip to content
On this page

Setup your project

Starting with the quickstarter

Start by creating a new folder (e.g. my-vuero-quickstarter-project) and extract the quickstarter-vuero-v2.6.1.zip archive content into it.

bash
# Create a new folder
mkdir my-vuero-quickstarter-project

# Extract the quickstarter archive (replace v2.4.0 with the actual version number)
unzip quickstarter-vuero-v2.4.0.zip -d my-vuero-quickstarter-project

# Go to the newly created folder
cd my-vuero-quickstarter-project

TIP

We recommend to initialize a new git repository for your project and create your first commit at this point.

Project overview

sh
my-vuero-quickstarter-project/
├── public/                 # static files (robots.txt, favicon.ico, etc.)
├── src/
   ├── assets/             # static files, will be processed by vite (e.g. optipng, svgo, etc.)
   ├── components/         # global components
   ├── composable/         # reusable composition functions 
   ├── directives/         # global vuejs directives
   ├── layouts/            # layout components
   ├── locales/            # global i18n locales
   ├── pages/              # pages components, each file will be accessible as a route
   ├── scss/               # scss files
   ├── stores/             # pinia stores
   ├── utils/              # utility functions
   ├── plugins/            # router guards, vue plugins installations, etc.
   ├── app.ts              # vuero initialization (head, i18n, router, pinia, etc.)
   ├── entry-client.ts     # client entry point 
   ├── entry-server.ts     # SSR entry point (experimental)
   ├── router.ts           # base vue-router configuration
   ├── styles.ts           # stylesheet configuration (scss, vendor, etc.)
   └── VueroApp.vue        # vuero root component
├── .env                    # environment variables available to the project
├── index.html              # main entry point (loads src/entry-client.ts)
├── package.json            # project dependencies
├── tsconfig.json           # typescript configuration
└── server.ts               # Node.js SSR server (experimental)
└── vite.config.ts          # vite plugins and configuration

This is an overview of the most important files and folders in your project. Other files are for linters, testing, docker, etc.

Dependencies installation

Install the project dependencies by running one of the following commands:

bash
pnpm install

Setup your IDE

VSCode and Volar

The recommended IDE setup is VSCode with the Volar extension. Volar provides syntax highlighting and advanced IntelliSense for template expressions, component props and even slots validation. We strongly recommend this setup if you want to get the best possible experience with Vue SFCs.

TIP

Once you have enabled the Volar extension:

  1. Open the project folder in VSCode and follow this guide to enable Take Over Mode: https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669

  2. Run the VSCode command Volar: Select TypeScript Version... then choose Use Workspace Version

These steps should be made on each new project Vue 3 you create.

Vue.js devtools

If you are on a Chromium based browser, we recommend that you to install the Vue.js devtools extension from any webstore: https://devtools.vuejs.org/guide/installation.html

Start development mode

To start the development server, run one of the following commands:

WARNING

Using quickstarter v2.6.0 requires you to update the vite.config.ts before starting the development server (we are working on a better solution for next release):

ts
// ...

// comment this import

// local vite plugin
// import { VitePluginVueroDoc } from './vite-plugin-vuero-doc' 

// ...

export default defineConfig({
  // ...
  plugins: [
    // ...

    // comment this block

    // VitePluginVueroDoc({
    //   pathPrefix: 'documentation',
    //   wrapperComponent: 'DocumentationItem',
    //   shiki: {
    //     theme: {
    //       light: 'min-light',
    //       dark: 'github-dark',
    //     },
    //   },
    //   sourceMeta: {
    //     enabled: true,
    //     editProtocol: 'vscode://vscode-remote/wsl+Ubuntu', // or 'vscode://file'
    //   },
    // }),
  ],
})
bash
pnpm dev

This will run both the dev:vite and dev:json-server scripts from the package.json file.

You will notice that two servers are started: one for the frontend (vite) and one for the backend (json-server).

  • The dev:vite script will start the frontend (vite) server. Vite is the build tool that we use to compile the frontend code. It replace webpack and vue-cli, used in vue 2 ecosystem.
    Read more about it on vitejs.dev

  • The dev:json-server script will start the frontend (vite) server. Json-server is a fake REST-API server that we use to simulate the backend. The configuration and the database are in the /json-server directory. You can find how we use it in the full template source in files /src/pages/messaging-v1.vue and /src/composable/useChatApi.ts.
    Read more about it on github.com/typicode/json-server

TIP

  • Access the Vuero frontend in your browser at http://localhost:3000/
  • Access the Json-server backend in your browser at http://localhost:8080/

WARNING

If you have any trouble while installing, check the Common issues or contact us on our Support portal

Start with Server Side Rendering

Experimental

Since Vuero v2.4.0, you can run your app with SSR.
This is an experimental feature, issues can occur, please report them on our support portal or on github if you find any.

Enabling SSR in an application require to know about how to Write SSR-friendly Code mostly due to Access to Platform-Specific APIs and Cross-Request State Pollution

Once you're ok with those articles, you can start using SSR with pnpm ssr:dev in replacement of pnpm dev. You can then use pnpm ssr:build and pnpm ssr:serve.

You can customize how server requests are handled by your server with those two files:

sh
my-vuero-quickstarter-project/
├── src/
   ├── entry-server.ts     # SSR app entry point (experimental)
└── server.ts               # Node.js SSR server (experimental)

Fake API with JSON-Server

You may have heard of jsonplaceholder.typicode.com service that provides a fake REST-API for testing purposes. Vuero also uses the json-server package to serve a custom REST-API. This enables us to provide real-world example implementations. By default we start the json-server in read-only mode, but you can change this by editing the package.json file.

When you run the dev:json-server script, you will see a message in the console informing you about available endpoints:

bash
  \{^_^}/ hi!

  Loading ./json-server/db.json
  Loading ./json-server/routes.json
  Done

  Resources
  http://localhost:8080/conversations
  http://localhost:8080/messages
  http://localhost:8080/companies
  http://localhost:8080/users

  Other routes
  /api/* -> /$1
  /users/me -> /users/3
  /conversations/:cid/messages -> /messages?conversationId=:cid
  /conversations/:cid/messages/:mid -> /messages?conversationId=:cid&messageId=:mid

  Home
  http://localhost:8080

Consuming the api

As you can see, the json-server is running on port 8080. You can access it in your browser at http://localhost:8080/. This also means that you can use any http client to consume the api. For example, you can use axios or directly use the Fetch API.

Luckily we provide a useApi composable, that is a wrapper around axios with custom interceptors for authentification, via JWT Bearer tokens, and baseURL set to VITE_API_BASE_URL environment variable. Read more on the Vuero and Vue 3 - Reuse logic with composable section.

A good example of how to load async data from an api is the messaging-v1 page. (You can find the sources in the full template in ./src/pages/messaging-v1.vue). In this section you will find how yo split the data into two parts: the application state (useChat pinia store) and the related api (useChatApi composable).

You can extend the api with your own data to speed up development, but avoid relying on it in production. Read more about it on the github page: https://github.com/typicode/json-server

Replace with real api

To bring your application alive you will need to create a backend for user authentification, data, etc ...

You can take a look at projects such as ⚗️ nitro, supabase or strapi , wich are open-source backend, that can be nicely used with Vuero (storyblok can be a good choice too) !

When you are ready to replace the fake api with a real one, you can follow the steps below:

  1. Edit the VITE_API_BASE_URL in the .env file to point to your api.
    Note that you can override this environment variable in the .env.local file.

  2. Remove json-server from the package.json file.

diff
{
  "scripts": {
+    "dev": "vite",
-    "dev": "run-p dev:vite dev:json-server",
-    "dev:vite": "vite",
-    "dev:json-server": "json-server --read-only --routes ./json-server/routes.json --port 8080 --delay 200 --watch ./json-server/db.json",
+    "preview": "vite preview --host 0.0.0.0",
-    "preview": "run-p preview:vite preview:json-server",
-    "preview:vite": "vite preview --host 0.0.0.0",
-    "preview:json-server": "json-server --read-only --routes ./json-server/routes.json --host 0.0.0.0 --port 8080 ./json-server/db.json",
  },
  "devDependencies": {
-    "json-server": "0.17.0",
  },
}

TIP

Is it totally fine to remove it and use a GraphQL API instead, in this case you can take a look at https://v4.apollo.vuejs.org/

Extending the quickstarter

Quickstarter pages anatomy

The quickstarter come with a few pages that you can use to start your project.

  • A landing page that you can use to introduce your project, everyone can access it.
  • An authentication section with login/signup forms.
  • A private page that need to be logged in to access.
  • And a 404 page when no matching component is found for the requested route.

Here is the overview of the pages:

sh
my-vuero-quickstarter-project/
├── src/
   ├── pages/
      ├── app/            # app nested routes
         └── index.vue   # the app page accessible at `/app/`
      ├── auth/           # auth nested routes
         ├── index.vue   # the auth page accessible at `/auth/`
         ├── login.vue   # the auth-login page accessible at `/auth/login`
         └── signup.vue  # the auth-signup page accessible at `/auth/signup`
      ├── [...all].vue    # the catch all page (404)
      ├── index.vue       # the index page accessible at `/`
      ├── app.vue         # optional app nested routes wrapper, should contain a `<RouterView />`
      └── auth.vue        # optional auth nested routes wrapper, should contain a `<RouterView />`

The route are automaticaly generated from the pages/ folder, this is done with the unplugin-vue-router plugin. You can read more about this plugin on it github page here: https://github.com/posva/unplugin-vue-router

As you can see, some pages are not directly accessible, but are wrapper for nested routes. This will allow us to setup layout for an entire section of our app. You can read more about how they work on the official vue-router documentation here: https://next.router.vuejs.org/guide/essentials/nested-routes.html

Creating new pages

Let's imagine we want to create a new blog section in our app. We want our blog to be accessible at /blog/ and we want to have a blog page that will be accessible at /blog/my-pretty-blog.

We have to create at least two files:

diff
 my-vuero-quickstarter-project/
 ├── src/
 │   ├── pages/
+│   │   ├── blog/           // blog nested routes
+│   │   │   ├── index.vue   // the articles listing page accessible at `/blog/`
+│   │   │   └── [slug].vue  // the article detail page accessible at `/blog/:slug`
  1. Create the src/pages/blog/index.vue file.
vue
<script setup lang="ts">
import { useHead } from '@vueuse/head'

// we import our useApi helper
import { useApi } from '/@src/composable/useApi'

// We may want to retrieve the posts from an API
// as we are using typescript, it is a good practice to always define our types
interface Article {
  id: string
  title: string
  slug: string
}

// articles and fetchArticles variables can be provided by a composable function
const articles = ref<Article[]>([]) // we know that the articles will be an array of Article

async function fetchArticles() {
  try {
    const { data } = await api.get<Article[]>('/articles') // we know that our api respond with an array of Article
    articles.value = data
  } catch (error) {
    // here we can handle the error
    console.error(error)
  }
}

// We trigger the fetchArticles function when the component is mounted
watchEffect(fetchArticles)

// don't forget to setup our page meta
useHead({
  title: 'My blog',
})
</script>

<template>
  <LandingLayout theme="light">
    <div class="blog-list-wrapper">
      <!-- This is a simple page example -->
      <h1>My blog posts:</h1>
      <ul>
        <li v-for="article in articles" :key="article.id">
          <!-- Here we are linking to the article detail page with a dynamic "slug" parameter -->
          <RouterLink
            :to="{
              name: '/blog/[slug]',
              params: {
                slug: article.slug,
              },
            }"
          >
            {{ article.title }}
          </RouterLink>
        </li>
      </ul>
    </div>
  </LandingLayout>
</template>

<style lang="scss" scoped>
.blog-list-wrapper {
  // Here we can add custom styles for the blog page
  // They will be only applied to this component
}
</style>

  1. Create the src/pages/blog/[slug].vue file.
vue
<script setup lang="ts">
import { useHead } from '@vueuse/head'

// we import our useApi helper
import { useApi } from '/@src/composable/useApi'

interface Article {
  id: string
  title: string
  slug: string
  content: string
  comments: string[]
}

const article = ref<Article>()
const loading = ref(false)
const api = useApi()
const router = useRouter()
const route = useRoute()

// Trigger the function when the slug changes
watchEffect(async () => {
  const currentSlug = (route.params?.slug as string) ?? ''

  loading.value = true

  try {
    const { data } = await api.get<Article[]>(`/articles?slug=${slug}`)

    if (!data?.length) {
      throw new Error('Artcile not found')
    }

    article.value = data[0]
  } catch (error) {
    // If the article does not exist, we replace the route to the 404 page
    // we also pass the original url to the 404 page as a query parameter
    // http://localhost:3000/article-not-found?original=/blog/a-fake-slug
    router.replace({
      name: '/[...all]', // this will match the ./src/pages/[...all].vue route
      params: {
        all: 'article-not-found',
      },
      query: {
        original: router.currentRoute.value.fullPath,
      },
    })
  } finally {
    loading.value = false
  }
})

// Setup our page meta with our article data
useHead({
  title: computed(() => article.value?.title ?? 'Loading article...'),
})
</script>

<template>
  <LandingLayout theme="light">
    <div v-if="loading">Loading article...</div>
    <div v-else class="blog-detail-wrapper">
      <!--
        Page content goes here

        You can see more complete pages content samples from 
        files in /src/components/pages directory
      -->

      <h1>{{ article?.title }}</h1>
      <div>{{ article?.content }}</div>
    </div>
  </LandingLayout>
</template>

<style lang="scss" scoped>
.blog-detail-wrapper {
  // Here we can add custom styles for the blog page
  // They will be only applied to this component
}
</style>

TIP

To start customizing your pages, you can check content in src/components/pages/ folder or src/pages/wizard-v1.vue, src/pages/inbox.vue, src/pages/auth/login-1.vue, src/pages/marketing-1.vue files to see how you can arrange elements to create awesome apps!

Adding fake data

At this point you can browse the blog section at /blog/ and you will see the blog page with the articles listed. In fact no artciles will be shown until you add some to the Fake API.

To do so, simply add this in the ./json-server/db.json file:

diff
 {
+  "articles": [
+    {
+      "id": 1,
+      "slug": "my-first-post",
+      "title": "My First Post",
+      "content": "This is my first post!"
+    },
+    {
+      "id": 2,
+      "slug": "my-pretty-blog",
+      "title": "My pretty blog",
+      "content": "Vue.js is awesome!"
+    }
+  ],
   "conversations": [

You can try by yourself by browsing to /blog/my-first-post, you will see the article detail page whereas going to /blog/random-slug will show the 404 page.

Using nested route to reuse layouts

You may have noticed that both of our pages contains the same layout component <LandingLayout theme="light">. This can be ok if both page has different layout, but in this case, the layout will be unmounted and remounted on each page change (when going from /blog to /blog/my-first-post for example, or from /blog/my-first-post to /blog/my-pretty-blog). This can cause flickering and performance issues.

To avoid this we can use the power of nested routes by adding a wrapper arround the /blog/* pages. In order to do so, we just have to create a file that is named after the directory it wrap, in this case src/pages/blog.vue.

diff
 my-vuero-quickstarter-project/
 ├── src/
 │   ├── pages/
 │   │   ├── blog/           // blog nested routes
 │   │   │   ├── index.vue   // the articles listing page accessible at `/blog/`
 │   │   │   └── [slug].vue  // the article detail page accessible at `/blog/:slug`
+│   │   └── blog.vue        // blog nested routes wrapper, should contain a `<RouterView />`
  1. Remove <LandingLayout theme="light"> from src/pages/blog/index.vue and src/pages/blog/[slug].vue

  2. Create the src/pages/blog.vue file with the following content:

vue
<script setup lang="ts">
// we do not need to so anything special here, but we can!
</script>

<template>
  <LandingLayout theme="light">
    <RouterView />
  </LandingLayout>
</template>

WARNING

This component will need to have a <RouterView />, which will render the correct nested page based on the current route.

TIP

You can also declare page transition here (see src/scss/abstracts/_transitions.scss file for more info about available transitions):

vue
<template>
  <RouterView v-slot="{ Component }">
    <Transition name="fade-fast" mode="out-in">
      <component :is="Component" />
    </Transition>
  </RouterView>
</template>

And voila! You have created your first pages with dynamic routes and asynchronous data loading.

Using components from the full template

WARNING

Retreiving components from the full template should be made carrefully, as it can contains <RouterLink> to page that don't exists in your project.

Using a layout from the full template

Layouts are just components with a default slots. They are mostly used to wrap the nested routes.

You have to choose a layout from one available

Copy the layout into your src/layouts project directory. Notice that there are also AuthLayout, MinimalLayout and MinimalLayoutLayout available. They can be used for landing pages, auth or anything else.

Now you have to also copy all its related components manualy (navigations, panels, etc...).
You can copy them anywhere in your src/components directory.

TIP

You may decide to omit some components like <LanguagesPanel /> if you do not need them.

WARNING

You will have to replace all <RouterLink> parameters. A good way to do so is to replace all names to index

All Rights Reserved