diff --git a/src/plainui/jinja2/plainui/under_construction.html.j2 b/src/plainui/jinja2/plainui/under_construction.html.j2
new file mode 100644
index 0000000000000000000000000000000000000000..cb6c907afdd7f35040efa66906a3488dfc50e0f3
--- /dev/null
+++ b/src/plainui/jinja2/plainui/under_construction.html.j2
@@ -0,0 +1,25 @@
+{% extends "plainui/base.html" %}
+{% import "plainui/components/integrations.html" as integrations %}
+{% import "plainui/components/markdown.html" as markdownMacro %}
+{% import "plainui/components/nav.html" as navMacro with context %}
+{% import "plainui/components/three_cards.html" as threeCardsMacro %}
+{% import "plainui/components/buttons.html" as buttonsMacro %}
+{% import "plainui/components/list_events.html.j2" as list_events with context %}
+
+{% block title %}{{ conf.name }} - {{ _("Under Construction") }}{% endblock %}
+{% block content %}
+
+  {{ navMacro.top_nav(_("Under Construction") ) }}
+
+  <div class="hub-vlayout">
+    <div class="hub-row">
+      <div class="hub-col-325px hub-card">
+        <h2 class="hub-section-title">{{ _("under_construction__box-not_yet_published__title") }}</h2>
+        <div class="hub-vlayout-l">
+          <p>{{ _("under_construction__box-not_yet_published__paragraph") }}</p>
+        </div>
+      </div>
+    </div>
+  </div>
+
+{% endblock content %}
diff --git a/src/plainui/locale/de/LC_MESSAGES/django.po b/src/plainui/locale/de/LC_MESSAGES/django.po
index be4ecfc786afb7091b9a0a5ca4150031d89678a2..0df8ff1454d5c05accd0597f19582a42e9e1520e 100644
--- a/src/plainui/locale/de/LC_MESSAGES/django.po
+++ b/src/plainui/locale/de/LC_MESSAGES/django.po
@@ -771,6 +771,15 @@ msgstr "<Unbekannter Nutzer>"
 msgid "Password Reset"
 msgstr "Passwort Zurücksetzen"
 
+msgid "Under Construction"
+msgstr "Kommt bald"
+
+msgid "under_construction__box-not_yet_published__title"
+msgstr "Kommt bald"
+
+msgid "under_construction__box-not_yet_published__paragraph"
+msgstr "An dieser Stelle entsteht gerade der Hub für den 38c3, bitte gedulde dich noch etwas."
+
 msgid "Send PN"
 msgstr "DN senden"
 
diff --git a/src/plainui/locale/en/LC_MESSAGES/django.po b/src/plainui/locale/en/LC_MESSAGES/django.po
index b60eeceeaaa659a5e00d2cb607683402dbf9b48f..81cc4cf34b1aab0c893559f5657638ad7beae736 100644
--- a/src/plainui/locale/en/LC_MESSAGES/django.po
+++ b/src/plainui/locale/en/LC_MESSAGES/django.po
@@ -771,6 +771,15 @@ msgstr ""
 msgid "Password Reset"
 msgstr ""
 
+msgid "Under Construction"
+msgstr ""
+
+msgid "under_construction__box-not_yet_published__title"
+msgstr "Under Construction"
+
+msgid "under_construction__box-not_yet_published__paragraph"
+msgstr "The hub for the 38c3 is currently being built here, please be patient."
+
 msgid "Send PN"
 msgstr "Send DN"
 
diff --git a/src/plainui/urls.py b/src/plainui/urls.py
index 9bfcf728b9f64d8b802316c1e2fd98f7d81c45d8..47c1b27c0e30f6b4cfd4613c23dafbdb297f3b4f 100644
--- a/src/plainui/urls.py
+++ b/src/plainui/urls.py
@@ -55,6 +55,7 @@ urlpatterns = [
     path('modify_favorites', views.ModifyFavoritesView.as_view(), name='modify_favorites'),
     path('modify_theme', views.ModifyThemeView.as_view(), name='modify_theme'),
     path('index', views.IndexView.as_view(), name='index'),
+    path('under_construction', views.UnderConstructionView.as_view(), name='under_construction'),
     path('pm/inbox', views.PersonalMessageListView.as_view(), name='personal_message'),
     path('pm/outbox', views.PersonalMessageListView.as_view(sent=True), name='personal_message_outbox'),
     path('pm/new', views.PersonalMessageSendView.as_view(), name='personal_message_send'),
diff --git a/src/plainui/views/auth.py b/src/plainui/views/auth.py
index 80a99308b32a87f27b5c431dbc724ccdc9770735..5dd1b2586f2112bd0c1f3f8020d5e2011d48531a 100644
--- a/src/plainui/views/auth.py
+++ b/src/plainui/views/auth.py
@@ -48,6 +48,7 @@ class LoginView(ConferenceRequiredMixin, BaseLoginView):
     next_page = 'plainui:index'
     require_conference_member = False
     require_login = False
+    only_published = False
 
     def get_context_data(self, **kwargs):
         context = super().get_context_data(**kwargs)
@@ -66,6 +67,7 @@ class RegistrationView(ConferenceRequiredMixin, BaseRegistrationView):
     activation_url = 'plainui:signup_activate'
     require_conference_member = False
     require_login = False
+    only_published = False
 
     def get_form_kwargs(self) -> dict[str, Any]:
         return {**super().get_form_kwargs(), 'conference': self.conf}
@@ -82,6 +84,7 @@ class RegistrationDoneView(ConferenceRequiredMixin, TemplateView):
     template_name = 'plainui/signup_done.html'
     require_conference_member = False
     require_login = False
+    only_published = False
 
     def get_context_data(self, **kwargs):
         context = super().get_context_data(**kwargs)
@@ -94,6 +97,7 @@ class RegistrationDoneView(ConferenceRequiredMixin, TemplateView):
 class RegistrationActivationView(ConferenceRequiredMixin, BaseRegistrationActivationView):
     require_conference_member = False
     require_login = False
+    only_published = False
     redirect_target_failure = 'plainui:signup'
     redirect_target_success = 'plainui:index'
 
@@ -107,6 +111,7 @@ class RegistrationActivationView(ConferenceRequiredMixin, BaseRegistrationActiva
 class PasswordChangeView(ConferenceRequiredMixin, auth_views.PasswordChangeView):
     require_user = True
     require_conference_member = False
+    only_published = False
     template_name = 'plainui/password_change.html'
 
     @method_decorator(sensitive_post_parameters())
@@ -128,6 +133,7 @@ class PasswordChangeView(ConferenceRequiredMixin, auth_views.PasswordChangeView)
 class PasswordResetView(ConferenceRequiredMixin, BasePasswordResetView):
     require_conference_member = False
     require_login = False
+    only_published = False
     success_url = reverse_lazy('plainui:password_reset_done')
     template_name = 'plainui/registration/password_reset_form.html'
     extra_email_context = {'reset_url': 'plainui:password_reset_confirm'}
@@ -144,6 +150,7 @@ class PasswordResetDoneView(ConferenceRequiredMixin, auth_views.PasswordResetDon
     template_name = 'plainui/password_reset_done.html'
     require_conference_member = False
     require_login = False
+    only_published = False
 
     def get_context_data(self, **kwargs):
         context = super().get_context_data(**kwargs)
@@ -157,6 +164,7 @@ class PasswordResetConfirmView(ConferenceRequiredMixin, BasePasswordResetConfirm
     template_name = 'plainui/password_reset_confirm.html'
     require_conference_member = False
     require_login = False
+    only_published = False
     success_url = reverse_lazy('plainui:password_reset_complete')
     failure_url = reverse_lazy('plainui:password_reset')
 
@@ -172,6 +180,7 @@ class PasswordResetCompleteView(ConferenceRequiredMixin, auth_views.PasswordRese
     template_name = 'plainui/password_reset_complete.html'
     require_conference_member = False
     require_login = False
+    only_published = False
 
     def get_context_data(self, **kwargs):
         context = super().get_context_data(**kwargs)
@@ -185,6 +194,7 @@ class TokenPasswordResetView(ConferenceRequiredMixin, FormView):
     template_name = 'plainui/token_password_reset.html'
     require_conference_member = False
     require_login = False
+    only_published = False
     form_class = TokenPasswortResetForm
 
     def get_context_data(self, *args, **kwargs):
@@ -212,6 +222,7 @@ class LogoutView(auth_views.LogoutView):
 class ShibboleetView(ConferenceRequiredMixin, View):
     require_user = True
     require_conference_member = False
+    only_published = False
 
     def get(self, *args, **kwargs):
         if settings.SHIBBOLEET_WA_ROOM_ID is None:
diff --git a/src/plainui/views/dereferrer.py b/src/plainui/views/dereferrer.py
index 75a168f9abde62bff41628ac192f0071ecbbeb72..a09642665cfbb5266a28fcb4d69274db2ad200a3 100644
--- a/src/plainui/views/dereferrer.py
+++ b/src/plainui/views/dereferrer.py
@@ -30,6 +30,8 @@ from plainui.views.utils import ConferenceRequiredMixin
 
 
 class BaseDereferrerView(ConferenceRequiredMixin, View):
+    only_published = False
+
     def gen_salt(self):
         if self.request.user.is_authenticated:
             return f'{self.request.user.pk}dereferrer'
diff --git a/src/plainui/views/general.py b/src/plainui/views/general.py
index 8da737d4bc8b66459e8f63138e20b5f931782c81..e2b3eef5d97b3ecf688e75fb21bbe48fcf97167c 100644
--- a/src/plainui/views/general.py
+++ b/src/plainui/views/general.py
@@ -5,6 +5,7 @@ __all__ = (
     'TagView',
     'SearchView',
     'ComponentGalleryView',
+    'UnderConstructionView',
 )
 
 from datetime import timedelta
@@ -83,9 +84,23 @@ class IndexView(ConferenceRequiredMixin, TemplateView):
         return context
 
 
+class UnderConstructionView(ConferenceRequiredMixin, TemplateView):
+    template_name = 'plainui/under_construction.html.j2'
+    only_published = False
+
+    def get_context_data(self, **kwargs):
+        context = super().get_context_data(**kwargs)
+        context['conf'] = self.conf
+
+        context['report_info'] = {'enabled': True}
+
+        return context
+
+
 class MetaNavView(ConferenceRequiredMixin, TemplateView):
     require_login = False
     require_conference_member = False
+    only_published = False
     template_name = 'plainui/metanav.html'
 
     # TODO: implement caching, e.g. using ConferenceExportCache?
diff --git a/src/plainui/views/personal_messages.py b/src/plainui/views/personal_messages.py
index 64aec416a2ee6173e9003cbb7ce4973984928a4b..23c16c9f0e6f7f320292e7dd4472ee991d61f035 100644
--- a/src/plainui/views/personal_messages.py
+++ b/src/plainui/views/personal_messages.py
@@ -31,6 +31,7 @@ from plainui.views.utils import (
 
 class PersonalMessageListView(ConferenceRequiredMixin, TemplateView):
     require_user = True
+    only_published = False
     template_name = 'plainui/personal_message_list.html'
     sent = False
 
@@ -61,6 +62,7 @@ class PersonalMessageListView(ConferenceRequiredMixin, TemplateView):
 @method_decorator(ratelimit(key='ip', rate='5/m', method=ratelimit.UNSAFE), name='dispatch')
 class PersonalMessageSendView(ConferenceRequiredMixin, FormView):
     require_user = True
+    only_published = False
     template_name = 'plainui/personal_message_send.html'
     form_class = NewDirectMessageForm
 
@@ -110,6 +112,7 @@ class PersonalMessageSendView(ConferenceRequiredMixin, FormView):
 
 class PersonalMessageShowView(ConferenceRequiredMixin, TemplateView):
     require_user = True
+    only_published = False
     template_name = 'plainui/personal_message_show.html'
 
     def get_context_data(self, msg_id, **kwargs):
@@ -131,6 +134,7 @@ class PersonalMessageShowView(ConferenceRequiredMixin, TemplateView):
 
 class PersonalMessageDeleteView(ConferenceRequiredMixin, View):
     require_user = True
+    only_published = False
 
     def get(self, request, **kwargs):
         return redirect(reverse('plainui:personal_message'))
diff --git a/src/plainui/views/report.py b/src/plainui/views/report.py
index a9491643ed14b798dfe2c8b097d057a6cd4d766e..4ea305c1d171eb6c45fe6f8ad8819666f7ffde5d 100644
--- a/src/plainui/views/report.py
+++ b/src/plainui/views/report.py
@@ -22,6 +22,7 @@ from plainui.views.utils import (
 @method_decorator(ratelimit(key='ip', rate='5/m', method=ratelimit.UNSAFE), name='dispatch')
 class ReportContentView(ConferenceRequiredMixin, FormView):
     require_user = True
+    only_published = False
     template_name = 'plainui/report_content.html'
     form_class = ReportForm
 
diff --git a/src/plainui/views/user_profile.py b/src/plainui/views/user_profile.py
index 0a0a6817e87ce54d5879c6aee90211f40aad7354..3ead46e079763f47da82a68bba4602bedd33dc7d 100644
--- a/src/plainui/views/user_profile.py
+++ b/src/plainui/views/user_profile.py
@@ -49,6 +49,7 @@ from plainui.views.utils import (
 
 class ProfileView(ConferenceRequiredMixin, UpdateView):
     require_user = True
+    only_published = False
     template_name = 'plainui/profile.html'
     form_class = ProfileEditForm
 
diff --git a/src/plainui/views/utils.py b/src/plainui/views/utils.py
index 7296f860f3158732c04653f93ce771720f58dcf4..4c150bd4a8886006d827b909573c799eb5633394 100644
--- a/src/plainui/views/utils.py
+++ b/src/plainui/views/utils.py
@@ -33,6 +33,8 @@ class ConferenceRequiredMixin(auth_mixins.AccessMixin):
     """ This view requires a logged-in user, regardless of the conference being public or non-public """
     require_conference_member = True
     """ This view requires the user having a valid ticket. Implies `require_login`. """
+    only_published = True
+    """ This view should only be shown when the conference is published."""
 
     # cache for the conference used in this plainui instance
     _conf: ClassVar[Optional[Conference]] = None
@@ -91,6 +93,9 @@ class ConferenceRequiredMixin(auth_mixins.AccessMixin):
 
         self.conf = conf
 
+        if self.only_published and not conf.is_published:
+            return redirect(reverse('plainui:under_construction'))
+
         if (
             self.require_login and conf.require_login or conf.require_ticket and self.require_conference_member or self.require_user
         ) and not request.user.is_authenticated: