In the second Nuxt tutorial issue, we'll look at the basics of creating the presentation layer of our future Nuxt application.
Components in General
All Vue.js applications (Nuxt itself runs on top of Vue.js) are built using components. A Vue.js component is typically a text file with a .vue extension, consisting of three possible parts:
-
<template>- html-like instructions defining the rendering specification, which is converted (compiled) into actual HTML+CSS+JS at runtime -
<script>- place for JavaScript (TypeScript) to handle the component's functionality -
<style>- option to define graphical CSS styles
The <style> section is quite often omitted because the component either doesn't need to define its "own" styles, or styling is handled in the classic centralized way through importing .css (or .scss, etc.) files. Or you use Tailwind CSS, which will be covered in later tutorials.
We also encounter components without a <script> section that only define static portion of your website, or use simple JS expressions inlined directly in <template>.
Even a component without <template> is valid (but must contain a <script> section in such a case) and is also called a "functional" component, but I haven't actually encountered practical use for it yet. For handling code without graphical output, it's better to use other methods, which we'll discuss in later parts of the tutorial.
Components are a way to create reusable logic, so they can receive unique properties (props) from outside for each instance and emit events to the surroundings. However, we'll cover this in more detail later.
To be able to use components in other places of a Vue.js application, they need to be registered - either in another component where we want to use them, or globally for the entire application. For both cases, in vanilla Vue.js this must always be performed manually.
For a Vue.js application to have more pages than just the initial content at the domain level, you need to use the Vue router tool and define URL paths (routes) to components that should be displayed for each URL entered in the browser. Again, this must be done by you.
Nuxt can effectively shield programmers from both of these tasks through special directories /app/components and /app/pages, which we'll now introduce.
Note: Since Nuxt v4, the directories are located within /app. Previously they were directly in the project's root directory, but after a discussion an update to the logical project structure was introduced. Nuxt 4 is also smart enough to recognize the original structure and adapt accordingly, so it doesn't need to be changed immediately in older projects. However, for teaching purposes, we'll naturally stick with the latest way of working.
/app/components
Nuxt expects to find our defined components in the project subdirectory /app/components. It's able to recognize that it's a component definition file and perform its automatic global registration. So if we write a component /app/components/MyComponent.vue, without having to worry about anything else, we can use the <MyComponent> tag in templates of other components, which will render its content in the appropriate place.
Additionally, Nuxt can navigate into nested subdirectories on its own, composing the resulting component name from the entire relative path + the .vue filename. A component /app/components/my/component.vue will also be available as <MyComponent>. Even in this case, we don't have to worry about anything.
However, I'd like to attach a small warning right away. This feature, together with the Vue.js convention that component names should be composed „od nejobecnějšího ke konkrétnímu“, tempts you to create many subdirectories with many single-word named .vue files at the lowest level. This doesn't contradict another convention about multi-word names for Vue.js components (to prevent clashes with existing or possible future HTML tags), because the resulting name is composed of path + filename. It sounds like a good idea at first, but in larger applications it can later bring problems with orientation in a huge pile of variously nested files. Files in different paths can also have the same name and get confused with each other. Therefore, some developers rather recommend that all components should be named with their full name and placed directly in the root directory /app/components.
I don't have a completely formed opinion on this yet. It's true that in a larger project where I introduced fine subdivision, I ran into orientation problems. On the other hand, from a certain number of files, probably no single directory is that clear either. Another option is to use the setting pathPrefix: false, then Nuxt will stop adding directory names before the filename. However, this requires more advanced intervention into the configuration.
What's important anyway is that Nuxt supports various options and doesn't need much more from us besides the files themselves. We can even decide to change the component structure after some time and don't have to modify anything anywhere. Because registrations are automatic, there's no need to define paths anywhere in scripts, and therefore we don't have to change them either. Nuxt generates its linking .d.ts files itself, so it will rework them itself too. At worst, we'll need a short restart of the development server.
Final note: It's not dogma that your components must necessarily be located inside /app/components. Source directories (yes, there can be more than one) can be set up to suit your needs. It's not common usage for beginners, but it illustrates well the general aspect of the framework - at its core it offers convenient default behavior suitable for most users, while also being maximally easy to customize it.
/app/pages
Thanks to the /app/components folder, we already have a way to make our defined components accessible everywhere in a Nuxt application. The second special folder /app/pages also contains files that are technically Vue.js components. Nuxt also processes them automatically, but does something a bit different with them. It converts the contained subdirectories and component files into application paths (routes). If you enter the corresponding URL in the browser, the content of the appropriate content will be rendered.
The foundation is typically the /app/pages/index.vue file, which contains what will be displayed after the first visit to the domain - in the test case http://localhost:3000. If we have a truly "single-page" application, this step can be omitted and everything can be directly in app.vue (or in components referenced from there). However, as soon as we start adding more pages, we can't really do without this page anymore, because Nuxt would then have trouble determining what to display in the default state.
If we add a /app/pages/first.vue file, the application will be able to display it when visiting http://localhost:3000/first. If we add subdirectories, their structure is respected. The content of /app/pages/first/second.vue will be displayed at http://localhost:3000/first/second. If Nuxt doesn't find any corresponding page, it will throw a 404 error. All of this, just like with component registration, happens "automatically". We simply just add new pages and Nuxt creates new URLs.
To be completely precise, the page won't display just like that, we must have the special component called <NuxtPage /> reachable from /app/app.vue. In theory, this could be the only content of app.vue, and then the content of each page will be unique based on the data from /app/pages. But more commonly we define some basic layout shared through the entire application and somewhere inside it we display unique content according to the URL within a <NuxtPage />.
As if that weren't enough, Nuxt also handles so-called "dynamic" pages in addition to predefined URLs. If we wrap the filename or directory name in square brackets, Nuxt will start catching "anything" that couldn't be mapped to a more specific path. This means that if we create /app/pages/[page].vue and nothing else, absolutely everything will fall into this component (so it's not entirely true that we always need /app/pages/index.vue). If we also create /app/pages/first.vue, then http://localhost:3000/first will lead to the content of the first page and anything else to the content of the [page] page.
The part in square brackets usually serves to identify unique data content, which however is displayed the same in general terms. Typically, we might have, for example, a page for product detail in a store /app/pages/product/[id].vue, which will display specific data within a common template based on the captured id.
Parameters obtained from the URL path are available in the <script> part of the page component via useRoute().params - for example, useRoute().params.id or useRoute().params.page depending on what we named our dynamic parameter. We can then work with this value in the script and get the correct data for display.
The second variant of use is so-called "catch-all" paths, with which we display a default page for "anything other than what we have a dedicated page for", and avoid the need to handle the 404 error.
I'd like to mention three more tricks:
- you can combine static and dynamic parts - i.e., for example, have files
/app/pages/product-[id].vueand/app/pages/cagtegory-[id].vue - or it's possible to have multiple dynamic parameters in one path -
/app/pages/[category]/[product].vue - since version 3.13, there are also so-called "route groups" - directories in parentheses (e.g.,
/(dir)/) are ignored within the path -/app/pages/(extra)/page.vuewill be accessible in the browser just as/page. It's an option to better organize content from the developer's point of view without affecting the result from the user's point of view.
As you can see, the system is really flexible and allows different "tricks" out-of-the-box. And if the default Nuxt behavior wasn't enough for us, if we need to dig deeper into the usual behavior, it is not a problem at all. You still have access directly to the [Vue Router] object (https://nuxt.com/docs/api/composables/use-router), which lives underneath all of this and actually runs the navigation. Nuxt does the work that's sufficient in most cases, but the possibility of full control is still with the programmer.
Case Study
Where it makes sense, I'll try to demonstrate what we're talking about with an illustrative example.
I have my own blog, where you can read the original of this article (in Czech language).
This blog is displayed to you thanks to the file /app/pages/article/[article].vue. The foundation (header with name, dates and tags + footer with links to GitHub) is common to all articles and is realized using two components - /app/components/ArticleHeader.vue and /app/components/ArticleFooter.vue. According to the specific URL (here /article/nuxt-pages), the required article content is selected - it matches the unique expression at the end of the URL (here nuxt-pages). The navigation box to other parts of the tutorial is rendered thanks to the /app/components/ArticleNavigation.vue component, which itself contains other components and conditionally renders them based on the article value.
Note: The basic principle applies, but the way of working with the obtained article value in practice is actually a bit more complex - the rendering of the article is realized using the Nuxt Content module, which you'll learn more about in the future.
Demo Project
You can find the source code of a sample implementation illustrating the principle of components and pages in Nuxt here:
nuxt-pages @ GitHub
In basic "entry" file /app/app.vue you can see how to create a layout common to all pages and render the current page content according to the URL inside <NuxtPage /> within this layout.
For display, there are two helper components in the /app/components directory:
-
TheMenu.vue- defines links to individual pages and is inserted (only once) into the common layout in/app/app.vue -
TextBox.vue- contains a<div>that displays text taken from a property (prop) passed inside each instance, used multiple times with different texts
The project then defines a total of 4 pages in the /app/pages directory:
-
index.vue- default available at path/or/index -
first.vue- will be displayed after navigating to/first -
second.vue- will be displayed after navigating to/second -
(extra)/third.vue- will be displayed after navigating to/third(demonstrates the novelty from Nuxt 3.13 called route groups)
As a bonus, the project shows the usage of a special component <NuxtLink>, which replaces the plain HTML anchor link (<a>). Aside from other useful features, it prevents page from full reload if it is not needed, so it can save server CPU and bandwidth.
Summary
Nuxt makes working with Vue.js components easier by automatically scanning the dedicated directories /app/components and /app/pages and performing automatic global registration of components into the application. Moreover, it automatically creates routing for navigation using URL links over the directory and file structure within /app/pages.
In the next part of the tutorial, we'll continue with an explanation of other very useful directories /app/composables and /app/utils.
Top comments (0)