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 files200.html
and404.html
. As the time of writing this document, the404.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 thedefinePageMeta
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 thenext
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.