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.
# 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
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:
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:
Open the project folder in VSCode and follow this guide to enable Take Over Mode: https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669
Run the VSCode command
Volar: Select TypeScript Version...
then chooseUse 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):
// ...
// 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'
// },
// }),
],
})
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.devThe
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
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:
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:
\{^_^}/ 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:
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.Remove json-server from the
package.json
file.
{
"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:
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:
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`
- Create the
src/pages/blog/index.vue
file.
<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>
- Create the
src/pages/blog/[slug].vue
file.
<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:
{
+ "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
.
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 />`
Remove
<LandingLayout theme="light">
fromsrc/pages/blog/index.vue
andsrc/pages/blog/[slug].vue
Create the
src/pages/blog.vue
file with the following content:
<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):
<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
- SidebarLayout
src/layouts/SidebarLayout.vue
- Regular Sidebar: with theme="default"
- Curved Sidebar: with theme="curved"
- Colored Sidebar: with theme="color"
- Curved Colored Sidebar: with theme="color-curved"
- Labels Sidebar: with theme="labels"
- Labels Hover Sidebar: with theme="labels-hover"
- Float Sidebar: with theme="float"
- NavbarLayout
src/layouts/NavbarLayout.vue
- Regular Navbar: with theme="default"
- Fading Navbar: with theme="fade"
- Colored Navbar: with theme="colored"
- NavbarDropdownLayout
src/layouts/NavbarDropdownLayout.vue
- Dropdown Navbar: with theme="default"
- Colored Dropdown Navbar: with theme="colored"
- NavbarSearchLayout
src/layouts/NavbarSearchLayout.vue
- Clean Navbar: with theme="default"
- Clean Center Navbar: with theme="center"
- Clean Fade Navbar: with theme="fade"
- SideblockLayout
src/layouts/SideblockLayout.vue
- Regular Sideblock: with theme="default"
- Curved Sideblock: with theme="curved"
- Colored Sideblock: with theme="color"
- Curved Colored Sideblock: with theme="color-curved"
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