Amplify a Wagtail/Django site - URLs (Part 1)

Accelerated Mobile Pages (AMP) is an Open Source project with a focus on creating a fast and smooth experience for website users. AMP version of a page also shows at the top of Google search results for mobile users. AMP is must for every content site these days. If you already have a running website, AMP is just another version of an existing page on the site. AMP version loads fast due to AMP spec like max CSS size can be 50KB only. You can read more about AMP on its site.

This blog post is the first part of the series: Amplify a Wagtail/Django site.

  • Part 1: Amplify a Wagtail/Django site - URLs (current article)
  • Part 2: Amplify a Wagtail/Django site - Validation
  • Part 3: Amplify a Wagtail/Django site - CORS

Wagtail and AMP logo.

Wagtail is a beautiful CMS built on top of the Django web framework. This blog series will list the steps to create an AMP version of your existing Wagtail pages. A demo project is available on Github.

URL of the AMP version of page

As AMP is just another version of an existing page, it will have a new URL. Commonly amp keyword is added to the normal page URL. Suppose we want to Amplify a page like https://wagtail.io/blog/graphql-with-streamfield/. AMP version of this page can have the following URLs:

  • https://wagtail.io/blog/graphql-with-streamfield/amp/
  • https://wagtail.io/amp/blog/graphql-with-streamfield/

The first URL is easy to create in Wagtail. But the second URL needs some customization in the Wagtail serving mechanism. This blog post will list the steps to use any of these URLs in your project.

URL 1: AMP keyword at end of Normal URL

Wagtail comes with a contrib app called RoutablePageMixin. The RoutablePageMixin extend a page to respond on multiple sub URLs with different views.

class BlogPage(RoutablePageMixin, Page):
    # Complete code: https://github.com/Parbhat/wagtail-amp/blob/master/blog/models.py#L47
    ...
    ...

    @route(r'^amp/$')
    def amp(self, request):
        context = self.get_context(request)
        response = TemplateResponse(
            request, 'blog/blog_page_amp.html', context
        )
        return response

    ...

In the above example, we get the Blog Page object context and use it in the AMP template of a blog page. So, if a blog page has a URL https://wagtail.io/blog/wagtail-space-usa/. AMP version will exist at https://wagtail.io/blog/wagtail-space-usa/amp/

Template requirements

Normal page template (blog_page.html) should include this Meta tag. It helps Google know the location of AMP version of this page.

{% load wagtailroutablepage_tags %}

<link rel="amphtml" href="{{ request.site.root_url }}{% routablepageurl page 'amp' %}" />

AMP template (blog_page_amp.html) should include

<link rel="canonical" href="{{ page.full_url }}"/>

URL 2: AMP keyword after site root URL

Using the first approach is sufficient in most cases. In some custom Django projects, this approach will be useful if the non Wagtail powered AMP pages also use amp keyword after root URL. So, we need to maintain consistency in the URL pattern of Wagtail powered and other URLs. We have to customize the Wagtail serving mechanism. First, we will understand the Wagtail serving mechanism and then work on the customization.

Wagtail Serving mechanism:

  1. When a request is made, it is passed to Wagtail serve view.

  2. Serve view creates path components list for the requested path. For example, if a request is made for URL https://wagtail.io/blog/graphql-with-streamfield/. Path components list will be

    path_components = ['blog', 'graphql-with-streamfield']
    
  3. Then current site's root page route method is called with path_components. In demo project, root page is home.HomePage.

  4. Wagtail checks the page existence by slug one by one from the path_components list. For example, it first checks the root page (HomePage) has a child with slug: blog. If a page is found, the route method of the blog index page is called with remaining path components. In case a page is not found with a slug, 404 error is raised.

  5. The process continues and takes a slug from the path_components list until it becomes empty. And if a page is live it is returned to serve view.

  6. In serve view, the serve method of the page object is called.

Wagtail docs also have a great explanation on Anatomy of a Wagtail Request.

Changes in the Serving mechanism for AMP

As the AMP version of Wagtail page should be served from https://wagtail.io/amp/*, some changes are required in above workflow:

  1. As the HomePage route method is first called. We can have a flag (is_amp_request) which helps to determine the request is for the AMP page. If the first item in the path_components list is amp, we set the Flag on and reduce the list to path_components[1:]. So, we can check page with remaining path components exists. For example, if the request is made for https://wagtail.io/amp/blog/graphql-with-streamfield/

    Wagtail creates path components list in serve view.

    path_components = ['amp', 'blog', 'graphql-with-streamfield']
    

    In HomePage route method, it is changed to

    path_components = ['blog', 'graphql-with-streamfield']
    

    Overriding route method

    class HomePage(Page):
        body = RichTextField(blank=True)
    
        def route(self, request, path_components):
            # Check the request is for AMP page
            is_amp_request = False
            if path_components and path_components[0] == 'amp':
                is_amp_request = True
                # Remove the amp from path components to check if the page exist
                path_components = path_components[1:]
    
            page, args, kwargs = super(HomePage, self).route(
                request, path_components
            )
            if is_amp_request:
                # If the page has amp template serve it otherwise raise 404
                if hasattr(page, 'get_template_amp'):
                    kwargs['is_amp_request'] = is_amp_request
                else:
                    raise Http404
    
            return page, args, kwargs
    
        content_panels = Page.content_panels + [
            FieldPanel('body', classname="full"),
        ]
    
        parent_page_types = ['wagtailcore.Page']
    
    • If the page exists in Wagtail with the remaining path_components, we check the page has an AMP version by method get_template_amp. If no, we raise 404 error as the AMP version for the page not exists. If yes, we add a kwarg argument kwargs['is_amp_request'] = is_amp_request.

    • The page object, args and kwargs are returned to Wagtail Serve view from the root page's route method.

  2. In Wagtail serve view, page's serve method is called with args, kwargs. In the case of a blog page, we customised the Serve method to handle AMP requests. Using the kwarg argument is_amp_request, we return the AMP version of the blog page.

    class AmpBlogPage(Page):
        # Complete code: https://github.com/Parbhat/wagtail-amp/blob/master/blog/models.py#L85
        ...
        ...
    
        def get_template_amp(self, request, *args, **kwargs):
            return 'blog/amp_blog_page_amp.html'
    
        def serve(self, request, *args, **kwargs):
            is_amp_request = kwargs.get('is_amp_request')
            if is_amp_request:
                kwargs.pop('is_amp_request')
                context = self.get_context(request, *args, **kwargs)
    
                return TemplateResponse(
                    request,
                    self.get_template_amp(request, *args, **kwargs),
                    context
                )
            return super(AmpBlogPage, self).serve(request, *args, **kwargs)
    
        ...
    

What's Next

Part 1 of this series list two options to choose a URL for AMP version. The focus of next parts in the series will be

  • Part 2 on Validation: AMP spec has some restriction on the elements like img tag. AMP page should pass Validation to get served from Google AMP cache. In the next part, we will work on creating Valid Wagtail powered AMP pages.

  • Part 3 on CORS: When using AMP components like amp-list, the server should include CORS headers in the response. It is not specific to Wagtail and applies to any Django project.

You can also follow the Github repo. AMP-lify!

By @Parbhat Puri in
Tags : #wagtail, #django, #opensource, #web, #Programming, #code,

Comments !