A Quasar v2 App Extension to generate static site AKA JAMstack.
๐ Supports Vite since v4.4.0.
This project was created to fill this Feature Request from Quasar.
Live Demo | Installing | Uninstalling | Upgrading | Developing | Usage | Configuration | Infos
A live demo built from a fresh new Quasar CLI project (with Vite) is available at https://quasar-app-extension-ssg-vite.netlify.app.
The demo achieves a Google PageSpeed โโInsights score of 100 for mobile and desktop platforms.
Run this command into the Quasar project:
quasar ext add ssg
This will find and install the extensionโs module. Once the installation is complete, interactive prompts will wait for responses.
Vite
-
add scripts into package.json?
: Extends package.json by adding scripts.scripts: { 'build:ssg': 'quasar ssg generate', 'serve:ssg': 'quasar ssg serve dist/ssg' }
-
Add auto-completion of ssg property of quasar.config.js file for IDE ?
: Augmentsconfigure()
helper fromquasar/wrappers
to get a better IDE autocomplete experience with the extension. -
Inline critical css and async load the rest ?
: Uses Beastcss to inline critical CSS and async load the rest for each generated page.
Webpack
-
add scripts into package.json?
: Extends package.json by adding scripts.scripts: { 'build:ssg': 'quasar ssg generate', 'serve:ssg': 'quasar ssg serve dist/ssg' }
-
Add auto-completion of ssg property of quasar.config.js file for IDE ?
: Augmentsconfigure()
helper fromquasar/wrappers
to get a better IDE autocomplete experience with the extension. -
Inline critical css and async load the rest ?
: Uses Beastcss to inline critical CSS and async load the rest for each generated page. -
Inline CSS from Vue SFC <style> blocks ?
: Inlines css from Vue Single-File Component (SFC)<style>
blocks.
quasar ext remove ssg
This is done with the same command used for installation:
echo y | quasar ext add ssg
Note: It is recommended to overwrite files when requested, so as not to miss any updates.
To generate a static site run this command from the quasar project folder:
quasar ssg generate
-h, --help
: Display usage instructions.--force-build
: Force to build the application with webpack.-d, --debug
: Build for debugging purposes.
๐ Added in v4.2.0
Starts the app in development mode (live reloading, error reporting, etc):
quasar ssg dev
The development server allows to develop the app by compiling and maintaining code in-memory. A web server will serve the app while offering live-reload out of the box. Running in-memory offers faster rebuilds when the code is changed.
The server can be configured by editing the `/quasar.config.jsโ file:
devServer: {
host: '...',
port: ...
}
-h, --help
: Display usage instructions.--port, -p
: A port number on which to start the application.--hostname, -H
: A hostname to use for serving the application.--devtools, -d
: Open remote Vue Devtools.
This extension provides a command to create a server to locally test the generated static site:
quasar ssg serve <dist-folder>
Notes: This server is based on the Quasar cli server adapted for static site. It serves the SPA fallback file (404.html) when a page has not been generated for a given route.
-
--port, -p
: Port to use (default: 4000). -
--hostname, -H
: Address to use (default: 0.0.0.0). -
--prefix-path
: Create a virtual path prefix (default: /). -
--gzip, -g
: Compress content (default: true). -
--silent, -s
: Suppress log message. -
--colors
: Log messages with colors (default: true). -
--open, -o
: Open browser window after starting. -
--cache, -c <number>
: Cache time (max-age) in seconds. Does not apply to /service-worker.js (default: 86400 - 24 hours). -
--micro, -m <seconds>
: Use micro-cache (default: 1 second). -
--https
: Enable HTTPS. -
--cert, -C [path]
: Path to SSL cert file (Optional). -
--key, -K [path]
: Path to SSL key file (Optional). -
--proxy <file.js>
: Proxy specific requests defined in file. File must export Array ({ path, rule }). "rule" is defined at: https://github.com/chimurai/http-proxy-middleware.module.exports = [ { path: "/api", rule: { target: "http://www.example.org" }, }, ]; // will be transformed into app.use(path, httpProxyMiddleware(rule))
-
--cors
: Enable CORS for all requests. -
--help, -h
: Display usage instructions.
This command can be used to inspect the Webpack config generated by this app extension.
quasar ssg inspect
Vite
-
--cmd, -c
: Quasar SSG command [dev|generate] (default: dev) -
-d, --depth
: Number of levels deep (default: 2). -
-p, --path
: Path of config in dot notation.Examples:
quasar ssg inspect -p build.rollupOptions quasar ssg inspect -p plugins
-
--colors
: Style output with ANSI color codes (default: true). -
--thread, -t
: Display only one specific ssg config thread -
-h, --help
: Display usage instructions.
Webpack
-
-d, --depth
: Number of levels deep (default: 5). -
-p, --path
: Path of config in dot notation.Examples:
quasar ssg inspect -p module.rules quasar ssg inspect -p plugins
-
--colors
: Style output with ANSI color codes (default: true). -
-h, --help
: Display usage instructions.
Options can be passed with ssg
key in /quasar.config.js
file.
// quasar.config.js
module.exports = function (/* ctx */) {
return {
// ...
ssg: {
// pass options here
},
// ...
};
};
See all available options
Type: Number
Default: 10
Page generation is concurrent, ssg.concurrency
specifies the amount of page generation that runs in one thread.
Type: Number
Default: 0
Interval in milliseconds between two batches of concurrent page generation to avoid flooding a potential API with calls to the API from the web application.
Notes:
This option is intended to be used in conjunction with the
concurrency
option. For example, settingconcurrency
to10
andinterval
to5000
will execute the generation of 10 pages in parallel every 5 seconds.
Type: String[]
or Function
Default: []
A list of routes to generate the corresponding pages.
Note: As of quasar-app-extension-ssg v2.0.0 this option is optionnal due to the crawler feature and the ability to include static routes from the app's router using the
ssg.includeStaticRoutes
option.If the app has unlinked pages (such as secret pages) and these also need to be generated, the
ssg.routes
property can be used.
Example:
ssg: {
routes: ["/", "/about", "/users", "/users/someone"];
}
With a Function
which returns a Promise
:
// quasar.config.js
const axios = require("axios");
module.exports = function (/* ctx */) {
return {
// ...
ssg: {
routes() {
return axios.get("https://my-api/users").then((res) => {
return res.data.map((user) => {
return "/users/" + user.id;
});
});
},
},
// ...
};
};
With a Function
which returns a callback(err, params)
:
// quasar.config.js
const axios = require("axios");
module.exports = function (/* ctx */) {
return {
// ...
ssg: {
routes(callback) {
axios
.get("https://my-api/users")
.then((res) => {
const routes = res.data.map((user) => {
return "/users/" + user.id;
});
callback(null, routes);
})
.catch(callback);
},
},
// ...
};
};
๐ Added in v4.0.0
Type: Boolean
Default: true
Include the application's router static routes to generate the corresponding pages.
Note: In case of warnings issued when initializing routes, this option can be disabled. Then the
crawler
feature and thessg.routes
options can be used to provide the static and dynamic routes.
๐ Added in v4.2.0
Type: String
Default: '<project-folder>/dist/ssg'
Folder where the extension should generate the distributables. Relative path to project root directory.
Type: String
Default: '<project-folder>/node_modules/.cache/quasar-app-extension-ssg'
or '<project-folder>/.ssg-build'
if cache
is set to false.
The webpack compilation output folder from where the extension can prerender pages.
Type: Object
or false
Default:
{
ignore: [
join(conf.ssg.distDir, '/**'), // dist/ssg
join(conf.ssg.buildDir, '/**'), // node_modules/.cache/quasar-app-extension-ssg
...conf.build.distDir ? [join(conf.build.distDir, '/**')] : [],
'dist/**',
'public/**',
'src-ssr/**',
'src-cordova/**',
'src-electron/**',
'src-bex/**',
'src/ssg.d.ts',
'node_modules/**',
'.**/*',
'.*',
'README.md'
],
globbyOptions: {
gitignore: true
}
}
This option caches the compilation output folder and skips recompilation when no tracked file has changed.
-
ignore
is a Globby patterns to ignore tracked files. If an array is provided, it will be merged with default options. A function can be passed to return an array that will remove the defaults.Example with an
Array
:ssg: { cache: { ignore: ["renovate.json"]; // ignore changes applied on this file } }
With a
Function
:ssg: { cache: { ignore: (defaultIgnore) => defaultIgnore.push("renovate.json") && defaultIgnore; } }
-
globbyOptions
can be used to add globby options.
Type: String
Default: '404.html'
The name of the SPA/PWA fallback file intended to be served when an index.html file does not exist for a given route.
Notes:
- Overrides
build.htmlFilename
andbuild.ssrPwaHtmlFilename
.- This file is created with the webpack plugin
html-webpack-plugin
via this Quasar file. It can be extended with some plugins.- Multiple services (e.g. Netlify, Vercel) detect a 404.html automatically. For custom web server, an error page must be set up and set to the 404.html file.
๐ Added in v2.0.0
Type: Boolean
Default: true
Crawls html links as each page is generated to find dynamic and static routes to add to the page generation queue.
๐ Added in v2.0.0
Type: String[] | Regexp[]
An array of routes or regular expressions matching them to prevent corresponding pages from being generated.
Example with an Array of String
:
ssg: {
exclude: ["/my-secret-page"];
}
With an Array of Regexp
:
ssg: {
exclude: [
/^\/admin/, // path starts with /admin
];
}
๐ Added in v3.3.0
Type: Function
A function to control what files should have resource hints generated.
By default, no assets will be preloaded.
Example to preload assets:
ssg: {
shouldPreload: (file, type, ext) => {
// type is inferred based on the file extension.
// https://fetch.spec.whatwg.org/#concept-request-destination
if (type === "script" || type === "style") {
return true;
}
if (type === "font" && ext === "woff2") {
// only preload woff2 fonts
return file;
}
if (type === "image") {
// only preload important images
return file === "hero.jpg";
}
};
}
๐ Added in v3.3.0
Type: Function
A function to control what files should have resource hints generated.
By default no assets will be prefetched. However this is possible to customize what to prefetch in order to better control bandwidth usage. This option expects the same function signature as shouldPreload.
Type: Boolean
or Object
Default: true
Uses Beastcss to inline critical CSS and async load the rest for each generated page.
The default beastcss options can be customized by passing them to inlineCriticalCss
.
Example:
ssg: {
inlineCriticalCss: {
internal: false,
merge: false,
};
}
Notes:
The value is forced to
false
when using the dev command.
๐ Added in v3.3.0
Type: Boolean
Default: false
Inline css from Vue Single-File Component (SFC) <style>
blocks.
Note: This option works even if build.extractCSS is set to true
in quasar.config.js file.
Notes:
The value is forced to
true
when using the dev command.
Type: Function
Hook executed after pre-rendering a page just before writing it to the filesystem.
This function must return the html string.
Can use async/await or directly return a Promise.
Type: Function
Hook executed after all pages has been generated.
Can use async/await or directly return a Promise.
Note: The
files
parameter is anArray
of all generated page paths + filenames (including the fallback file).
See all available options
Type: Number
Default: 10
Page generation is concurrent, ssg.concurrency
specifies the amount of page generation that runs in one thread.
Type: Number
Default: 0
Interval in milliseconds between two batches of concurrent page generation to avoid flooding a potential API with calls to the API from the web application.
Notes:
This option is intended to be used in conjunction with the
concurrency
option. For example, settingconcurrency
to10
andinterval
to5000
will execute the generation of 10 pages in parallel every 5 seconds.
Type: String[]
or Function
Default: []
A list of routes to generate the corresponding pages.
Note: This option is optionnal due to the crawler feature and the ability to include static routes from the app's router using the
ssg.includeStaticRoutes
option.If the app has unlinked pages (such as secret pages) and these also need to be generated, the
ssg.routes
property can be used.
Example:
ssg: {
routes: ["/", "/about", "/users", "/users/someone"];
}
With a Function
which returns a Promise
:
// quasar.config.js
const axios = require("axios");
module.exports = function (/* ctx */) {
return {
// ...
ssg: {
routes() {
return axios.get("https://my-api/users").then((res) => {
return res.data.map((user) => {
return "/users/" + user.id;
});
});
},
},
// ...
};
};
With a Function
which returns a callback(err, params)
:
// quasar.config.js
const axios = require("axios");
module.exports = function (/* ctx */) {
return {
// ...
ssg: {
routes(callback) {
axios
.get("https://my-api/users")
.then((res) => {
const routes = res.data.map((user) => {
return "/users/" + user.id;
});
callback(null, routes);
})
.catch(callback);
},
},
// ...
};
};
Type: Boolean
Default: true
Include the application's router static routes to generate the corresponding pages.
Note: In case of warnings issued when initializing routes, this option can be disabled. Then the
crawler
feature and thessg.routes
options can be used to provide the static and dynamic routes.
Type: String
Default: '<project-folder>/dist/ssg'
Folder where the extension should generate the distributables. Relative path to project root directory.
Type: String
Default: '<project-folder>/node_modules/.cache/quasar-app-extension-ssg'
or '<project-folder>/.ssg-compilation'
if cache
is set to false.
The Vite compilation output folder from where the extension can prerender pages.
Type: Object
or false
Default:
{
ignore: [
join(conf.ssg.distDir, '/**'), // dist/ssg
join(conf.ssg.compilationDir, '/**'), // node_modules/.cache/quasar-app-extension-ssg
join(conf.build.distDir, '/**'),
'dist/**',
'src-ssr/**',
'src-cordova/**',
'src-electron/**',
'src-bex/**',
'src/ssg.d.ts',
'node_modules/**',
'.**/*',
'.*',
'README.md'
],
globbyOptions: {
gitignore: true
}
}
This option caches the compilation output folder and skips recompilation when no tracked file has changed.
-
ignore
is a Globby patterns to ignore tracked files. If an array is provided, it will be merged with default options. A function can be passed to return an array that will remove the defaults.Example with an
Array
:ssg: { cache: { ignore: ["renovate.json"]; // ignore changes applied on this file } }
With a
Function
:ssg: { cache: { ignore: (defaultIgnore) => defaultIgnore.push("renovate.json") && defaultIgnore; } }
-
globbyOptions
can be used to add globby options.
Type: String
Default: '404.html'
The name of the SPA/PWA fallback file intended to be served when an index.html file does not exist for a given route.
Notes:
- Overrides
build.htmlFilename
andbuild.ssrPwaHtmlFilename
.- This file is created with the webpack plugin
html-webpack-plugin
via this Quasar file. It can be extended with some plugins.- Multiple services (e.g. Netlify, Vercel) detect a 404.html automatically. For custom web server, an error page must be set up and set to the 404.html file.
Type: Boolean
Default: true
Crawls html links as each page is generated to find dynamic and static routes to add to the page generation queue.
Type: String[] | Regexp[]
An array of routes or regular expressions matching them to prevent corresponding pages from being generated.
Example with an Array of String
:
ssg: {
exclude: ["/my-secret-page"];
}
With an Array of Regexp
:
ssg: {
exclude: [
/^\/admin/, // path starts with /admin
];
}
Type: Function
A function to control what files should have resource hints generated.
By default, no assets will be preloaded.
Example to preload assets:
ssg: {
shouldPreload: ({ file, type, extension, isLazilyHydrated }) => {
// type is inferred based on the file extension.
// https://fetch.spec.whatwg.org/#concept-request-destination
if (type === "script" || type === "style") {
return true;
}
if (type === "font" && ext === "woff2") {
// only preload woff2 fonts
return file;
}
if (type === "image") {
// only preload important images
return file === "hero.jpg";
}
// do not preload anything else
return false;
};
}
Type: Function
A function to control what files should have resource hints generated.
By default no assets will be prefetched. However this is possible to customize what to prefetch in order to better control bandwidth usage. This option expects the same function signature as shouldPreload.
Type: Boolean
or Object
Default: true
Uses Beastcss to inline critical CSS and async load the rest for each generated page.
The default beastcss options can be customized by passing them to inlineCriticalCss
.
Example:
ssg: {
inlineCriticalCss: {
internal: false,
merge: false,
};
}
Notes:
The value is forced to
false
when using the dev command.
๐ Added in v4.5.0
Type: String
Default: 'Optional'
Set the font-display css descriptor of Roboto font imported from @quasar/extras package.
This blog post from the Chrome Developers website can help to choose the best value.
Notes: The Roboto font imported from @quasar/extras package is replaced by its woff2 version (instead of woff) which reduces its weight by half.
With the help of the unicode-range css descriptor the browser will download latin and/or latin-ext variants depending on the characters used in the page.
๐ Added in v4.5.0
Type: Boolean
Default: true
Auto import svg icons from @quasar/extras package.
Notes: For better performance when compiling the application, only icons from the configured Quasar Icon Set are auto imported.
Type: Function
Hook executed after pre-rendering a page just before writing it to the filesystem. This hook can be used to update html string and/or the generated page output path.
This function must return an Object
containing html
and path
properties.
Can use async/await or directly return a Promise.
Example:
const { join, sep } = require('path');
// skipped code...
ssg: {
onPageGenerated(page) {
// do not write generated pages in subfolders
// replace dist/ssg/some-route/index.html
// by dist/ssg/some-route.html
const normalizedRoute = page.route.replace(/\/$/, '');
const fileName = page.route.length > 1 ? join(sep, normalizedRoute + '.html') : join(sep, 'index.html')
return {
html: page.html,
path: page.path.replace(join(page.route, 'index.html'), fileName),
};
},
}
Type: Function
Hook executed after all pages has been generated.
Can use async/await or directly return a Promise.
Note: The
files
parameter is anArray
of all generated page paths + filenames (including the fallback file).
It is possible to lazy hydrate components using the vue3-lazy-hydration package.
In production, when generating pages, the extension does not inject script/preload tags for split chunks corresponding to lazily hydrated components. In this way, these components are loaded client-side, on-demand, when hydration occurs.
Since the version v4.0.0 the value of process.env.MODE
is ssg
when the app was built with the command quasar ssg generate
or quasar ssg dev
.
Below v4.0.0, process.env.STATIC
can be used instead.
It could be useful if multiple builds are mixed with different modes to differentiate runtime procedures.
Svg icons are highly recommended for SSG. The extension tries to reduce the disavantage of using svg by auto importing them.
Example:
// quasar.config.js
module.exports = configure(function (/* ctx */) {
return {
// skipped codes...
framework: {
iconSet: "svg-material-icons",
},
};
});
<!--- default MainLayout.vue --->
<template>
<q-layout view="lHh Lpr lFf">
<q-header elevated>
<q-toolbar>
<!--- matMenu icon is auto imported -->
<q-btn
flat
dense
round
:icon="matMenu"
aria-label="Menu"
@click="toggleLeftDrawer"
/>
<!--- skipped code -->
</q-toolbar>
</q-header>
</q-layout>
</template>
<script>
// skipped code...
const linksList = [
{
title: "Docs",
caption: "quasar.dev",
icon: matSchool, // auto imported icon
link: "https://quasar.dev",
},
// skipped code...
];
export default defineComponent({
// skipped code....
});
</script>
This Extension uses a boot file ssg-corrections.js
at client-side to apply corrections to the <body>
tag classes.
This is necessary because the running platform is unknown at build time.
SSG + PWA can be enabled by setting ssr.pwa
to true
inside quasar.config.js
file.
Vite
Quasar uses workbox-build package to generate a complete service worker and a list of assets to precache which is injected into the service worker file.
This means that all generated pages cannot be precached when Vite is compiling because they do not yet exist at this time. To fix this, when running the generate command, the extension moves the execution of workbox-build methods after all pages have been generated.
Webpack
Quasar uses workbox-webpack-plugin package to generate a complete service worker and a list of assets to precache which is injected into the service worker file.
This means that all generated pages cannot be precached when webpack is compiling because they do not yet exist at this time. To fix this, when running the generate command, the extension instead uses workbox-build package after all pages have been generated.
Therefore, workbox-build options must be passed in the key pwa.workboxOptions
of quasar.config.js
file instead of the workbox-webpack-plugin options. All other PWA options of the pwa
key in the quasar.config.js
file are valid and used according to the Quasar documentation.
The cache mechanism to avoid recompiling the app when it is not necessary is strongly inspired by Nuxt. See the Nuxt blog post about this feature.
The quasar-app-extension-ssg repository is a monorepo using pnpm workspaces. The package manager used to install and link dependencies must be pnpm.
The monorepo contains the extension package inside the packages
folder.
It also contains private packages in the playground
folder for testing the extension with quasar CLI with vite or webpack.
To develop locally, fork the repository then:
-
Clone it in your local machine.
-
Run
pnpm i
in the root folder. -
Change the code in the
packages/quasar-app-extension-ssg
folder. -
Run
pnpm -w run command:vite
orpnpm -w run command:webpack
to test the extension commands against private packages in the playground folder.
pnpm -w run watch
command in the root folder.
You may wish to test your locally modified copy against an external quasar project that is using the extension. To do this you must specify pnpm.overrides
and list the package as a dependency in the root package.json:
{
"dependencies": {
"quasar-app-extension-ssg": "^4.7.0"
},
"pnpm": {
"overrides": {
"quasar-app-extension-ssg": "link:../path/to/forked-repo/packages/quasar-app-extension-ssg"
}
}
}
pnpm
as a package manager it is mandatory to use yalc to avoid possible issues with unresolved dependencies.