Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Web app overview

The web app is created using:

  • Nuxt 4
  • PrimeVue
  • TailwindCSS v4

Static site generation

The web app is a single-page application (SPA). Although Nuxt comes with its own web server with server-side rendering support, Gutenberg does not use it. The app is rendered during the build step using the static preset of Nitro (the server component of Nuxt) with some modifications. The build command is:

cd webapp && pnpm run build

The result of the build is placed in the .output directory. Inside it, there are two important directories:

  • html contains the HTML files 200.html and 404.html. As the time of writing this document, the 404.html file is not used anywhere.
  • public contains other static files necessary for running the app: JavaScript, CSS, image and JSON files.

The public directory is designed to be served by Django or NGINX directly under yoursite.example.com/static/. It should be included in the STATICFILES_DIRS in Django settings. Please note that the files from .output/public are not the only files that need to be served under the /static/ endpoint, as other Django apps (like Django REST Framework) include their own static files.

When the DEBUG setting of Django is True, the command manage.py runserver serves all files from the directories specified in STATICFILES_DIRS, including the .output/public folder generated by Nuxt 4 and in other app directories (see also STATICFILES_FINDERS).

When it's set to False, Django does not serve the static files; a separate web server like NGINX is required. To gather all the static files, the Django command manage.py collectstatic is used. It copies all static files from STATICFILES_DIRS and installed Django apps to the directory specified by the STATIC_ROOT Django setting. It's this static root directory that should be served using NGINX under the /static/ endpoint.

The html is not included in the static files, because they should not be accessed under the /static/ endpoint. Django will serve them directly from the .output/html directory (specified in the custom GUTENBERG_SPA_HTML_DIR Django setting). See the file backend/printing/urls.py (included from backend/gutenberg/urls.py) for the details on how Django serves these files. Django will serve the 200.html file for any webapp request.

PR #91 introduces a new NGINX_ACCEL_ENABLED setting, which moves the responsibility of serving the HTML files also to NGINX.

The generation of the html files is altered in the prerender:generate Nitro hook in webapp/nuxt.config.js. It disables the rendering of index.html and changes the output directory for HTML files to .output/html.

Routing

The web app uses Vue Router for navigation. Since the same 200.html file is served for all routes (yoursite.example.com/, yoursite.example.com/print/setup-ipp/, etc.), Vue Router reads the URL from the window.location.pathname and determines which page to render based on that. Navigation between different web app routes is done internally by Vue Router using the History API, which modifies the URL in the browser's address bar but does not reload the page.

important

An exception to that is the /login/ route. Depending on the configuration, Django might either serve the 200.html file to render the web app login form or redirect to an Open ID Connect authentication URL. For this reason all links to the /login/ route should use standard HTML <a> links, not Vue Router links.

Trailing slashes

Django automatically redirects all requests without a trailing slash to the corresponding URL with a trailing slash. For example, a user trying to access yoursite.example.com/print/setup-ipp will be redirected to yoursite.example.com/print/setup-ipp/.

The default Vue Router configuration generated by Nuxt prefers routes without a trailing slash, so a custom pages:extend hook in webapp/nuxt.config.js changes the route matching logic to only handle routes with a trailing slash. Due to this, internal navigation to yoursite.example.com/print/setup-ipp will show a Vue Router "404" page, so care must be taken to append trailing slashes in Vue Router links. If a user tried to access yoursite.example.com/print/setup-ipp from the address bar, Django will redirect them to the correct URL (with a trailing slash) before serving the web app.

Authentication

Some web app routes are publicly accessible, while others require authentication. The login route can only be accessed by unauthenticated users.

Two mechanisms are used for automatic redirection if the user cannot access a route:

  • In Django the backend/printing/urls.py specifies the view function to use for a given route:
    • webapp_public for public routes;
    • webapp_require_auth for routes that require authentication;
    • login for the login route.
  • In Nuxt the login route uses a custom middleware and authenticated routes use a shared require-auth middleware. The middleware used for a given route is specified in the definePageMeta composable.

The Django views and Nuxt middlewares have the same job of redirecting:

  • unauthenticated users trying to access a page that requires authentication: to the login page with the next search query parameter pointing to the route which they tried to access;
  • authenticated users who are trying to access the /login/ page: to the route specified in the next parameter.

Django handles the redirection if the user makes a request to the server to access a page, Nuxt middleware is triggered when users are using Vue Router internal navigation.

important

When adding or modifying routes, care should be taken to keep the Django and Nuxt authentication logic in sync.

The auth Nuxt plugin

A custom webapp/app/plugins/auth.ts plugin handles the authentication logic in the web app. The plugin uses the async setup syntax, so no components will be rendered until the auth state is loaded from the API.

For almost all requests made to the API using the custom webapp/app/plugins/api.ts plugin, an onResponseError hook is installed to handle the 401/403 responses from the API. If an API call fails because the user is not authenticated according to the API, but the user was authenticated in the app, they are immediately redirected to the login page (with the next query parameter set to the path of the route which they tried to access and an expired flag to display a "session expired" message on the login screen).

Page layouts

A few components were created for standard page layouts used in the web app. The layouts are plain components, not Nuxt layouts. Future updates might change that.

The standard layouts are sidebar-layout.vue and single-column-layout.vue. Only the app-panel.vue and app-content.vue components should be used as their direct children. This design keeps the logic for handling different screen sizes centralized in these components.

PrimeVue components

Currently, all the PrimeVue components used in the app need to be specified in webapp/nuxt.config.js. A comment in that file explains why.

Development server

cd webapp && pnpm run dev # (runs "nuxt dev")

This script starts a Nuxt development server with live updates and integrated Nuxt devtools. The server is configured to proxy API endpoints to the Django development server (the host of the Django dev server is specified using the GUTENBERG_DEV_DJANGO_URL env variable, or http://localhost:11111/ if unspecified). The proxied endpoints are specified in webapp/nuxt.config.js.

warning

This dev proxy has some issues — for example, the redirect to the logout page makes Nuxt display a 404 page until the site is reloaded.

The Nuxt server also watches for changes in the code and generates type definitions in the .nuxt directory. Without the dev server running, the type definitions might not be up to date, and your IDE might not resolve automatic imports correctly.