Skip to content

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.9.0.zip archive content into it.

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

# Extract the quickstarter archive
unzip quickstarter-vuero-vv2.9.0.zip -d my-vuero-quickstarter-project

# Go to the newly created folder
cd my-vuero-quickstarter-project
Initialize a git repository

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

bash
# Create a new folder
git init

# Add all files to git
git add .

# Create your first commit
git commit -m "Initial commit"

WARNING

Remember to make your repository private if you fork or create a new git repository, as the template is a paid product.

Project overview

bash
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
   ├── 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
└── 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

Start development mode

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

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

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 useFetch composable, that is a wrapper around native fetch with custom interceptors for authentication, via JWT Bearer tokens, and baseURL set to VITE_API_BASE_URL environment variable. Read more on the Vuero and Vue 3 - Reuse stateful 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 authentication, data, etc ...

You can take a look at projects such as ⚗️ nitro, supabase or Strapi , which 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.

json
{
  "scripts": {
     "dev": "vite --open", 
     "dev": "run-p dev:vite dev:json-server", 
     "dev:vite": "vite --open", 
     "dev:json-server": "json-server --read-only --routes ./json-server/routes.json --port 8080 --delay 200 --watch ./json-server/db.json", 
     "preview": "serve dist -s -p 5000", 
     "preview": "run-p preview:vite preview:json-server", 
     "preview:vite": "serve dist -s -p 5000", 
     "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:

bash
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 automatically 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://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:

bash
 my-vuero-quickstarter-project/
 ├── src/
   ├── pages/
   ├── blog/           // blog nested routes // [!code ++]
   ├── index.vue   // the articles listing page accessible at `/blog/` // [!code ++]
   └── [slug].vue  // the article detail page accessible at `/blog/:slug` // [!code ++]
  1. Create the src/pages/blog/index.vue file.
vue
<script setup lang="ts">
// we import our useFetch helper
import { useFetch } from '/@src/composable/useFetch'

// 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
}

const $fetch = useFetch()

// 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 {
    articles.value = await $fetch<Article[]>('/articles') // we know that our api respond with an array of Article
  } 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">
// we import our useFetch helper
import { useFetch } from '/@src/composable/useFetch'

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

const article = ref<Article>()
const loading = ref(false)
const $fetch = useFetch()
const router = useRouter()
const route = useRoute()

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

  loading.value = true

  try {
    const data = await $fetch<Article[]>(`/articles`, {
      query: {
        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 articles will be shown until you add some to the Fake API.

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

json
 {
   "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 around 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.

bash
 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 />` // [!code ++]
  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

Retrieving components from the full template should be made carefully, 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

SidebarLayout

src/layouts/SidebarLayout.vue

NameThemeDemo
Regular Sidebardefaultdemo
Curved Sidebarcurveddemo
Colored Sidebarcolordemo
Curved Colored Sidebarcolor-curveddemo
Labels Sidebarlabelsdemo
Labels Hover Sidebarlabels-hoverdemo
Float Sidebarfloatdemo

src/layouts/NavbarLayout.vue

NameThemeDemo
Regular Navbardefaultdemo
Fading Navbarfadedemo
Colored Navbarcoloreddemo

src/layouts/NavbarDropdownLayout.vue

NameThemeDemo
Dropdown Navbardefaultdemo
Colored Dropdown Navbarcoloreddemo

src/layouts/NavbarSearchLayout.vue

NameThemeDemo
Clean Navbardefaultdemo
Clean Center Navbarcenterdemo
Clean Fade Navbarfadedemo

SideblockLayout

src/layouts/SideblockLayout.vue

NameThemeDemo
Regular Side blockdefaultdemo
Curved Side blockcurveddemo
Colored Side blockcolordemo
Curved Colored Side blockcolor-curveddemo

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, authentication or anything else.

Now you have to also copy all its related components manually (navigation, 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 /

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

All Rights Reserved