diff --git a/djangoldp/__init__.py b/djangoldp/__init__.py
index efc86af2331f391a7ed00ffbccd8a1c50f0f8651..7f7595fa3486f400aad249cd1e95a12a4a9ce5bc 100644
--- a/djangoldp/__init__.py
+++ b/djangoldp/__init__.py
@@ -5,4 +5,4 @@ __version__ = '0.0.0'
 options.DEFAULT_NAMES += (
     'lookup_field', 'rdf_type', 'rdf_context', 'auto_author', 'owner_field', 'owner_urlid_field',
     'view_set', 'container_path', 'permission_classes', 'serializer_fields', 'serializer_fields_exclude', 'empty_containers',
-    'nested_fields', 'depth', 'permission_roles', 'inherit_permissions', 'public_field')
+    'nested_fields', 'depth', 'permission_roles', 'inherit_permissions', 'public_field', 'static_version', 'static_params')
diff --git a/djangoldp/management/commands/generate_static_content.py b/djangoldp/management/commands/generate_static_content.py
new file mode 100644
index 0000000000000000000000000000000000000000..2cce19e3bc93cac243c6fae6df7e9169d5c84afa
--- /dev/null
+++ b/djangoldp/management/commands/generate_static_content.py
@@ -0,0 +1,107 @@
+import os
+import requests
+import json
+from django.core.management.base import BaseCommand
+from django.conf import settings
+from django.apps import apps
+from urllib.parse import urlparse, urlunparse
+
+base_uri = getattr(settings, 'BASE_URL', '')
+max_depth = getattr(settings, 'MAX_RECURSION_DEPTH', 5)
+request_timeout = getattr(settings, 'SSR_REQUEST_TIMEOUT', 10)
+
+class Command(BaseCommand):
+    help = 'Generate static content for models having the static_version meta attribute set to 1/true'
+
+    def handle(self, *args, **kwargs):
+        output_dir = 'ssr'
+        if not os.path.exists(output_dir):
+          os.makedirs(output_dir, exist_ok=True)
+
+
+        for model in apps.get_models():
+            if hasattr(model._meta, 'static_version'):
+                print(f"Generating content for model: {model}")
+                container_path = model.get_container_path()
+                url = f'{base_uri}{container_path}'
+                print(f"Current request url before adding params: {url}")
+
+                if hasattr(model._meta, 'static_params'):
+                    # static_params are added to the url as query parameters
+                    url += '?'
+                    for key, value in model._meta.static_params.items():
+                        url += f'{key}={value}&'
+                    url = url[:-1]
+
+                print(f"Current request url after adding params: {url}")
+                response = requests.get(url, timeout=request_timeout)
+
+                if response.status_code == 200:
+                    content = response.text
+                    content = self.update_ids_and_fetch_associated(content, base_uri,  output_dir, 0, max_depth)
+
+                    filename = container_path[1:-1]
+                    file_path = os.path.join(output_dir, f'{filename}.jsonld')
+
+                    print(f"Output file_path: {file_path}")
+                    with open(file_path, 'w') as f:
+                        f.write(content)
+                    self.stdout.write(self.style.SUCCESS(f'Successfully fetched and saved content for {model._meta.model_name} from {url}'))
+                else:
+                    self.stdout.write(self.style.ERROR(f'Failed to fetch content from {url}: {response.status_code}'))
+
+    def update_ids_and_fetch_associated(self, content, base_uri, output_dir, depth, max_depth):
+        if depth > max_depth:
+            return content
+
+        try:
+            data = json.loads(content)
+            if isinstance(data, list):
+                for item in data:
+                    self.update_and_fetch_id(item, base_uri, output_dir, depth, max_depth)
+            else:
+                self.update_and_fetch_id(data, base_uri, output_dir, depth, max_depth)
+
+            return json.dumps(data)
+        except json.JSONDecodeError as e:
+            self.stdout.write(self.style.ERROR(f'Failed to decode JSON: {e}'))
+            return content
+
+    def update_and_fetch_id(self, item, base_uri, output_dir, depth, max_depth):
+        if '@id' in item:
+            parsed_url = urlparse(item['@id'])
+            path = f'/ssr{parsed_url.path}'
+            item['@id'] = urlunparse((parsed_url.scheme, parsed_url.netloc, path, parsed_url.params, parsed_url.query, parsed_url.fragment))
+
+            associated_url = urlunparse((parsed_url.scheme, parsed_url.netloc, parsed_url.path, parsed_url.params, parsed_url.query, parsed_url.fragment))
+            associated_file_path = path[1:-1] + '.jsonld'
+            associated_file_dir = os.path.dirname(associated_file_path)
+
+            if not os.path.exists(associated_file_dir):
+                os.makedirs(associated_file_dir)
+
+            try:
+                response = requests.get(associated_url, timeout=request_timeout)
+                if response.status_code == 200:
+                    associated_content = self.update_ids_and_fetch_associated(response.text, base_uri, output_dir, depth + 1, max_depth)
+                    associated_file_dir = os.path.dirname(associated_file_path)
+
+                    if not os.path.exists(associated_file_dir):
+                        os.makedirs(associated_file_dir)
+                    with open(associated_file_path, 'w') as f:
+                        f.write(associated_content)
+                    self.stdout.write(self.style.SUCCESS(f'Successfully fetched and saved associated content for {associated_url}'))
+                else:
+                    self.stdout.write(self.style.ERROR(f'Failed to fetch associated content from {associated_url}: {response.status_code}'))
+            except requests.exceptions.Timeout:
+                self.stdout.write(self.style.ERROR(f'Request to {associated_url} timed out'))
+            except requests.exceptions.RequestException as e:
+                self.stdout.write(self.style.ERROR(f'An error occurred: {e}'))
+
+        for key, value in item.items():
+            if isinstance(value, dict):
+                self.update_and_fetch_id(value, base_uri, output_dir, depth, max_depth)
+            elif isinstance(value, list):
+                for sub_item in value:
+                    if isinstance(sub_item, dict):
+                        self.update_and_fetch_id(sub_item, base_uri, output_dir, depth, max_depth)
\ No newline at end of file
diff --git a/djangoldp/urls.py b/djangoldp/urls.py
index 86682c5f7e48d9e58c65cef39835769da43943dd..defa166bed99aac2c0c7a28aa4b2258671703b6a 100644
--- a/djangoldp/urls.py
+++ b/djangoldp/urls.py
@@ -7,7 +7,8 @@ from django.urls import path, re_path, include
 from djangoldp.models import LDPSource, Model
 from djangoldp.permissions import ReadOnly
 from djangoldp.views import LDPSourceViewSet, WebFingerView, InboxView
-from djangoldp.views import LDPViewSet
+from djangoldp.views import LDPViewSet, serve_static_content
+
 
 def __clean_path(path):
     '''ensures path is Django-friendly'''
@@ -35,7 +36,8 @@ urlpatterns = [
     re_path(r'^sources/(?P<federation>\w+)/', LDPSourceViewSet.urls(model=LDPSource, fields=['federation', 'urlid'],
                                                                     permission_classes=[ReadOnly], )),
     re_path(r'^\.well-known/webfinger/?$', WebFingerView.as_view()),
-    path('inbox/', InboxView.as_view())
+    path('inbox/', InboxView.as_view()),
+    re_path(r'^ssr/(?P<path>.*)$', serve_static_content, name='serve_static_content'),
 ]
 
 if settings.ENABLE_SWAGGER_DOCUMENTATION:
diff --git a/djangoldp/views.py b/djangoldp/views.py
index 27f0400a4cd62f84ee0ab7614e402d151c172a50..408976900b702d327f5b458878348abd8f52390d 100644
--- a/djangoldp/views.py
+++ b/djangoldp/views.py
@@ -5,7 +5,7 @@ from django.conf import settings
 from django.contrib.auth import get_user_model
 from django.core.exceptions import FieldDoesNotExist, ObjectDoesNotExist
 from django.db import IntegrityError, transaction
-from django.http import JsonResponse, Http404
+from django.http import JsonResponse, Http404, HttpResponseNotFound
 from django.shortcuts import get_object_or_404
 from django.urls import include, re_path, path
 from django.urls.resolvers import get_resolver
@@ -31,6 +31,7 @@ from djangoldp.utils import is_authenticated_user
 from djangoldp.activities import ActivityQueueService, as_activitystream, ACTIVITY_SAVING_SETTING, ActivityPubService
 from djangoldp.activities.errors import ActivityStreamDecodeError, ActivityStreamValidationError
 import logging
+import os
 
 logger = logging.getLogger('djangoldp')
 get_user_model()._meta.rdf_context = {"get_full_name": "rdfs:label"}
@@ -615,3 +616,23 @@ class WebFingerView(View):
 
     def post(self, request, *args, **kwargs):
         return self.on_request(request)
+
+
+def serve_static_content(request, path):
+    file_path = os.path.join('ssr', path[:-1])
+    if not file_path.endswith('.jsonld'):
+        file_path += '.jsonld'
+
+    if os.path.exists(file_path):
+        with open(file_path, 'r') as file:
+            content = file.read()
+
+        json_content = json.loads(content)
+        return JsonResponse(json_content, safe=False, status=200,
+                            content_type='application/ld+json',
+                            headers={
+                              'Access-Control-Allow-Origin': '*',
+                              'Cache-Control': 'public, max-age=3600',
+                            })
+    else:
+        return HttpResponseNotFound('File not found')
diff --git a/docs/commands.md b/docs/commands.md
new file mode 100644
index 0000000000000000000000000000000000000000..357305d4de693c8b7256cac6e960f39a029b2b82
--- /dev/null
+++ b/docs/commands.md
@@ -0,0 +1,41 @@
+# Available commands
+
+## generate_static_content
+
+You can generate and make available at a /ssr/xxx URI a static copy of the AnonymousUser view of given models.
+Those models need to be configured with the `static_version` and `static_params` Meta options like:
+
+```python
+class Location(Model):
+    name = models.CharField(max_length=255)
+    address = models.CharField(max_length=255)
+    zip_code = models.IntegerField()
+    visible = models.BooleanField(default=False)
+
+    class Meta:
+        # Allow generating a static version of the container view
+        static_version = 1
+
+        # Add some GET parameters to configure the selection of data
+        static_params = {
+          "search-fields": "visible",
+          "search-terms": True,
+          "search-method": "exact"
+        }
+```
+
+You will need additional settings defined either in your settings.yml or settings.py file:
+
+```yml
+BASE_URL: 'http://localhost:8000/'
+MAX_RECURSION_DEPTH: 10 # Default value: 5
+SSR_REQUEST_TIME: 20 # Default value 10 (seconds)
+```
+
+Then you can try it out by executing the following command:
+
+```sh
+python manage.py generate_static_content
+```
+
+You can also set a cron task or a celery Task to launch this command in a regular basis.
\ No newline at end of file
diff --git a/docs/create_model.md b/docs/create_model.md
index 9297ba5274fcbe142aab297e38b3c3dbdb2a1bac..50fffc9f240ca61dd2c3819962573c14d849084c 100644
--- a/docs/create_model.md
+++ b/docs/create_model.md
@@ -1,5 +1,5 @@
 
-### User model requirements
+# User model requirements
 
 When implementing authentication in your own application, you have two options:
 
@@ -469,3 +469,4 @@ MIDDLEWARE = [
 ```
 
 Notice that it will return only HTTP 200 Code.
+