diff --git a/grudge/geometry/metrics.py b/grudge/geometry/metrics.py
index 24c5f8a28fe9320993a82b0d876b2a1bf542abde..5535725464383999347bb40953ae6a5dd507308d 100644
--- a/grudge/geometry/metrics.py
+++ b/grudge/geometry/metrics.py
@@ -379,7 +379,8 @@ def inverse_metric_derivative(
 
 def inverse_surface_metric_derivative(
         actx: ArrayContext, dcoll: DiscretizationCollection,
-        rst_axis, xyz_axis, dd=None):
+        rst_axis, xyz_axis, dd=None,
+        *, _use_geoderiv_connection=False):
     r"""Computes the inverse surface metric derivative of the physical
     coordinate enumerated by *xyz_axis* with respect to the
     reference axis *rst_axis*. These geometric terms are used in the
@@ -391,6 +392,12 @@ def inverse_surface_metric_derivative(
     :arg xyz_axis: an integer denoting the physical coordinate axis.
     :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one.
         Defaults to the base volume discretization.
+    :arg _use_geoderiv_connection: If *True*, process returned
+        :class:`~meshmode.dof_array.DOFArray`\ s through
+        :meth:`~grudge.DiscretizationCollection._base_to_geoderiv_connection`.
+        This should be set based on whether the code using the result of this
+        function is able to make use of these arrays. (This is an internal
+        argument and not intended for use outside :mod:`grudge`.)
     :returns: a :class:`~meshmode.dof_array.DOFArray` containing the
         inverse metric derivative at each nodal coordinate.
     """
@@ -402,20 +409,25 @@ def inverse_surface_metric_derivative(
     dd = dof_desc.as_dofdesc(dd)
 
     if ambient_dim == dim:
-        return inverse_metric_derivative(
+        result = inverse_metric_derivative(
             actx, dcoll, rst_axis, xyz_axis, dd=dd
         )
     else:
         inv_form1 = inverse_first_fundamental_form(actx, dcoll, dd=dd)
-        return sum(
+        result = sum(
             inv_form1[rst_axis, d]*forward_metric_nth_derivative(
                 actx, dcoll, xyz_axis, d, dd=dd
             ) for d in range(dim))
 
+    if _use_geoderiv_connection:
+        result = dcoll._base_to_geoderiv_connection(dd)(result)
+
+    return result
+
 
 def inverse_surface_metric_derivative_mat(
         actx: ArrayContext, dcoll: DiscretizationCollection, dd=None,
-        *, times_area_element=False):
+        *, times_area_element=False, _use_geoderiv_connection=False):
     r"""Computes the matrix of inverse surface metric derivatives, indexed by
     ``(xyz_axis, rst_axis)``. It returns all values of
     :func:`inverse_surface_metric_derivative_mat` in cached matrix form.
@@ -427,24 +439,33 @@ def inverse_surface_metric_derivative_mat(
     :arg times_area_element: If *True*, each entry of the matrix is premultiplied
         with the value of :func:`area_element`, reflecting the typical use
         of the matrix in integrals evaluating weak derivatives.
+    :arg _use_geoderiv_connection: If *True*, process returned
+        :class:`~meshmode.dof_array.DOFArray`\ s through
+        :meth:`~grudge.DiscretizationCollection._base_to_geoderiv_connection`.
+        This should be set based on whether the code using the result of this
+        function is able to make use of these arrays.  (This is an internal
+        argument and not intended for use outside :mod:`grudge`.)
     :returns: a :class:`~meshmode.dof_array.DOFArray` containing the
         inverse metric derivatives in per-group arrays of shape
         ``(xyz_dimension, rst_dimension, nelements, ndof)``.
     """
 
     @memoize_in(dcoll, (inverse_surface_metric_derivative_mat, dd,
-        times_area_element))
+        times_area_element, _use_geoderiv_connection))
     def _inv_surf_metric_deriv():
         if times_area_element:
-            multiplier = area_element(actx, dcoll, dd=dd)
+            multiplier = area_element(actx, dcoll, dd=dd,
+                    _use_geoderiv_connection=_use_geoderiv_connection)
         else:
             multiplier = 1
 
         mat = actx.np.stack([
-                actx.np.stack(
-                    [multiplier * inverse_surface_metric_derivative(actx, dcoll,
-                        rst_axis, xyz_axis, dd=dd)
-                        for rst_axis in range(dcoll.dim)])
+                actx.np.stack([
+                    multiplier
+                    * inverse_surface_metric_derivative(actx, dcoll,
+                        rst_axis, xyz_axis, dd=dd,
+                        _use_geoderiv_connection=_use_geoderiv_connection)
+                    for rst_axis in range(dcoll.dim)])
                 for xyz_axis in range(dcoll.ambient_dim)])
 
         return freeze(mat, actx)
@@ -524,7 +545,8 @@ def pseudoscalar(actx: ArrayContext, dcoll: DiscretizationCollection,
 
 
 def area_element(
-        actx: ArrayContext, dcoll: DiscretizationCollection, dd=None
+        actx: ArrayContext, dcoll: DiscretizationCollection, dd=None,
+        *, _use_geoderiv_connection=False
         ) -> DOFArray:
     r"""Computes the scale factor used to transform integrals from reference
     to global space.
@@ -533,16 +555,27 @@ def area_element(
 
     :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one.
         Defaults to the base volume discretization.
+    :arg _use_geoderiv_connection: If *True*, process returned
+        :class:`~meshmode.dof_array.DOFArray`\ s through
+        :meth:`~grudge.DiscretizationCollection._base_to_geoderiv_connection`.
+        This should be set based on whether the code using the result of this
+        function is able to make use of these arrays.  (This is an internal
+        argument and not intended for use outside :mod:`grudge`.)
     :returns: a :class:`~meshmode.dof_array.DOFArray` containing the transformed
         volumes for each element.
     """
     if dd is None:
         dd = DD_VOLUME
 
-    @memoize_in(dcoll, (area_element, dd, actx.supports_nonscalar_broadcasting))
+    @memoize_in(dcoll, (area_element, dd, _use_geoderiv_connection))
     def _area_elements():
-        return freeze(actx.np.sqrt(
-            pseudoscalar(actx, dcoll, dd=dd).norm_squared()), actx)
+        result = actx.np.sqrt(
+            pseudoscalar(actx, dcoll, dd=dd).norm_squared())
+
+        if _use_geoderiv_connection:
+            result = dcoll._base_to_geoderiv_connection(dd)(result)
+
+        return freeze(result, actx)
 
     return thaw(_area_elements(), actx)
 
@@ -573,8 +606,9 @@ def rel_mv_normal(
 
 def mv_normal(
         actx: ArrayContext, dcoll: DiscretizationCollection, dd,
+        *, _use_geoderiv_connection=False
         ) -> MultiVector:
-    """Exterior unit normal as a :class:`~pymbolic.geometric_algebra.MultiVector`.
+    r"""Exterior unit normal as a :class:`~pymbolic.geometric_algebra.MultiVector`.
     This supports both volume discretizations
     (where ambient == topological dimension) and surface discretizations
     (where ambient == topological dimension + 1). In the latter case, extra
@@ -584,12 +618,22 @@ def mv_normal(
     This function caches its results.
 
     :arg dd: a :class:`~grudge.dof_desc.DOFDesc` as the surface discretization.
+    :arg _use_geoderiv_connection: If *True*, process returned
+        :class:`~meshmode.dof_array.DOFArray`\ s through
+        :meth:`~grudge.DiscretizationCollection._base_to_geoderiv_connection`.
+        This should be set based on whether the code using the result of this
+        function is able to make use of these arrays.  The default value agrees
+        with ``actx.supports_nonscalar_broadcasting``.  (This is an internal
+        argument and not intended for use outside :mod:`grudge`.)
     :returns: a :class:`~pymbolic.geometric_algebra.MultiVector`
         containing the unit normals.
     """
     dd = dof_desc.as_dofdesc(dd)
 
-    @memoize_in(dcoll, (mv_normal, dd))
+    if _use_geoderiv_connection is None:
+        _use_geoderiv_connection = actx.supports_nonscalar_broadcasting
+
+    @memoize_in(dcoll, (mv_normal, dd, _use_geoderiv_connection))
     def _normal():
         dim = dcoll.discr_from_dd(dd).dim
         ambient_dim = dcoll.ambient_dim
@@ -627,13 +671,17 @@ def mv_normal(
 
             result = mv / actx.np.sqrt(mv.norm_squared())
 
+        if _use_geoderiv_connection:
+            result = dcoll._base_to_geoderiv_connection(dd)(result)
+
         return freeze(result, actx)
 
     n = _normal()
     return thaw(n, actx)
 
 
-def normal(actx: ArrayContext, dcoll: DiscretizationCollection, dd):
+def normal(actx: ArrayContext, dcoll: DiscretizationCollection, dd,
+        *, _use_geoderiv_connection=None):
     """Get the unit normal to the specified surface discretization, *dd*.
     This supports both volume discretizations
     (where ambient == topological dimension) and surface discretizations
@@ -644,10 +692,17 @@ def normal(actx: ArrayContext, dcoll: DiscretizationCollection, dd):
     This function may be treated as if it caches its results.
 
     :arg dd: a :class:`~grudge.dof_desc.DOFDesc` as the surface discretization.
+    :arg _use_geoderiv_connection: See :func:`mv_normal` for a full description.
+        (This is an internal argument and not intended for use outside
+        :mod:`grudge`.)
+
     :returns: an object array of :class:`~meshmode.dof_array.DOFArray`
         containing the unit normals at each nodal location.
     """
-    return mv_normal(actx, dcoll, dd).as_vector(dtype=object)
+    return mv_normal(
+            actx, dcoll, dd,
+            _use_geoderiv_connection=_use_geoderiv_connection
+            ).as_vector(dtype=object)
 
 # }}}
 
diff --git a/grudge/op.py b/grudge/op.py
index ed2a29b3fa3611388000dbe1a37fd4cccfc5015d..473e099d26f730e5d020f3746049b86fd1f17589 100644
--- a/grudge/op.py
+++ b/grudge/op.py
@@ -207,7 +207,8 @@ def local_grad(
     discr = dcoll.discr_from_dd(dof_desc.DD_VOLUME)
     actx = vec.array_context
 
-    inverse_jac_mat = inverse_surface_metric_derivative_mat(actx, dcoll)
+    inverse_jac_mat = inverse_surface_metric_derivative_mat(actx, dcoll,
+            _use_geoderiv_connection=actx.supports_nonscalar_broadcasting)
     return _gradient_kernel(actx, discr, discr,
             _reference_derivative_matrices, inverse_jac_mat, vec,
             metric_in_matvec=False)
@@ -230,7 +231,8 @@ def local_d_dx(dcoll: DiscretizationCollection, xyz_axis, vec):
     actx = vec.array_context
 
     from grudge.geometry import inverse_surface_metric_derivative_mat
-    inverse_jac_mat = inverse_surface_metric_derivative_mat(actx, dcoll)
+    inverse_jac_mat = inverse_surface_metric_derivative_mat(actx, dcoll,
+            _use_geoderiv_connection=actx.supports_nonscalar_broadcasting)
 
     return _single_axis_derivative_kernel(
         actx, discr, discr,
@@ -374,7 +376,8 @@ def weak_local_grad(dcoll: DiscretizationCollection, *args, nested=False):
 
     actx = vec.array_context
     inverse_jac_mat = inverse_surface_metric_derivative_mat(actx, dcoll, dd=dd_in,
-            times_area_element=True)
+            times_area_element=True,
+            _use_geoderiv_connection=actx.supports_nonscalar_broadcasting)
 
     return _gradient_kernel(actx, out_discr, in_discr,
             _reference_stiffness_transpose_matrix, inverse_jac_mat, vec,
@@ -422,7 +425,8 @@ def weak_local_d_dx(dcoll: DiscretizationCollection, *args):
 
     actx = vec.array_context
     inverse_jac_mat = inverse_surface_metric_derivative_mat(actx, dcoll, dd=dd_in,
-            times_area_element=True)
+            times_area_element=True,
+            _use_geoderiv_connection=actx.supports_nonscalar_broadcasting)
 
     return _single_axis_derivative_kernel(
             actx, out_discr, in_discr, _reference_stiffness_transpose_matrix,
@@ -526,7 +530,8 @@ def _apply_mass_operator(
     out_discr = dcoll.discr_from_dd(dd_out)
 
     actx = vec.array_context
-    area_elements = area_element(actx, dcoll, dd=dd_in)
+    area_elements = area_element(actx, dcoll, dd=dd_in,
+            _use_geoderiv_connection=actx.supports_nonscalar_broadcasting)
     return DOFArray(
         actx,
         data=tuple(
@@ -629,7 +634,8 @@ def _apply_inverse_mass_operator(
 
     actx = vec.array_context
     discr = dcoll.discr_from_dd(dd_in)
-    inv_area_elements = 1./area_element(actx, dcoll, dd=dd_in)
+    inv_area_elements = 1./area_element(actx, dcoll, dd=dd_in,
+            _use_geoderiv_connection=actx.supports_nonscalar_broadcasting)
     group_data = []
     for grp, jac_inv, vec_i in zip(discr.groups, inv_area_elements, vec):
 
@@ -796,7 +802,8 @@ def _apply_face_mass_operator(dcoll: DiscretizationCollection, dd, vec):
     actx = vec.array_context
 
     assert len(face_discr.groups) == len(volm_discr.groups)
-    surf_area_elements = area_element(actx, dcoll, dd=dd)
+    surf_area_elements = area_element(actx, dcoll, dd=dd,
+            _use_geoderiv_connection=actx.supports_nonscalar_broadcasting)
 
     return DOFArray(
         actx,