Site Build Notes
We will discuss how the general components and modules used to build this site.
Resources
I started with the excellent Udemy course by Piotr Jura Unlock Nuxt 3 & Vue Mastery: Build a Markdown Blog-Portfolio and Supabase Finance Tracker here and studied the GitHub source final and then like all developers before me, I built from there.
Definitions
- Server Side Rendering - SSR - Server runs the JavaScript, fetches data, modifies the HTML document before sending to the browser to render.
- Client Side Rendering - CSR - Server sends the HTML scaffold document and the JavaScript to the browser. The browser runs the JavaScript, fetches data, and renders the updated HTML document.
- Universal Render - (aka dynamic rendering, or hydrated rendering) is both SSR and CSR.
- Static Site Generation - SSG - Generate the site and upload the pre-rendered site to a static web hosting service
- Web Apps/Single Page Apps (SPA) - single page app like Facebook, Twitter/X
- Hydration is making a static page interactive in the browser. You 'hydrate' a static page, when the client JavaScript is run and document re-rendered.
Nuxt 3
Setup Nuxt Project
npx nuxi@latest init <project name>
Nuxt Config file
nuxt.config.ts
default config file docs
Nuxt Directories
public
- publicly available assets - icons, images, etc.server
- server-side codepages
- Website page componentscomponents
- all the components in your application - auto imported by Nuxt from herecomponents/content
- Vue components used in content fileslayouts
- common layouts like header, every-single pagecomposables
- small pieces of reusable code or componentscontent
- blog, code, and ops markdown article files.nuxt
,node_modules
- generative by Nuxt and NPM
Nuxt Routing
- A route is created automatically for each page in the
pages
directory. A subdirectory will add a level to the route parameter. - Use
<NuxtPage />
where you want to pages to appear based on route. Typically, this is in theapp.vue
file, within the<NuxtLayout />
element - Add a routing page with a parameter you add
[]
to the filename! - Add the parameter name inside the brackets like
[...slug].vue
slug
means where spaces are dash characters - blog post short name
Layout
A page can belong to a Nuxt Layout which contains the hierarchical structure. This gives all the pages for the Nuxt Layout a common template, script, and style.
- Nuxt uses the
layouts
directory by default and the filedefault.vue
- You wrap the
<NuxtPage />
with a<NuxtLayout></NuxtLayout>
inapp.vue
like this. The layout and page are dynamic
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
You assign custom layouts for a page in the following three ways (using the custom another.vue
layout in the code
directory)
- override globally in the
<NuxtLayout :name="another"
element and name inapp.vue
- On the page in script setup use Nuxt
definePageMeta
with the name of the layout in thelayout
property, like this.
definePageMeta({
layout: 'another'
})
- On a page you can dynamically update the layout by using
setPageLayout()
function with the name of the layout Vue router slug, like this.
function enableCustomLayout () {
setPageLayout('another')
}
Nuxt Modules
You can browse Nuxt modules here. You install a Nuxt specific package according to the instructions.
Components Directory
The default folder for components is /components
, but that can be updated/customized.
Component Names
When a component has a compound name you can either name the file as either
compound-name.vue
orCompoundName.vue
(my preferred) In either case, when coding the component in the<template></template>
section of a page the reference would be<CompoundName></CompoundName>
Data fetching
Nuxt has three built in data fetching methods
useFetch
composableuseAsyncData
composable$fetch
Nuxt Content Doc
The Nuxt Content Doc is a Nuxt module that allows you to generate HTML content from markdown files. Used in combination with Tailwind Typography module to automatically 'up level the default style' Markdown documents to beautiful and readable HTML.
- Markdown Cheat Sheet
- Markdown Extended Syntax
- Markdown Syntax Highlighting - GitHub
- Markdown NuxtContent Usage
Installation
Install Content Doc
npx nuxi@latest module add content
Content Folder
The Content Doc module will automatically scan the /content
folder in the source tree for markdown files and generate HTML for each. In each page .vue
file you add a <ContentDoc />
element within <template></template>
section. The NuxtContent generated HTML from the markdown file is injected in the <ContentDoc />
element.
The /content
directory structure should match the /pages
folder, so the Markdown file at /content/about.md
contents will be converted and injected into the <ContentDoc />
in the <template></template>
for the page at /pages/about.vue
.
Mapping Content to Different Pages
To override the default behavior, you can specify that a different Markdown file is included by adding a path="{contentfolderpath}"
property to the <ContentDoc />
. For example, to inject the /content/blog/2023/hello.md
into the /pages/about.vue
page, you would add the following.
<ContentDoc path="/blog/2023/hello" />
NuxtContent Remark Plugin
Nuxt Content uses the MDC Remark plugins process Markdown text and to allow Markdown to support Vue components. One downside to this approach is that it will convert an image into a <p><img></p>
structure. For example, an image specified in Markdown like this:
![Logo](/images/PPNDLogoSm.png)
It will get converted by the MDC Remark plugin into HTML like this:
<p>
<img title="Logo" src="/images/PPNDLogoSm.png">
</p>
remark-unwrap-images
The outer <p></p>
tags can cause formatting issues.
NuxtContent MDC uses remark plugins list to do the conversion to code blocks. A remark-unwrap-images plugin will unwrap images from the paragraph elements.
The Nuxt Configuration for remark plugins documents on how to enable them.
Unwrap Installation
npm install remark-unwrap-images
And then add this to your nuxt.config.js
content: {
markdown: {
remarkPlugins: ['remark-unwrap-images']
},
},
Tailwind CSS
The Tailwind CSS module enables the use of Tailwind CSS classes and particularly the Typographic Prose class for NuxtContent Markdown files. Tailwind CSS has a CSS 'reset', which resets the basic default classes for common HTML elements like <p></p>
and <h1></h1>
. The Tailwind Typographic Prose class is an Uber class which will style its element and all of its children elements with carefully chosen defaults for consistency and readability.
Installation
I installed the Tailwind CSS Typographic Prose module like this:
npm install --save-dev @nuxtjs/tailwindcss
added 104 packages, and audited 931 packages in 7s
184 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
PS C:\dev\>
Further you need to add the Tailwind CSS module to the nuxt.config.ts
file
export default defineNuxtConfig({
devtools: { enabled: true },
modules: [
'@nuxtjs/tailwindcss'
]
})
Restarting the server will enable the new module. You can check in the Vue dev tools under the modules section.
Typographic Prose Class
A common way to add the Tailwind Typographic prose class is to add the class="prose"
attribute to the parent element containing the NuxtContent <ContentDoc />
element, for example the surrounding <div></div>
or <article></article>
element. Tailwind CSS, by default, will strip all the normal default HTML styling, i.e. the <h1></h1>
element is not shown as large by default. When you use the Prose class it reasserts a clean and typography styling. The <h1></h1>
elements are all set to this. You add the tailwind class prose
for light mode and dark: prose-invert
for dark mode.
<template>
<article class="prose dark:prose-invert">
<ContentDoc />
</article>
</template>
Using Tailwind CSS
In a .vue
file you can use tailwind CSS classes directly in the <template></template>
section, or you can apply them to CSS rules in the <style></style>
section. To add tailwind classes in the style section use the @apply
line, for example, the class link
has the tailwind CSS class p-1
and hover:bg-gray-200
applied to it.
<style scoped>
.link {
@apply p-1 hover:bg-gray-200
}
</style>
Note you can also use regular styles, the :deep()
combinator, v-bind()
, etc. alongside the @apply
.
For example:
<style>
.monk-inset :deep(p) {
font-size: v-bind('pFontSizeClass');
line-height: v-bind('pLineHeightClass');
height: v-bind('pLineHeightClass');
margin: -0.2ch 0 0 0;
@apply p-0
}
</style>
Social Share Buttons
Since each page has custom metadata, I also wanted convenience buttons to quickly share the page on social media. Stefano Bartoletti Nuxt Social Share module was a good and easy as following the instructions to add the module. Then add the component into the page template.
The terminal command I issued.
npx nuxi@latest module add nuxt-social-share
nuxt.config.ts
entries required
modules: [
'@stefanobartoletti/nuxt-social-share'
],
socialShare: {
baseUrl: 'https://pennockprojects.com'
}
Here is the usage within the code.
<template>
/<!-- snip -->
<SocialShare
v-for="network in ['facebook', 'x', 'linkedin', 'email']"
:key="network"
:label="false"
:network="network"
:styled="true"
/>
<!-- snip -->
</template>
Nuxt PDF
CloudFlare Analytics
Install
npm i nuxt-cloudflare-analytics
Update nuxt.config.js
{
modules: [
'nuxt-cloudflare-analytics'
],
cloudflareAnalytics: {
// See below for more options
token: 'your-token', // Example 1a2b3v4a5er6ac7r8afd
}
}
web-vitals
npm install web-vitals